diff --git a/Cargo.toml b/Cargo.toml index 8b77f22d4..7667942e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ rustybuzz = "0.4" serde = { version = "1", features = ["derive", "rc"] } svg2pdf = { version = "0.1", default-features = false, features = ["text", "png", "jpeg"] } ttf-parser = "0.12" +typst-macros = { path = "./macros" } unicode-bidi = "0.3.5" unicode-segmentation = "1" unicode-xid = "0.2" diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 000000000..6a7f55224 --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "typst-macros" +version = "0.1.0" +authors = ["The Typst Project Developers"] +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1" +quote = "1" +syn = { version = "1", features = ["full"] } diff --git a/macros/src/lib.rs b/macros/src/lib.rs new file mode 100644 index 000000000..3b1d05488 --- /dev/null +++ b/macros/src/lib.rs @@ -0,0 +1,171 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::parse_quote; +use syn::spanned::Spanned; +use syn::{Error, Result}; + +/// Generate node properties. +#[proc_macro_attribute] +pub fn properties(_: TokenStream, item: TokenStream) -> TokenStream { + let impl_block = syn::parse_macro_input!(item as syn::ItemImpl); + expand(impl_block).unwrap_or_else(|err| err.to_compile_error()).into() +} + +/// Expand a property impl block for a node. +fn expand(mut impl_block: syn::ItemImpl) -> Result { + // Split the node type into name and generic type arguments. + let (self_name, self_args) = parse_self(&*impl_block.self_ty)?; + + // Rewrite the const items from values to keys. + let mut style_ids = vec![]; + let mut modules = vec![]; + for item in &mut impl_block.items { + if let syn::ImplItem::Const(item) = item { + let (style_id, module) = process_const(item, &self_name, &self_args)?; + style_ids.push(style_id); + modules.push(module); + } + } + + // Here, we use the collected `style_ids` to provide a function that checks + // whether a property belongs to the node. + impl_block.items.insert(0, parse_quote! { + /// Check whether the property with the given type id belongs to `Self`. + pub fn has_property(id: StyleId) -> bool { + [#(#style_ids),*].contains(&id) + } + }); + + // Put everything into a module with a hopefully unique type to isolate + // it from the outside. + let module = quote::format_ident!("{}_types", self_name); + Ok(quote! { + #[allow(non_snake_case)] + mod #module { + use std::marker::PhantomData; + use once_cell::sync::Lazy; + use crate::eval::{Property, StyleId}; + use super::*; + + #impl_block + #(#modules)* + } + }) +} + +/// Parse the name and generic type arguments of the node type. +fn parse_self(self_ty: &syn::Type) -> Result<(String, Vec<&syn::Type>)> { + // Extract the node type for which we want to generate properties. + let path = match self_ty { + syn::Type::Path(path) => path, + ty => return Err(Error::new(ty.span(), "must be a path type")), + }; + + // Split up the type into its name and its generic type arguments. + let last = path.path.segments.last().unwrap(); + let self_name = last.ident.to_string(); + let self_args = match &last.arguments { + syn::PathArguments::AngleBracketed(args) => args + .args + .iter() + .filter_map(|arg| match arg { + syn::GenericArgument::Type(ty) => Some(ty), + _ => None, + }) + .collect(), + _ => vec![], + }; + + Ok((self_name, self_args)) +} + +/// Process a single const item. +fn process_const( + item: &mut syn::ImplItemConst, + self_name: &str, + self_args: &[&syn::Type], +) -> Result<(syn::Expr, syn::ItemMod)> { + // The type of the property's value is what the user of our macro wrote + // as type of the const ... + let value_ty = &item.ty; + + // ... but the real type of the const becomes Key<#key_param>. + let key_param = if self_args.is_empty() { + quote! { #value_ty } + } else { + quote! { (#value_ty, #(#self_args),*) } + }; + + // The display name, e.g. `TextNode::STRONG`. + let name = format!("{}::{}", self_name, &item.ident); + + // The default value of the property is what the user wrote as + // initialization value of the const. + let default = &item.expr; + + // Look for a folding function like `#[fold(u64::add)]`. + let mut combinator = None; + for attr in &item.attrs { + if attr.path.is_ident("fold") { + let fold: syn::Expr = attr.parse_args()?; + combinator = Some(quote! { + fn combine(inner: Self::Value, outer: Self::Value) -> Self::Value { + let f: fn(Self::Value, Self::Value) -> Self::Value = #fold; + f(inner, outer) + } + }); + } + } + + // The implementation of the `Property` trait. + let property_impl = quote! { + impl Property for Key { + type Value = #value_ty; + + const NAME: &'static str = #name; + + fn default() -> Self::Value { + #default + } + + fn default_ref() -> &'static Self::Value { + static LAZY: Lazy<#value_ty> = Lazy::new(|| #default); + &*LAZY + } + + #combinator + } + }; + + // The module that will contain the `Key` type. + let module_name = &item.ident; + + // Generate the style id and module code. + let style_id = parse_quote! { StyleId::of::<#module_name::Key<#key_param>>() }; + let module = parse_quote! { + #[allow(non_snake_case)] + mod #module_name { + use super::*; + + pub struct Key(pub PhantomData); + impl Copy for Key {} + impl Clone for Key { + fn clone(&self) -> Self { + *self + } + } + + #property_impl + } + }; + + // Replace type and initializer expression with the `Key`. + item.attrs.retain(|attr| !attr.path.is_ident("fold")); + item.ty = parse_quote! { #module_name::Key<#key_param> }; + item.expr = parse_quote! { #module_name::Key(PhantomData) }; + + Ok((style_id, module)) +} diff --git a/src/eval/node.rs b/src/eval/node.rs index a04fe84b7..acdf4ed69 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -1,6 +1,7 @@ use std::convert::TryFrom; use std::fmt::Debug; use std::hash::Hash; +use std::iter::Sum; use std::mem; use std::ops::{Add, AddAssign}; @@ -127,6 +128,12 @@ impl AddAssign for Node { } } +impl Sum for Node { + fn sum>(iter: I) -> Self { + Self::Sequence(iter.map(|n| (n, Styles::new())).collect()) + } +} + /// Packs a [`Node`] into a flow or whole document. struct Packer { /// Whether this packer produces the top-level document. diff --git a/src/eval/styles.rs b/src/eval/styles.rs index 555c2a619..2396646fa 100644 --- a/src/eval/styles.rs +++ b/src/eval/styles.rs @@ -68,7 +68,9 @@ impl Styles { where P::Value: Copy, { - self.get_direct(key).copied().unwrap_or_else(P::default) + self.get_direct(key) + .map(|&v| P::combine(v, P::default())) + .unwrap_or_else(P::default) } /// Get a reference to a style property. @@ -249,9 +251,6 @@ pub trait Property: Copy + 'static { /// The name of the property, used for debug printing. const NAME: &'static str; - /// Combine the property with an outer value. - fn combine(inner: Self::Value, outer: Self::Value) -> Self::Value; - /// The default value of the property. fn default() -> Self::Value; @@ -261,6 +260,11 @@ pub trait Property: Copy + 'static { /// `properties!` macro. This way, expensive defaults don't need to be /// recreated all the time. fn default_ref() -> &'static Self::Value; + + /// Combine the property with an outer value. + fn combine(inner: Self::Value, _: Self::Value) -> Self::Value { + inner + } } /// A unique identifier for a style property. @@ -274,73 +278,6 @@ impl StyleId { } } -/// Generate the property keys for a node. -macro_rules! properties { - ($node:ty, $( - $(#[doc = $doc:expr])* - $(#[fold($combine:expr)])? - $name:ident: $type:ty = $default:expr - ),* $(,)?) => { - // TODO(set): Fix possible name clash. - mod properties { - use std::marker::PhantomData; - use $crate::eval::{Property, StyleId}; - use super::*; - - $(#[allow(non_snake_case)] mod $name { - use once_cell::sync::Lazy; - use super::*; - - pub struct Key(pub PhantomData); - - impl Copy for Key {} - impl Clone for Key { - fn clone(&self) -> Self { - *self - } - } - - impl Property for Key<$type> { - type Value = $type; - - const NAME: &'static str = concat!( - stringify!($node), "::", stringify!($name) - ); - - #[allow(unused_mut, unused_variables)] - fn combine(mut inner: Self::Value, outer: Self::Value) -> Self::Value { - $( - let combine: fn(Self::Value, Self::Value) -> Self::Value = $combine; - inner = combine(inner, outer); - )? - inner - } - - fn default() -> Self::Value { - $default - } - - fn default_ref() -> &'static Self::Value { - static LAZY: Lazy<$type> = Lazy::new(|| $default); - &*LAZY - } - } - })* - - impl $node { - /// Check whether the property with the given type id belongs to - /// `Self`. - pub fn has_property(id: StyleId) -> bool { - false || $(id == StyleId::of::<$name::Key<$type>>())||* - } - - $($(#[doc = $doc])* pub const $name: $name::Key<$type> - = $name::Key(PhantomData);)* - } - } - }; -} - /// Set a style property to a value if the value is `Some`. macro_rules! set { ($styles:expr, $target:expr => $value:expr) => { diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 4ede39b2b..cf714f888 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -18,7 +18,7 @@ use std::rc::Rc; use crate::eval::Styles; use crate::font::FontStore; use crate::frame::Frame; -use crate::geom::{Align, Linear, Point, Sides, Spec, Transform}; +use crate::geom::{Align, Linear, Point, Sides, Size, Spec, Transform}; use crate::image::ImageStore; use crate::library::{AlignNode, DocumentNode, PadNode, SizedNode, TransformNode}; use crate::Context; @@ -232,6 +232,12 @@ impl PackedNode { } } +impl Default for PackedNode { + fn default() -> Self { + EmptyNode.pack() + } +} + impl Debug for PackedNode { fn fmt(&self, f: &mut Formatter) -> fmt::Result { if f.alternate() { @@ -282,3 +288,22 @@ where state.finish() } } + +/// A layout node that produces an empty frame. +/// +/// The packed version of this is returned by [`PackedNode::default`]. +#[derive(Debug, Hash)] +pub struct EmptyNode; + +impl Layout for EmptyNode { + fn layout( + &self, + _: &mut LayoutContext, + regions: &Regions, + ) -> Vec>> { + let size = regions.expand.select(regions.current, Size::zero()); + let mut cts = Constraints::new(regions.expand); + cts.exact = regions.current.filter(regions.expand); + vec![Frame::new(size).constrain(cts)] + } +} diff --git a/src/library/flow.rs b/src/library/flow.rs index de6ab8129..cfa761b6d 100644 --- a/src/library/flow.rs +++ b/src/library/flow.rs @@ -134,9 +134,8 @@ impl<'a> FlowLayouter<'a> { match child { FlowChild::Break(styles) => { let chain = styles.chain(&ctx.styles); - let amount = chain - .get(ParNode::SPACING) - .resolve(chain.get(TextNode::SIZE).abs); + let em = chain.get(TextNode::SIZE).abs; + let amount = chain.get(ParNode::SPACING).resolve(em); self.layout_absolute(amount.into()); } FlowChild::Spacing(node) => match node.kind { diff --git a/src/library/link.rs b/src/library/link.rs index 114d25a11..40604a62d 100644 --- a/src/library/link.rs +++ b/src/library/link.rs @@ -21,9 +21,8 @@ pub fn link(_: &mut EvalContext, args: &mut Args) -> TypResult { #[derive(Debug, Hash)] pub struct LinkNode; -properties! { - LinkNode, - +#[properties] +impl LinkNode { /// An URL to link to. - URL: Option = None, + pub const URL: Option = None; } diff --git a/src/library/mod.rs b/src/library/mod.rs index 1e2ee224b..5852f2bb6 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -26,6 +26,8 @@ mod prelude { pub use std::fmt::{self, Debug, Formatter}; pub use std::rc::Rc; + pub use typst_macros::properties; + pub use crate::diag::{At, TypResult}; pub use crate::eval::{ Args, Construct, EvalContext, Node, Property, Set, Smart, Styles, Value, @@ -80,6 +82,7 @@ pub fn new() -> Scope { std.def_func("block", block); std.def_func("pagebreak", pagebreak); std.def_func("parbreak", parbreak); + std.def_func("linebreak", linebreak); std.def_func("stack", stack); std.def_func("grid", grid); std.def_func("pad", pad); diff --git a/src/library/page.rs b/src/library/page.rs index 0b0cc2d9d..3bb5cbd33 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -20,27 +20,26 @@ pub struct PageNode { pub styles: Styles, } -properties! { - PageNode, - +#[properties] +impl PageNode { /// The unflipped width of the page. - WIDTH: Smart = Smart::Custom(Paper::default().width()), + pub const WIDTH: Smart = Smart::Custom(Paper::default().width()); /// The unflipped height of the page. - HEIGHT: Smart = Smart::Custom(Paper::default().height()), + pub const HEIGHT: Smart = Smart::Custom(Paper::default().height()); /// The class of paper. Defines the default margins. - CLASS: PaperClass = Paper::default().class(), + pub const CLASS: PaperClass = Paper::default().class(); /// Whether the page is flipped into landscape orientation. - FLIPPED: bool = false, + pub const FLIPPED: bool = false; /// The left margin. - LEFT: Smart = Smart::Auto, + pub const LEFT: Smart = Smart::Auto; /// The right margin. - RIGHT: Smart = Smart::Auto, + pub const RIGHT: Smart = Smart::Auto; /// The top margin. - TOP: Smart = Smart::Auto, + pub const TOP: Smart = Smart::Auto; /// The bottom margin. - BOTTOM: Smart = Smart::Auto, + pub const BOTTOM: Smart = Smart::Auto; /// The page's background color. - FILL: Option = None, + pub const FILL: Option = None; } impl Construct for PageNode { @@ -54,29 +53,29 @@ impl Construct for PageNode { impl Set for PageNode { fn set(styles: &mut Styles, args: &mut Args) -> TypResult<()> { if let Some(paper) = args.named::("paper")?.or_else(|| args.find()) { - styles.set(PageNode::CLASS, paper.class()); - styles.set(PageNode::WIDTH, Smart::Custom(paper.width())); - styles.set(PageNode::HEIGHT, Smart::Custom(paper.height())); + styles.set(Self::CLASS, paper.class()); + styles.set(Self::WIDTH, Smart::Custom(paper.width())); + styles.set(Self::HEIGHT, Smart::Custom(paper.height())); } if let Some(width) = args.named("width")? { - styles.set(PageNode::CLASS, PaperClass::Custom); - styles.set(PageNode::WIDTH, width); + styles.set(Self::CLASS, PaperClass::Custom); + styles.set(Self::WIDTH, width); } if let Some(height) = args.named("height")? { - styles.set(PageNode::CLASS, PaperClass::Custom); - styles.set(PageNode::HEIGHT, height); + styles.set(Self::CLASS, PaperClass::Custom); + styles.set(Self::HEIGHT, height); } let margins = args.named("margins")?; - set!(styles, PageNode::FLIPPED => args.named("flipped")?); - set!(styles, PageNode::LEFT => args.named("left")?.or(margins)); - set!(styles, PageNode::TOP => args.named("top")?.or(margins)); - set!(styles, PageNode::RIGHT => args.named("right")?.or(margins)); - set!(styles, PageNode::BOTTOM => args.named("bottom")?.or(margins)); - set!(styles, PageNode::FILL => args.named("fill")?); + set!(styles, Self::FLIPPED => args.named("flipped")?); + set!(styles, Self::LEFT => args.named("left")?.or(margins)); + set!(styles, Self::TOP => args.named("top")?.or(margins)); + set!(styles, Self::RIGHT => args.named("right")?.or(margins)); + set!(styles, Self::BOTTOM => args.named("bottom")?.or(margins)); + set!(styles, Self::FILL => args.named("fill")?); Ok(()) } diff --git a/src/library/par.rs b/src/library/par.rs index d63c23154..9a70b2c71 100644 --- a/src/library/par.rs +++ b/src/library/par.rs @@ -14,21 +14,25 @@ pub fn parbreak(_: &mut EvalContext, _: &mut Args) -> TypResult { Ok(Value::Node(Node::Parbreak)) } +/// `linebreak`: Start a new line. +pub fn linebreak(_: &mut EvalContext, _: &mut Args) -> TypResult { + Ok(Value::Node(Node::Linebreak)) +} + /// A node that arranges its children into a paragraph. #[derive(Hash)] pub struct ParNode(pub Vec); -properties! { - ParNode, - +#[properties] +impl ParNode { /// The direction for text and inline objects. - DIR: Dir = Dir::LTR, + pub const DIR: Dir = Dir::LTR; /// How to align text and inline objects in their line. - ALIGN: Align = Align::Left, + pub const ALIGN: Align = Align::Left; /// The spacing between lines (dependent on scaled font size). - LEADING: Linear = Relative::new(0.65).into(), + pub const LEADING: Linear = Relative::new(0.65).into(); /// The spacing between paragraphs (dependent on scaled font size). - SPACING: Linear = Relative::new(1.2).into(), + pub const SPACING: Linear = Relative::new(1.2).into(); } impl Construct for ParNode { @@ -70,10 +74,10 @@ impl Set for ParNode { align = Some(if dir == Dir::LTR { Align::Left } else { Align::Right }); } - set!(styles, ParNode::DIR => dir); - set!(styles, ParNode::ALIGN => align); - set!(styles, ParNode::LEADING => leading); - set!(styles, ParNode::SPACING => spacing); + set!(styles, Self::DIR => dir); + set!(styles, Self::ALIGN => align); + set!(styles, Self::LEADING => leading); + set!(styles, Self::SPACING => spacing); Ok(()) } @@ -266,11 +270,9 @@ impl<'a> ParLayouter<'a> { } } + let em = ctx.styles.get(TextNode::SIZE).abs; let align = ctx.styles.get(ParNode::ALIGN); - let leading = ctx - .styles - .get(ParNode::LEADING) - .resolve(ctx.styles.get(TextNode::SIZE).abs); + let leading = ctx.styles.get(ParNode::LEADING).resolve(em); Self { align, leading, bidi, items, ranges } } diff --git a/src/library/text.rs b/src/library/text.rs index 4baf4bc5d..e0cbb1ad3 100644 --- a/src/library/text.rs +++ b/src/library/text.rs @@ -51,77 +51,76 @@ pub struct TextNode { pub styles: Styles, } -properties! { - TextNode, - +#[properties] +impl TextNode { /// A prioritized sequence of font families. - FAMILY_LIST: Vec = vec![FontFamily::SansSerif], + pub const FAMILY_LIST: Vec = vec![FontFamily::SansSerif]; /// The serif font family/families. - SERIF_LIST: Vec = vec!["ibm plex serif".into()], + pub const SERIF_LIST: Vec = vec!["ibm plex serif".into()]; /// The sans-serif font family/families. - SANS_SERIF_LIST: Vec = vec!["ibm plex sans".into()], + pub const SANS_SERIF_LIST: Vec = vec!["ibm plex sans".into()]; /// The monospace font family/families. - MONOSPACE_LIST: Vec = vec!["ibm plex mono".into()], + pub const MONOSPACE_LIST: Vec = vec!["ibm plex mono".into()]; /// Whether to allow font fallback when the primary font list contains no /// match. - FALLBACK: bool = true, + pub const FALLBACK: bool = true; /// How the font is styled. - STYLE: FontStyle = FontStyle::Normal, + pub const STYLE: FontStyle = FontStyle::Normal; /// The boldness / thickness of the font's glyphs. - WEIGHT: FontWeight = FontWeight::REGULAR, + pub const WEIGHT: FontWeight = FontWeight::REGULAR; /// The width of the glyphs. - STRETCH: FontStretch = FontStretch::NORMAL, + pub const STRETCH: FontStretch = FontStretch::NORMAL; /// Whether the font weight should be increased by 300. #[fold(bool::bitxor)] - STRONG: bool = false, + pub const STRONG: bool = false; /// Whether the the font style should be inverted. #[fold(bool::bitxor)] - EMPH: bool = false, + pub const EMPH: bool = false; /// Whether a monospace font should be preferred. - MONOSPACE: bool = false, + pub const MONOSPACE: bool = false; /// The glyph fill color. - FILL: Paint = RgbaColor::BLACK.into(), + pub const FILL: Paint = RgbaColor::BLACK.into(); /// Decorative lines. #[fold(|a, b| a.into_iter().chain(b).collect())] - LINES: Vec = vec![], + pub const LINES: Vec = vec![]; /// The size of the glyphs. #[fold(Linear::compose)] - SIZE: Linear = Length::pt(11.0).into(), + pub const SIZE: Linear = Length::pt(11.0).into(); /// The amount of space that should be added between characters. - TRACKING: Em = Em::zero(), + pub const TRACKING: Em = Em::zero(); /// The top end of the text bounding box. - TOP_EDGE: VerticalFontMetric = VerticalFontMetric::CapHeight, + pub const TOP_EDGE: VerticalFontMetric = VerticalFontMetric::CapHeight; /// The bottom end of the text bounding box. - BOTTOM_EDGE: VerticalFontMetric = VerticalFontMetric::Baseline, + pub const BOTTOM_EDGE: VerticalFontMetric = VerticalFontMetric::Baseline; /// Whether to apply kerning ("kern"). - KERNING: bool = true, + pub const KERNING: bool = true; /// Whether small capital glyphs should be used. ("smcp") - SMALLCAPS: bool = false, + pub const SMALLCAPS: bool = false; /// Whether to apply stylistic alternates. ("salt") - ALTERNATES: bool = false, + pub const ALTERNATES: bool = false; /// Which stylistic set to apply. ("ss01" - "ss20") - STYLISTIC_SET: Option = None, + pub const STYLISTIC_SET: Option = None; /// Whether standard ligatures are active. ("liga", "clig") - LIGATURES: bool = true, + pub const LIGATURES: bool = true; /// Whether ligatures that should be used sparingly are active. ("dlig") - DISCRETIONARY_LIGATURES: bool = false, + pub const DISCRETIONARY_LIGATURES: bool = false; /// Whether historical ligatures are active. ("hlig") - HISTORICAL_LIGATURES: bool = false, + pub const HISTORICAL_LIGATURES: bool = false; /// Which kind of numbers / figures to select. - NUMBER_TYPE: Smart = Smart::Auto, + pub const NUMBER_TYPE: Smart = Smart::Auto; /// The width of numbers / figures. - NUMBER_WIDTH: Smart = Smart::Auto, + pub const NUMBER_WIDTH: Smart = Smart::Auto; /// How to position numbers. - NUMBER_POSITION: NumberPosition = NumberPosition::Normal, + pub const NUMBER_POSITION: NumberPosition = NumberPosition::Normal; /// Whether to have a slash through the zero glyph. ("zero") - SLASHED_ZERO: bool = false, + pub const SLASHED_ZERO: bool = false; /// Whether to convert fractions. ("frac") - FRACTIONS: bool = false, + pub const FRACTIONS: bool = false; /// Raw OpenType features to apply. - FEATURES: Vec<(Tag, u32)> = vec![], + pub const FEATURES: Vec<(Tag, u32)> = vec![]; } impl Construct for TextNode { @@ -140,32 +139,32 @@ impl Set for TextNode { (!families.is_empty()).then(|| families) }); - set!(styles, TextNode::FAMILY_LIST => list); - set!(styles, TextNode::SERIF_LIST => args.named("serif")?); - set!(styles, TextNode::SANS_SERIF_LIST => args.named("sans-serif")?); - set!(styles, TextNode::MONOSPACE_LIST => args.named("monospace")?); - set!(styles, TextNode::FALLBACK => args.named("fallback")?); - set!(styles, TextNode::STYLE => args.named("style")?); - set!(styles, TextNode::WEIGHT => args.named("weight")?); - set!(styles, TextNode::STRETCH => args.named("stretch")?); - set!(styles, TextNode::FILL => args.named("fill")?.or_else(|| args.find())); - set!(styles, TextNode::SIZE => args.named("size")?.or_else(|| args.find())); - set!(styles, TextNode::TRACKING => args.named("tracking")?.map(Em::new)); - set!(styles, TextNode::TOP_EDGE => args.named("top-edge")?); - set!(styles, TextNode::BOTTOM_EDGE => args.named("bottom-edge")?); - set!(styles, TextNode::KERNING => args.named("kerning")?); - set!(styles, TextNode::SMALLCAPS => args.named("smallcaps")?); - set!(styles, TextNode::ALTERNATES => args.named("alternates")?); - set!(styles, TextNode::STYLISTIC_SET => args.named("stylistic-set")?); - set!(styles, TextNode::LIGATURES => args.named("ligatures")?); - set!(styles, TextNode::DISCRETIONARY_LIGATURES => args.named("discretionary-ligatures")?); - set!(styles, TextNode::HISTORICAL_LIGATURES => args.named("historical-ligatures")?); - set!(styles, TextNode::NUMBER_TYPE => args.named("number-type")?); - set!(styles, TextNode::NUMBER_WIDTH => args.named("number-width")?); - set!(styles, TextNode::NUMBER_POSITION => args.named("number-position")?); - set!(styles, TextNode::SLASHED_ZERO => args.named("slashed-zero")?); - set!(styles, TextNode::FRACTIONS => args.named("fractions")?); - set!(styles, TextNode::FEATURES => args.named("features")?); + set!(styles, Self::FAMILY_LIST => list); + set!(styles, Self::SERIF_LIST => args.named("serif")?); + set!(styles, Self::SANS_SERIF_LIST => args.named("sans-serif")?); + set!(styles, Self::MONOSPACE_LIST => args.named("monospace")?); + set!(styles, Self::FALLBACK => args.named("fallback")?); + set!(styles, Self::STYLE => args.named("style")?); + set!(styles, Self::WEIGHT => args.named("weight")?); + set!(styles, Self::STRETCH => args.named("stretch")?); + set!(styles, Self::FILL => args.named("fill")?.or_else(|| args.find())); + set!(styles, Self::SIZE => args.named("size")?.or_else(|| args.find())); + set!(styles, Self::TRACKING => args.named("tracking")?.map(Em::new)); + set!(styles, Self::TOP_EDGE => args.named("top-edge")?); + set!(styles, Self::BOTTOM_EDGE => args.named("bottom-edge")?); + set!(styles, Self::KERNING => args.named("kerning")?); + set!(styles, Self::SMALLCAPS => args.named("smallcaps")?); + set!(styles, Self::ALTERNATES => args.named("alternates")?); + set!(styles, Self::STYLISTIC_SET => args.named("stylistic-set")?); + set!(styles, Self::LIGATURES => args.named("ligatures")?); + set!(styles, Self::DISCRETIONARY_LIGATURES => args.named("discretionary-ligatures")?); + set!(styles, Self::HISTORICAL_LIGATURES => args.named("historical-ligatures")?); + set!(styles, Self::NUMBER_TYPE => args.named("number-type")?); + set!(styles, Self::NUMBER_WIDTH => args.named("number-width")?); + set!(styles, Self::NUMBER_POSITION => args.named("number-position")?); + set!(styles, Self::SLASHED_ZERO => args.named("slashed-zero")?); + set!(styles, Self::FRACTIONS => args.named("fractions")?); + set!(styles, Self::FEATURES => args.named("features")?); Ok(()) }