Set Rules Episode VIII: The First Macro

This commit is contained in:
Laurenz 2021-12-16 14:43:02 +01:00
parent 2a3d0f4b39
commit 958f74f777
12 changed files with 333 additions and 178 deletions

View File

@ -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"

13
macros/Cargo.toml Normal file
View File

@ -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"] }

171
macros/src/lib.rs Normal file
View File

@ -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<TokenStream2> {
// 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<T: 'static> Property for Key<T> {
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<T>(pub PhantomData<T>);
impl<T> Copy for Key<T> {}
impl<T> Clone for Key<T> {
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))
}

View File

@ -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<I: Iterator<Item = Self>>(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.

View File

@ -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<T>(pub PhantomData<T>);
impl<T> Copy for Key<T> {}
impl<T> Clone for Key<T> {
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) => {

View File

@ -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<Constrained<Rc<Frame>>> {
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)]
}
}

View File

@ -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 {

View File

@ -21,9 +21,8 @@ pub fn link(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
#[derive(Debug, Hash)]
pub struct LinkNode;
properties! {
LinkNode,
#[properties]
impl LinkNode {
/// An URL to link to.
URL: Option<String> = None,
pub const URL: Option<String> = None;
}

View File

@ -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);

View File

@ -20,27 +20,26 @@ pub struct PageNode {
pub styles: Styles,
}
properties! {
PageNode,
#[properties]
impl PageNode {
/// The unflipped width of the page.
WIDTH: Smart<Length> = Smart::Custom(Paper::default().width()),
pub const WIDTH: Smart<Length> = Smart::Custom(Paper::default().width());
/// The unflipped height of the page.
HEIGHT: Smart<Length> = Smart::Custom(Paper::default().height()),
pub const HEIGHT: Smart<Length> = 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<Linear> = Smart::Auto,
pub const LEFT: Smart<Linear> = Smart::Auto;
/// The right margin.
RIGHT: Smart<Linear> = Smart::Auto,
pub const RIGHT: Smart<Linear> = Smart::Auto;
/// The top margin.
TOP: Smart<Linear> = Smart::Auto,
pub const TOP: Smart<Linear> = Smart::Auto;
/// The bottom margin.
BOTTOM: Smart<Linear> = Smart::Auto,
pub const BOTTOM: Smart<Linear> = Smart::Auto;
/// The page's background color.
FILL: Option<Paint> = None,
pub const FILL: Option<Paint> = 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>("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(())
}

View File

@ -14,21 +14,25 @@ pub fn parbreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
Ok(Value::Node(Node::Parbreak))
}
/// `linebreak`: Start a new line.
pub fn linebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
Ok(Value::Node(Node::Linebreak))
}
/// A node that arranges its children into a paragraph.
#[derive(Hash)]
pub struct ParNode(pub Vec<ParChild>);
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 }
}

View File

@ -51,77 +51,76 @@ pub struct TextNode {
pub styles: Styles,
}
properties! {
TextNode,
#[properties]
impl TextNode {
/// A prioritized sequence of font families.
FAMILY_LIST: Vec<FontFamily> = vec![FontFamily::SansSerif],
pub const FAMILY_LIST: Vec<FontFamily> = vec![FontFamily::SansSerif];
/// The serif font family/families.
SERIF_LIST: Vec<String> = vec!["ibm plex serif".into()],
pub const SERIF_LIST: Vec<String> = vec!["ibm plex serif".into()];
/// The sans-serif font family/families.
SANS_SERIF_LIST: Vec<String> = vec!["ibm plex sans".into()],
pub const SANS_SERIF_LIST: Vec<String> = vec!["ibm plex sans".into()];
/// The monospace font family/families.
MONOSPACE_LIST: Vec<String> = vec!["ibm plex mono".into()],
pub const MONOSPACE_LIST: Vec<String> = 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<LineDecoration> = vec![],
pub const LINES: Vec<LineDecoration> = 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<StylisticSet> = None,
pub const STYLISTIC_SET: Option<StylisticSet> = 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<NumberType> = Smart::Auto,
pub const NUMBER_TYPE: Smart<NumberType> = Smart::Auto;
/// The width of numbers / figures.
NUMBER_WIDTH: Smart<NumberWidth> = Smart::Auto,
pub const NUMBER_WIDTH: Smart<NumberWidth> = 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(())
}