summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--library/src/layout/par.rs19
-rw-r--r--library/src/math/matrix.rs36
-rw-r--r--library/src/text/misc.rs15
-rw-r--r--library/src/text/mod.rs44
-rw-r--r--library/src/visualize/image.rs24
-rw-r--r--macros/Cargo.toml1
-rw-r--r--macros/src/castable.rs62
-rw-r--r--macros/src/lib.rs11
-rw-r--r--macros/src/util.rs4
-rw-r--r--src/eval/cast.rs2
-rw-r--r--src/font/mod.rs38
-rw-r--r--src/font/variant.rs22
13 files changed, 108 insertions, 171 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 5a0784ee..ac4c3a1d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1269,6 +1269,7 @@ dependencies = [
name = "typst-macros"
version = "0.0.0"
dependencies = [
+ "heck",
"proc-macro2",
"quote",
"syn",
diff --git a/library/src/layout/par.rs b/library/src/layout/par.rs
index 6178c059..2d7c5d62 100644
--- a/library/src/layout/par.rs
+++ b/library/src/layout/par.rs
@@ -193,30 +193,15 @@ impl Resolve for HorizontalAlign {
}
/// How to determine line breaks in a paragraph.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum Linebreaks {
/// Determine the line breaks in a simple first-fit style.
Simple,
/// Optimize the line breaks for the whole paragraph.
- Optimized,
-}
-
-cast_from_value! {
- Linebreaks,
- /// Determine the line breaks in a simple first-fit style.
- "simple" => Self::Simple,
- /// Optimize the line breaks for the whole paragraph.
///
/// Typst will try to produce more evenly filled lines of text by
/// considering the whole paragraph when calculating line breaks.
- "optimized" => Self::Optimized,
-}
-
-cast_to_value! {
- v: Linebreaks => Value::from(match v {
- Linebreaks::Simple => "simple",
- Linebreaks::Optimized => "optimized",
- })
+ Optimized,
}
/// A paragraph break.
diff --git a/library/src/math/matrix.rs b/library/src/math/matrix.rs
index 148d79e8..d79c7ca5 100644
--- a/library/src/math/matrix.rs
+++ b/library/src/math/matrix.rs
@@ -169,12 +169,22 @@ impl LayoutMath for CasesNode {
}
/// A vector / matrix delimiter.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum Delimiter {
+ /// Delimit with parentheses.
+ #[string("(")]
Paren,
+ /// Delimit with brackets.
+ #[string("[")]
Bracket,
+ /// Delimit with curly braces.
+ #[string("{")]
Brace,
+ /// Delimit with vertical bars.
+ #[string("|")]
Bar,
+ /// Delimit with double vertical bars.
+ #[string("||")]
DoubleBar,
}
@@ -202,30 +212,6 @@ impl Delimiter {
}
}
-cast_from_value! {
- Delimiter,
- /// Delimit with parentheses.
- "(" => Self::Paren,
- /// Delimit with brackets.
- "[" => Self::Bracket,
- /// Delimit with curly braces.
- "{" => Self::Brace,
- /// Delimit with vertical bars.
- "|" => Self::Bar,
- /// Delimit with double vertical bars.
- "||" => Self::DoubleBar,
-}
-
-cast_to_value! {
- v: Delimiter => Value::from(match v {
- Delimiter::Paren => "(",
- Delimiter::Bracket => "[",
- Delimiter::Brace => "{",
- Delimiter::Bar => "|",
- Delimiter::DoubleBar => "||",
- })
-}
-
/// Layout the inner contents of a vector.
fn layout_vec_body(
ctx: &mut MathContext,
diff --git a/library/src/text/misc.rs b/library/src/text/misc.rs
index 029e15f4..5a5c8514 100644
--- a/library/src/text/misc.rs
+++ b/library/src/text/misc.rs
@@ -246,7 +246,7 @@ cast_from_value! {
}
/// A case transformation on text.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum Case {
/// Everything is lowercased.
Lower,
@@ -264,19 +264,6 @@ impl Case {
}
}
-cast_from_value! {
- Case,
- "lower" => Self::Lower,
- "upper" => Self::Upper,
-}
-
-cast_to_value! {
- v: Case => Value::from(match v {
- Case::Lower => "lower",
- Case::Upper => "upper",
- })
-}
-
/// Display text in small capitals.
///
/// _Note:_ This enables the OpenType `smcp` feature for the font. Not all fonts
diff --git a/library/src/text/mod.rs b/library/src/text/mod.rs
index a81ef3d7..845ffe29 100644
--- a/library/src/text/mod.rs
+++ b/library/src/text/mod.rs
@@ -686,53 +686,23 @@ cast_to_value! {
}
/// Which kind of numbers / figures to select.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum NumberType {
- /// Numbers that fit well with capital text. ("lnum")
- Lining,
- /// Numbers that fit well into a flow of upper- and lowercase text. ("onum")
- OldStyle,
-}
-
-cast_from_value! {
- NumberType,
/// Numbers that fit well with capital text (the OpenType `lnum`
/// font feature).
- "lining" => Self::Lining,
- // Numbers that fit well into a flow of upper- and lowercase text (the
+ Lining,
+ /// Numbers that fit well into a flow of upper- and lowercase text (the
/// OpenType `onum` font feature).
- "old-style" => Self::OldStyle,
-}
-
-cast_to_value! {
- v: NumberType => Value::from(match v {
- NumberType::Lining => "lining",
- NumberType::OldStyle => "old-style",
- })
+ OldStyle,
}
/// The width of numbers / figures.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum NumberWidth {
- /// Number widths are glyph specific. ("pnum")
- Proportional,
- /// All numbers are of equal width / monospaced. ("tnum")
- Tabular,
-}
-
-cast_from_value! {
- NumberWidth,
/// Numbers with glyph-specific widths (the OpenType `pnum` font feature).
- "proportional" => Self::Proportional,
+ Proportional,
/// Numbers of equal width (the OpenType `tnum` font feature).
- "tabular" => Self::Tabular,
-}
-
-cast_to_value! {
- v: NumberWidth => Value::from(match v {
- NumberWidth::Proportional => "proportional",
- NumberWidth::Tabular => "tabular",
- })
+ Tabular,
}
/// OpenType font features settings.
diff --git a/library/src/visualize/image.rs b/library/src/visualize/image.rs
index 29a04c96..4509cf5a 100644
--- a/library/src/visualize/image.rs
+++ b/library/src/visualize/image.rs
@@ -113,33 +113,15 @@ impl Layout for ImageNode {
}
/// How an image should adjust itself to a given area.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum ImageFit {
- /// The image should completely cover the area.
+ /// The image should completely cover the area. This is the default.
Cover,
/// The image should be fully contained in the area.
Contain,
- /// The image should be stretched so that it exactly fills the area.
- Stretch,
-}
-
-cast_from_value! {
- ImageFit,
- /// The image should completely cover the area. This is the default.
- "cover" => Self::Cover,
- /// The image should be fully contained in the area.
- "contain" => Self::Contain,
/// The image should be stretched so that it exactly fills the area, even if
/// this means that the image will be distorted.
- "stretch" => Self::Stretch,
-}
-
-cast_to_value! {
- fit: ImageFit => Value::from(match fit {
- ImageFit::Cover => "cover",
- ImageFit::Contain => "contain",
- ImageFit::Stretch => "stretch",
- })
+ Stretch,
}
/// Load an image from a path.
diff --git a/macros/Cargo.toml b/macros/Cargo.toml
index 8b4280fe..24d537be 100644
--- a/macros/Cargo.toml
+++ b/macros/Cargo.toml
@@ -15,3 +15,4 @@ proc-macro2 = "1"
quote = "1"
syn = { version = "1", features = ["full", "extra-traits"] }
unscanny = "0.1"
+heck = "0.4"
diff --git a/macros/src/castable.rs b/macros/src/castable.rs
index c0d0c1ad..cd05ed2d 100644
--- a/macros/src/castable.rs
+++ b/macros/src/castable.rs
@@ -1,5 +1,67 @@
use super::*;
+/// Expand the `#[derive(Cast)]` macro.
+pub fn cast(item: DeriveInput) -> Result<TokenStream> {
+ let ty = &item.ident;
+
+ let syn::Data::Enum(data) = &item.data else {
+ bail!(item, "only enums are supported");
+ };
+
+ let mut variants = vec![];
+ for variant in &data.variants {
+ if let Some((_, expr)) = &variant.discriminant {
+ bail!(expr, "explicit discriminant is not allowed");
+ }
+
+ let string = if let Some(attr) =
+ variant.attrs.iter().find(|attr| attr.path.is_ident("string"))
+ {
+ attr.parse_args::<syn::LitStr>()?.value()
+ } else {
+ kebab_case(&variant.ident)
+ };
+
+ variants.push(Variant {
+ ident: variant.ident.clone(),
+ string,
+ docs: documentation(&variant.attrs),
+ });
+ }
+
+ let strs_to_variants = variants.iter().map(|Variant { ident, string, docs }| {
+ quote! {
+ #[doc = #docs]
+ #string => Self::#ident
+ }
+ });
+
+ let variants_to_strs = variants.iter().map(|Variant { ident, string, .. }| {
+ quote! {
+ #ty::#ident => #string
+ }
+ });
+
+ Ok(quote! {
+ ::typst::eval::cast_from_value! {
+ #ty,
+ #(#strs_to_variants),*
+ }
+
+ ::typst::eval::cast_to_value! {
+ v: #ty => ::typst::eval::Value::from(match v {
+ #(#variants_to_strs),*
+ })
+ }
+ })
+}
+
+struct Variant {
+ ident: Ident,
+ string: String,
+ docs: String,
+}
+
/// Expand the `cast_from_value!` macro.
pub fn cast_from_value(stream: TokenStream) -> Result<TokenStream> {
let castable: Castable = syn::parse2(stream)?;
diff --git a/macros/src/lib.rs b/macros/src/lib.rs
index 889eaa7b..fafe8eea 100644
--- a/macros/src/lib.rs
+++ b/macros/src/lib.rs
@@ -15,7 +15,7 @@ use quote::quote;
use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream, Parser};
use syn::punctuated::Punctuated;
-use syn::{parse_quote, Ident, Result, Token};
+use syn::{parse_quote, DeriveInput, Ident, Result, Token};
use self::util::*;
@@ -35,6 +35,15 @@ pub fn node(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
.into()
}
+/// Implement `Cast` for an enum.
+#[proc_macro_derive(Cast, attributes(string))]
+pub fn cast(item: BoundaryStream) -> BoundaryStream {
+ let item = syn::parse_macro_input!(item as DeriveInput);
+ castable::cast(item)
+ .unwrap_or_else(|err| err.to_compile_error())
+ .into()
+}
+
/// Implement `Cast` and optionally `Type` for a type.
#[proc_macro]
pub fn cast_from_value(stream: BoundaryStream) -> BoundaryStream {
diff --git a/macros/src/util.rs b/macros/src/util.rs
index d94ba932..53a8354e 100644
--- a/macros/src/util.rs
+++ b/macros/src/util.rs
@@ -1,3 +1,5 @@
+use heck::ToKebabCase;
+
use super::*;
/// Return an error at the given item.
@@ -55,7 +57,7 @@ pub fn validate_attrs(attrs: &[syn::Attribute]) -> Result<()> {
/// Convert an identifier to a kebab-case string.
pub fn kebab_case(name: &Ident) -> String {
- name.to_string().to_lowercase().replace('_', "-")
+ name.to_string().to_kebab_case()
}
/// Extract documentation comments from an attribute list.
diff --git a/src/eval/cast.rs b/src/eval/cast.rs
index ac23bd3a..7b507dbb 100644
--- a/src/eval/cast.rs
+++ b/src/eval/cast.rs
@@ -1,4 +1,4 @@
-pub use typst_macros::{cast_from_value, cast_to_value};
+pub use typst_macros::{cast_from_value, cast_to_value, Cast};
use std::num::{NonZeroI64, NonZeroUsize};
use std::ops::Add;
diff --git a/src/font/mod.rs b/src/font/mod.rs
index 94ec170e..bfb790bb 100644
--- a/src/font/mod.rs
+++ b/src/font/mod.rs
@@ -12,7 +12,7 @@ use std::sync::Arc;
use ttf_parser::GlyphId;
-use crate::eval::{cast_from_value, cast_to_value, Value};
+use crate::eval::Cast;
use crate::geom::Em;
use crate::util::Buffer;
@@ -231,12 +231,9 @@ pub struct LineMetrics {
}
/// Identifies a vertical metric of a font.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum VerticalFontMetric {
- /// The typographic ascender.
- ///
- /// Corresponds to the typographic ascender from the `OS/2` table if present
- /// and falls back to the ascender from the `hhea` table otherwise.
+ /// The font's ascender, which typically exceeds the height of all glyphs.
Ascender,
/// The approximate height of uppercase letters.
CapHeight,
@@ -244,33 +241,6 @@ pub enum VerticalFontMetric {
XHeight,
/// The baseline on which the letters rest.
Baseline,
- /// The typographic descender.
- ///
- /// Corresponds to the typographic descender from the `OS/2` table if
- /// present and falls back to the descender from the `hhea` table otherwise.
- Descender,
-}
-
-cast_from_value! {
- VerticalFontMetric,
- /// The font's ascender, which typically exceeds the height of all glyphs.
- "ascender" => Self::Ascender,
- /// The approximate height of uppercase letters.
- "cap-height" => Self::CapHeight,
- /// The approximate height of non-ascending lowercase letters.
- "x-height" => Self::XHeight,
- /// The baseline on which the letters rest.
- "baseline" => Self::Baseline,
/// The font's ascender, which typically exceeds the depth of all glyphs.
- "descender" => Self::Descender,
-}
-
-cast_to_value! {
- v: VerticalFontMetric => Value::from(match v {
- VerticalFontMetric::Ascender => "ascender",
- VerticalFontMetric::CapHeight => "cap-height",
- VerticalFontMetric::XHeight => "x-height",
- VerticalFontMetric::Baseline => "baseline" ,
- VerticalFontMetric::Descender => "descender",
- })
+ Descender,
}
diff --git a/src/font/variant.rs b/src/font/variant.rs
index 4eda80ad..9391aae1 100644
--- a/src/font/variant.rs
+++ b/src/font/variant.rs
@@ -2,7 +2,7 @@ use std::fmt::{self, Debug, Formatter};
use serde::{Deserialize, Serialize};
-use crate::eval::{cast_from_value, cast_to_value, Value};
+use crate::eval::{cast_from_value, cast_to_value, Cast, Value};
use crate::geom::Ratio;
/// Properties that distinguish a font from other fonts in the same family.
@@ -32,7 +32,7 @@ impl Debug for FontVariant {
/// The style of a font.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Cast)]
#[serde(rename_all = "kebab-case")]
pub enum FontStyle {
/// The default, typically upright style.
@@ -62,24 +62,6 @@ impl Default for FontStyle {
}
}
-cast_from_value! {
- FontStyle,
- /// The default, typically upright style.
- "normal" => Self::Normal,
- /// A cursive style with custom letterform.
- "italic" => Self::Italic,
- /// Just a slanted version of the normal style.
- "oblique" => Self::Oblique,
-}
-
-cast_to_value! {
- v: FontStyle => Value::from(match v {
- FontStyle::Normal => "normal",
- FontStyle::Italic => "italic",
- FontStyle::Oblique => "oblique",
- })
-}
-
/// The weight of a font.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[derive(Serialize, Deserialize)]