Set Rules Episode VIII: The First Macro
This commit is contained in:
parent
2a3d0f4b39
commit
958f74f777
@ -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
13
macros/Cargo.toml
Normal 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
171
macros/src/lib.rs
Normal 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))
|
||||
}
|
@ -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.
|
||||
|
@ -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) => {
|
||||
|
@ -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)]
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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 }
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user