Rework style chains

This commit is contained in:
Laurenz 2022-04-07 10:50:39 +02:00
parent 20b4d590b3
commit 3d52387eea
23 changed files with 785 additions and 644 deletions

View File

@ -1,8 +1,8 @@
extern crate proc_macro; extern crate proc_macro;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::{TokenStream as TokenStream2, TokenTree};
use quote::quote; use quote::{quote, quote_spanned};
use syn::parse_quote; use syn::parse_quote;
use syn::punctuated::Punctuated; use syn::punctuated::Punctuated;
use syn::spanned::Spanned; use syn::spanned::Spanned;
@ -54,7 +54,7 @@ fn expand(stream: TokenStream2, mut impl_block: syn::ItemImpl) -> Result<TokenSt
construct.ok_or_else(|| Error::new(impl_block.span(), "missing constructor"))?; construct.ok_or_else(|| Error::new(impl_block.span(), "missing constructor"))?;
let set = set.unwrap_or_else(|| { let set = set.unwrap_or_else(|| {
let sets = properties.into_iter().filter(|p| !p.skip).map(|property| { let sets = properties.into_iter().filter(|p| !p.hidden).map(|property| {
let name = property.name; let name = property.name;
let string = name.to_string().replace("_", "-").to_lowercase(); let string = name.to_string().replace("_", "-").to_lowercase();
@ -116,14 +116,6 @@ fn expand(stream: TokenStream2, mut impl_block: syn::ItemImpl) -> Result<TokenSt
}) })
} }
/// A style property.
struct Property {
name: Ident,
shorthand: bool,
variadic: bool,
skip: bool,
}
/// Parse the name and generic type arguments of the node type. /// Parse the name and generic type arguments of the node type.
fn parse_self( fn parse_self(
self_ty: &syn::Type, self_ty: &syn::Type,
@ -153,12 +145,21 @@ fn process_const(
self_name: &str, self_name: &str,
self_args: &Punctuated<syn::GenericArgument, syn::Token![,]>, self_args: &Punctuated<syn::GenericArgument, syn::Token![,]>,
) -> Result<(Property, syn::ItemMod)> { ) -> Result<(Property, syn::ItemMod)> {
let property = parse_property(item)?;
// The display name, e.g. `TextNode::STRONG`. // The display name, e.g. `TextNode::STRONG`.
let name = format!("{}::{}", self_name, &item.ident); let name = format!("{}::{}", self_name, &item.ident);
// The type of the property's value is what the user of our macro wrote // The type of the property's value is what the user of our macro wrote
// as type of the const ... // as type of the const ...
let value_ty = &item.ty; let value_ty = &item.ty;
let output_ty = if property.fold {
parse_quote!(<#value_ty as eval::Fold>::Output)
} else if property.referenced {
parse_quote!(&'a #value_ty)
} else {
value_ty.clone()
};
// ... but the real type of the const becomes this.. // ... but the real type of the const becomes this..
let key = quote! { Key<#value_ty, #self_args> }; let key = quote! { Key<#value_ty, #self_args> };
@ -172,50 +173,41 @@ fn process_const(
_ => true, _ => true,
}); });
// The default value of the property is what the user wrote as
// initialization value of the const.
let default = &item.expr; let default = &item.expr;
let mut fold = None; // Ensure that the type is either `Copy` or that the property is referenced
let mut property = Property { // or that the property isn't copy but can't be referenced because it needs
name: item.ident.clone(), // folding.
shorthand: false, let get;
variadic: false, let mut copy = None;
skip: false,
};
for attr in std::mem::take(&mut item.attrs) { if property.referenced {
match attr.path.get_ident().map(ToString::to_string).as_deref() { get = quote! {
Some("fold") => { values.next().unwrap_or_else(|| {
// Look for a folding function like `#[fold(u64::add)]`. static LAZY: Lazy<#value_ty> = Lazy::new(|| #default);
let func: syn::Expr = attr.parse_args()?; &*LAZY
fold = Some(quote! { })
const FOLDING: bool = true; };
} else if property.fold {
fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value { get = quote! {
let f: fn(Self::Value, Self::Value) -> Self::Value = #func; match values.next().cloned() {
f(inner, outer) Some(inner) => eval::Fold::fold(inner, Self::get(values)),
} None => #default,
});
} }
Some("shorthand") => property.shorthand = true, };
Some("variadic") => property.variadic = true, } else {
Some("skip") => property.skip = true, get = quote! {
_ => item.attrs.push(attr), values.next().copied().unwrap_or(#default)
} };
}
if property.shorthand && property.variadic { copy = Some(quote_spanned! { item.ty.span() =>
return Err(Error::new( const _: fn() -> () = || {
property.name.span(), fn type_must_be_copy_or_fold_or_referenced<T: Copy>() {}
"shorthand and variadic are mutually exclusive", type_must_be_copy_or_fold_or_referenced::<#value_ty>();
)); };
});
} }
let referencable = fold.is_none().then(|| {
quote! { impl<#params> eval::Referencable for #key {} }
});
// Generate the module code. // Generate the module code.
let module_name = &item.ident; let module_name = &item.ident;
let module = parse_quote! { let module = parse_quote! {
@ -232,28 +224,21 @@ fn process_const(
} }
} }
impl<#params> eval::Key for #key { impl<'a, #params> eval::Key<'a> for #key {
type Value = #value_ty; type Value = #value_ty;
type Output = #output_ty;
const NAME: &'static str = #name; const NAME: &'static str = #name;
fn node_id() -> TypeId { fn node() -> TypeId {
TypeId::of::<#self_ty>() TypeId::of::<#self_ty>()
} }
fn default() -> Self::Value { fn get(mut values: impl Iterator<Item = &'a Self::Value>) -> Self::Output {
#default #get
} }
fn default_ref() -> &'static Self::Value {
static LAZY: Lazy<#value_ty> = Lazy::new(|| #default);
&*LAZY
}
#fold
} }
#referencable #copy
} }
}; };
@ -263,3 +248,64 @@ fn process_const(
Ok((property, module)) Ok((property, module))
} }
/// A style property.
struct Property {
name: Ident,
hidden: bool,
referenced: bool,
shorthand: bool,
variadic: bool,
fold: bool,
}
/// Parse a style property attribute.
fn parse_property(item: &mut syn::ImplItemConst) -> Result<Property> {
let mut property = Property {
name: item.ident.clone(),
hidden: false,
referenced: false,
shorthand: false,
variadic: false,
fold: false,
};
if let Some(idx) = item
.attrs
.iter()
.position(|attr| attr.path.get_ident().map_or(false, |name| name == "property"))
{
let attr = item.attrs.remove(idx);
for token in attr.parse_args::<TokenStream2>()? {
match token {
TokenTree::Ident(ident) => match ident.to_string().as_str() {
"hidden" => property.hidden = true,
"shorthand" => property.shorthand = true,
"referenced" => property.referenced = true,
"variadic" => property.variadic = true,
"fold" => property.fold = true,
_ => return Err(Error::new(ident.span(), "invalid attribute")),
},
TokenTree::Punct(_) => {}
_ => return Err(Error::new(token.span(), "invalid token")),
}
}
}
let span = property.name.span();
if property.shorthand && property.variadic {
return Err(Error::new(
span,
"shorthand and variadic are mutually exclusive",
));
}
if property.referenced && property.fold {
return Err(Error::new(
span,
"referenced and fold are mutually exclusive",
));
}
Ok(property)
}

View File

@ -102,17 +102,15 @@ impl Content {
} }
/// Style this content with a single style property. /// Style this content with a single style property.
pub fn styled<P: Key>(mut self, key: P, value: P::Value) -> Self { pub fn styled<'k, K: Key<'k>>(mut self, key: K, value: K::Value) -> Self {
if let Self::Styled(styled) = &mut self { if let Self::Styled(styled) = &mut self {
if let Some((_, map)) = Arc::get_mut(styled) { if let Some((_, map)) = Arc::get_mut(styled) {
if !map.has_scoped() { map.apply(key, value);
map.set(key, value); return self;
return self;
}
} }
} }
self.styled_with_map(StyleMap::with(key, value)) Self::Styled(Arc::new((self, StyleMap::with(key, value))))
} }
/// Style this content with a full style map. /// Style this content with a full style map.
@ -123,10 +121,8 @@ impl Content {
if let Self::Styled(styled) = &mut self { if let Self::Styled(styled) = &mut self {
if let Some((_, map)) = Arc::get_mut(styled) { if let Some((_, map)) = Arc::get_mut(styled) {
if !styles.has_scoped() && !map.has_scoped() { map.apply_map(&styles);
map.apply(&styles); return self;
return self;
}
} }
} }
@ -161,7 +157,7 @@ impl Content {
let tpa = Arena::new(); let tpa = Arena::new();
let styles = ctx.styles.clone(); let styles = ctx.styles.clone();
let styles = StyleChain::new(&styles); let styles = StyleChain::with_root(&styles);
let mut builder = Builder::new(&sya, &tpa, true); let mut builder = Builder::new(&sya, &tpa, true);
builder.process(ctx, self, styles)?; builder.process(ctx, self, styles)?;

View File

@ -1,4 +1,3 @@
use std::any::TypeId;
use std::fmt::{self, Debug, Formatter, Write}; use std::fmt::{self, Debug, Formatter, Write};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::sync::Arc; use std::sync::Arc;
@ -52,7 +51,7 @@ impl Func {
show: if T::SHOWABLE { show: if T::SHOWABLE {
Some(|recipe, span| { Some(|recipe, span| {
let mut styles = StyleMap::new(); let mut styles = StyleMap::new();
styles.set_recipe(TypeId::of::<T>(), recipe, span); styles.set_recipe::<T>(recipe, span);
styles styles
}) })
} else { } else {

View File

@ -1,12 +1,12 @@
//! Layouting infrastructure. //! Layouting infrastructure.
use std::any::{Any, TypeId}; use std::any::Any;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::hash::Hash; use std::hash::Hash;
use std::sync::Arc; use std::sync::Arc;
use super::{Barrier, StyleChain};
use crate::diag::TypResult; use crate::diag::TypResult;
use crate::eval::StyleChain;
use crate::frame::{Element, Frame, Geometry, Shape, Stroke}; use crate::frame::{Element, Frame, Geometry, Shape, Stroke};
use crate::geom::{Align, Length, Linear, Paint, Point, Sides, Size, Spec, Transform}; use crate::geom::{Align, Length, Linear, Paint, Point, Sides, Size, Spec, Transform};
use crate::library::graphics::MoveNode; use crate::library::graphics::MoveNode;
@ -18,7 +18,7 @@ use crate::Context;
/// ///
/// Layout return one frame per used region alongside constraints that define /// Layout return one frame per used region alongside constraints that define
/// whether the result is reusable in other regions. /// whether the result is reusable in other regions.
pub trait Layout { pub trait Layout: 'static {
/// Layout this node into the given regions, producing constrained frames. /// Layout this node into the given regions, producing constrained frames.
fn layout( fn layout(
&self, &self,
@ -144,12 +144,12 @@ impl LayoutNode {
/// Check whether the contained node is a specific layout node. /// Check whether the contained node is a specific layout node.
pub fn is<T: 'static>(&self) -> bool { pub fn is<T: 'static>(&self) -> bool {
self.0.as_any().is::<T>() (**self.0).as_any().is::<T>()
} }
/// The type id of this node. /// A barrier for the node.
pub fn id(&self) -> TypeId { pub fn barrier(&self) -> Barrier {
self.0.as_any().type_id() (**self.0).barrier()
} }
/// Try to downcast to a specific layout node. /// Try to downcast to a specific layout node.
@ -157,7 +157,7 @@ impl LayoutNode {
where where
T: Layout + Debug + Hash + 'static, T: Layout + Debug + Hash + 'static,
{ {
self.0.as_any().downcast_ref() (**self.0).as_any().downcast_ref()
} }
/// Force a size for this node. /// Force a size for this node.
@ -223,7 +223,7 @@ impl Layout for LayoutNode {
styles: StyleChain, styles: StyleChain,
) -> TypResult<Vec<Arc<Frame>>> { ) -> TypResult<Vec<Arc<Frame>>> {
ctx.query((self, regions, styles), |ctx, (node, regions, styles)| { ctx.query((self, regions, styles), |ctx, (node, regions, styles)| {
node.0.layout(ctx, regions, styles.barred(node.id())) node.0.layout(ctx, regions, node.barrier().chain(&styles))
}) })
.clone() .clone()
} }
@ -253,6 +253,7 @@ impl PartialEq for LayoutNode {
trait Bounds: Layout + Debug + Sync + Send + 'static { trait Bounds: Layout + Debug + Sync + Send + 'static {
fn as_any(&self) -> &dyn Any; fn as_any(&self) -> &dyn Any;
fn barrier(&self) -> Barrier;
} }
impl<T> Bounds for T impl<T> Bounds for T
@ -262,6 +263,10 @@ where
fn as_any(&self) -> &dyn Any { fn as_any(&self) -> &dyn Any {
self self
} }
fn barrier(&self) -> Barrier {
Barrier::new::<T>()
}
} }
/// A layout node that produces an empty frame. /// A layout node that produces an empty frame.

View File

@ -9,7 +9,7 @@ use crate::util::Prehashed;
use crate::Context; use crate::Context;
/// A node that can be realized given some styles. /// A node that can be realized given some styles.
pub trait Show { pub trait Show: 'static {
/// Realize this node in the given styles. /// Realize this node in the given styles.
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content>; fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content>;

File diff suppressed because it is too large Load Diff

View File

@ -359,12 +359,12 @@ impl Dynamic {
/// Whether the wrapped type is `T`. /// Whether the wrapped type is `T`.
pub fn is<T: Type + 'static>(&self) -> bool { pub fn is<T: Type + 'static>(&self) -> bool {
self.0.as_any().is::<T>() (*self.0).as_any().is::<T>()
} }
/// Try to downcast to a reference to a specific type. /// Try to downcast to a reference to a specific type.
pub fn downcast<T: Type + 'static>(&self) -> Option<&T> { pub fn downcast<T: Type + 'static>(&self) -> Option<&T> {
self.0.as_any().downcast_ref() (*self.0).as_any().downcast_ref()
} }
/// The name of the stored value's type. /// The name of the stored value's type.

View File

@ -82,7 +82,7 @@ impl Layout for ImageNode {
} }
// Apply link if it exists. // Apply link if it exists.
if let Some(url) = styles.get_ref(TextNode::LINK) { if let Some(url) = styles.get(TextNode::LINK) {
frame.link(url); frame.link(url);
} }

View File

@ -132,7 +132,7 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
} }
// Apply link if it exists. // Apply link if it exists.
if let Some(url) = styles.get_ref(TextNode::LINK) { if let Some(url) = styles.get(TextNode::LINK) {
frame.link(url); frame.link(url);
} }

View File

@ -37,12 +37,12 @@ impl Layout for FlowNode {
let styles = map.chain(&styles); let styles = map.chain(&styles);
match child { match child {
FlowChild::Leading => { FlowChild::Leading => {
let em = styles.get(TextNode::SIZE).abs; let em = styles.get(TextNode::SIZE);
let amount = styles.get(ParNode::LEADING).resolve(em); let amount = styles.get(ParNode::LEADING).resolve(em);
layouter.layout_spacing(amount.into()); layouter.layout_spacing(amount.into());
} }
FlowChild::Parbreak => { FlowChild::Parbreak => {
let em = styles.get(TextNode::SIZE).abs; let em = styles.get(TextNode::SIZE);
let leading = styles.get(ParNode::LEADING); let leading = styles.get(ParNode::LEADING);
let spacing = styles.get(ParNode::SPACING); let spacing = styles.get(ParNode::SPACING);
let amount = (leading + spacing).resolve(em); let amount = (leading + spacing).resolve(em);

View File

@ -28,8 +28,10 @@ impl PageNode {
/// How many columns the page has. /// How many columns the page has.
pub const COLUMNS: NonZeroUsize = NonZeroUsize::new(1).unwrap(); pub const COLUMNS: NonZeroUsize = NonZeroUsize::new(1).unwrap();
/// The page's header. /// The page's header.
#[property(referenced)]
pub const HEADER: Marginal = Marginal::None; pub const HEADER: Marginal = Marginal::None;
/// The page's footer. /// The page's footer.
#[property(referenced)]
pub const FOOTER: Marginal = Marginal::None; pub const FOOTER: Marginal = Marginal::None;
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
@ -116,8 +118,8 @@ impl PageNode {
let regions = Regions::repeat(size, size, size.map(Length::is_finite)); let regions = Regions::repeat(size, size, size.map(Length::is_finite));
let mut frames = child.layout(ctx, &regions, styles)?; let mut frames = child.layout(ctx, &regions, styles)?;
let header = styles.get_ref(Self::HEADER); let header = styles.get(Self::HEADER);
let footer = styles.get_ref(Self::FOOTER); let footer = styles.get(Self::FOOTER);
// Realize header and footer. // Realize header and footer.
for frame in &mut frames { for frame in &mut frames {

View File

@ -15,6 +15,7 @@ pub struct MathNode {
#[node(showable)] #[node(showable)]
impl MathNode { impl MathNode {
/// The raw text's font family. Just the normal text family if `auto`. /// The raw text's font family. Just the normal text family if `auto`.
#[property(referenced)]
pub const FAMILY: Smart<FontFamily> = pub const FAMILY: Smart<FontFamily> =
Smart::Custom(FontFamily::new("Latin Modern Math")); Smart::Custom(FontFamily::new("Latin Modern Math"));
@ -28,16 +29,14 @@ impl MathNode {
impl Show for MathNode { impl Show for MathNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> { fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
let args = [Value::Str(self.formula.clone()), Value::Bool(self.display)];
let mut content = styles let mut content = styles
.show(self, ctx, [ .show::<Self, _>(ctx, args)?
Value::Str(self.formula.clone()),
Value::Bool(self.display),
])?
.unwrap_or_else(|| Content::Text(self.formula.trim().into())); .unwrap_or_else(|| Content::Text(self.formula.trim().into()));
let mut map = StyleMap::new(); let mut map = StyleMap::new();
if let Smart::Custom(family) = styles.get_cloned(Self::FAMILY) { if let Smart::Custom(family) = styles.get(Self::FAMILY) {
map.set_family(family, styles); map.set_family(family.clone(), styles);
} }
content = content.styled_with_map(map); content = content.styled_with_map(map);

View File

@ -9,8 +9,8 @@ pub use typst_macros::node;
pub use crate::diag::{with_alternative, At, Error, StrResult, TypError, TypResult}; pub use crate::diag::{with_alternative, At, Error, StrResult, TypError, TypResult};
pub use crate::eval::{ pub use crate::eval::{
Arg, Args, Array, Cast, Content, Dict, Func, Key, Layout, LayoutNode, Merge, Node, Arg, Args, Array, Cast, Content, Dict, Fold, Func, Key, Layout, LayoutNode, Merge,
Regions, Scope, Show, ShowNode, Smart, StyleChain, StyleMap, StyleVec, Value, Node, Regions, Scope, Show, ShowNode, Smart, StyleChain, StyleMap, StyleVec, Value,
}; };
pub use crate::frame::*; pub use crate::frame::*;
pub use crate::geom::*; pub use crate::geom::*;

View File

@ -1,5 +1,5 @@
use crate::library::prelude::*; use crate::library::prelude::*;
use crate::library::text::{FontFamily, TextNode}; use crate::library::text::{FontFamily, FontSize, TextNode, Toggle};
/// A section heading. /// A section heading.
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -14,25 +14,34 @@ pub struct HeadingNode {
#[node(showable)] #[node(showable)]
impl HeadingNode { impl HeadingNode {
/// The heading's font family. Just the normal text family if `auto`. /// The heading's font family. Just the normal text family if `auto`.
#[property(referenced)]
pub const FAMILY: Leveled<Smart<FontFamily>> = Leveled::Value(Smart::Auto); pub const FAMILY: Leveled<Smart<FontFamily>> = Leveled::Value(Smart::Auto);
/// The color of text in the heading. Just the normal text color if `auto`. /// The color of text in the heading. Just the normal text color if `auto`.
#[property(referenced)]
pub const FILL: Leveled<Smart<Paint>> = Leveled::Value(Smart::Auto); pub const FILL: Leveled<Smart<Paint>> = Leveled::Value(Smart::Auto);
/// The size of text in the heading. /// The size of text in the heading.
pub const SIZE: Leveled<Linear> = Leveled::Mapping(|level| { #[property(referenced)]
pub const SIZE: Leveled<FontSize> = Leveled::Mapping(|level| {
let upscale = (1.6 - 0.1 * level as f64).max(0.75); let upscale = (1.6 - 0.1 * level as f64).max(0.75);
Relative::new(upscale).into() FontSize(Relative::new(upscale).into())
}); });
/// Whether text in the heading is strengthend. /// Whether text in the heading is strengthend.
#[property(referenced)]
pub const STRONG: Leveled<bool> = Leveled::Value(true); pub const STRONG: Leveled<bool> = Leveled::Value(true);
/// Whether text in the heading is emphasized. /// Whether text in the heading is emphasized.
#[property(referenced)]
pub const EMPH: Leveled<bool> = Leveled::Value(false); pub const EMPH: Leveled<bool> = Leveled::Value(false);
/// Whether the heading is underlined. /// Whether the heading is underlined.
#[property(referenced)]
pub const UNDERLINE: Leveled<bool> = Leveled::Value(false); pub const UNDERLINE: Leveled<bool> = Leveled::Value(false);
/// The extra padding above the heading. /// The extra padding above the heading.
#[property(referenced)]
pub const ABOVE: Leveled<Length> = Leveled::Value(Length::zero()); pub const ABOVE: Leveled<Length> = Leveled::Value(Length::zero());
/// The extra padding below the heading. /// The extra padding below the heading.
#[property(referenced)]
pub const BELOW: Leveled<Length> = Leveled::Value(Length::zero()); pub const BELOW: Leveled<Length> = Leveled::Value(Length::zero());
/// Whether the heading is block-level. /// Whether the heading is block-level.
#[property(referenced)]
pub const BLOCK: Leveled<bool> = Leveled::Value(true); pub const BLOCK: Leveled<bool> = Leveled::Value(true);
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
@ -47,16 +56,17 @@ impl Show for HeadingNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> { fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
macro_rules! resolve { macro_rules! resolve {
($key:expr) => { ($key:expr) => {
styles.get_cloned($key).resolve(ctx, self.level)? styles.get($key).resolve(ctx, self.level)?
}; };
} }
// Resolve the user recipe. let args = [
Value::Int(self.level as i64),
Value::Content(self.body.clone()),
];
let mut body = styles let mut body = styles
.show(self, ctx, [ .show::<Self, _>(ctx, args)?
Value::Int(self.level as i64),
Value::Content(self.body.clone()),
])?
.unwrap_or_else(|| self.body.clone()); .unwrap_or_else(|| self.body.clone());
let mut map = StyleMap::new(); let mut map = StyleMap::new();
@ -71,11 +81,11 @@ impl Show for HeadingNode {
} }
if resolve!(Self::STRONG) { if resolve!(Self::STRONG) {
map.set(TextNode::STRONG, true); map.set(TextNode::STRONG, Toggle);
} }
if resolve!(Self::EMPH) { if resolve!(Self::EMPH) {
map.set(TextNode::EMPH, true); map.set(TextNode::EMPH, Toggle);
} }
let mut seq = vec![]; let mut seq = vec![];
@ -116,15 +126,15 @@ pub enum Leveled<T> {
Func(Func, Span), Func(Func, Span),
} }
impl<T: Cast> Leveled<T> { impl<T: Cast + Clone> Leveled<T> {
/// Resolve the value based on the level. /// Resolve the value based on the level.
pub fn resolve(self, ctx: &mut Context, level: usize) -> TypResult<T> { pub fn resolve(&self, ctx: &mut Context, level: usize) -> TypResult<T> {
Ok(match self { Ok(match self {
Self::Value(value) => value, Self::Value(value) => value.clone(),
Self::Mapping(mapping) => mapping(level), Self::Mapping(mapping) => mapping(level),
Self::Func(func, span) => { Self::Func(func, span) => {
let args = Args::from_values(span, [Value::Int(level as i64)]); let args = Args::from_values(*span, [Value::Int(level as i64)]);
func.call(ctx, args)?.cast().at(span)? func.call(ctx, args)?.cast().at(*span)?
} }
}) })
} }

View File

@ -31,6 +31,7 @@ pub type EnumNode = ListNode<ORDERED>;
#[node(showable)] #[node(showable)]
impl<const L: ListKind> ListNode<L> { impl<const L: ListKind> ListNode<L> {
/// How the list is labelled. /// How the list is labelled.
#[property(referenced)]
pub const LABEL: Label = Label::Default; pub const LABEL: Label = Label::Default;
/// The spacing between the list items of a non-wide list. /// The spacing between the list items of a non-wide list.
pub const SPACING: Linear = Linear::zero(); pub const SPACING: Linear = Linear::zero();
@ -58,17 +59,14 @@ impl<const L: ListKind> ListNode<L> {
impl<const L: ListKind> Show for ListNode<L> { impl<const L: ListKind> Show for ListNode<L> {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> { fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
let content = if let Some(content) = styles.show( let args = self.items.iter().map(|item| Value::Content((*item.body).clone()));
self, let content = if let Some(content) = styles.show::<Self, _>(ctx, args)? {
ctx,
self.items.iter().map(|item| Value::Content((*item.body).clone())),
)? {
content content
} else { } else {
let mut children = vec![]; let mut children = vec![];
let mut number = self.start; let mut number = self.start;
let label = styles.get_ref(Self::LABEL); let label = styles.get(Self::LABEL);
for item in &self.items { for item in &self.items {
number = item.number.unwrap_or(number); number = item.number.unwrap_or(number);
@ -79,7 +77,7 @@ impl<const L: ListKind> Show for ListNode<L> {
number += 1; number += 1;
} }
let em = styles.get(TextNode::SIZE).abs; let em = styles.get(TextNode::SIZE);
let leading = styles.get(ParNode::LEADING); let leading = styles.get(ParNode::LEADING);
let spacing = if self.wide { let spacing = if self.wide {
styles.get(ParNode::SPACING) styles.get(ParNode::SPACING)

View File

@ -55,11 +55,8 @@ impl TableNode {
impl Show for TableNode { impl Show for TableNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> { fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
if let Some(content) = styles.show( let args = self.children.iter().map(|child| Value::Content(child.clone()));
self, if let Some(content) = styles.show::<Self, _>(ctx, args)? {
ctx,
self.children.iter().map(|child| Value::Content(child.clone())),
)? {
return Ok(content); return Ok(content);
} }

View File

@ -21,11 +21,11 @@ pub type OverlineNode = DecoNode<OVERLINE>;
#[node(showable)] #[node(showable)]
impl<const L: DecoLine> DecoNode<L> { impl<const L: DecoLine> DecoNode<L> {
/// Stroke color of the line, defaults to the text color if `None`. /// Stroke color of the line, defaults to the text color if `None`.
#[shorthand] #[property(shorthand)]
pub const STROKE: Option<Paint> = None; pub const STROKE: Option<Paint> = None;
/// Thickness of the line's strokes (dependent on scaled font size), read /// Thickness of the line's strokes (dependent on scaled font size), read
/// from the font tables if `None`. /// from the font tables if `None`.
#[shorthand] #[property(shorthand)]
pub const THICKNESS: Option<Linear> = None; pub const THICKNESS: Option<Linear> = None;
/// Position of the line relative to the baseline (dependent on scaled font /// Position of the line relative to the baseline (dependent on scaled font
/// size), read from the font tables if `None`. /// size), read from the font tables if `None`.
@ -45,16 +45,16 @@ impl<const L: DecoLine> DecoNode<L> {
impl<const L: DecoLine> Show for DecoNode<L> { impl<const L: DecoLine> Show for DecoNode<L> {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> { fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
Ok(styles Ok(styles
.show(self, ctx, [Value::Content(self.0.clone())])? .show::<Self, _>(ctx, [Value::Content(self.0.clone())])?
.unwrap_or_else(|| { .unwrap_or_else(|| {
self.0.clone().styled(TextNode::LINES, vec![Decoration { self.0.clone().styled(TextNode::DECO, Decoration {
line: L, line: L,
stroke: styles.get(Self::STROKE), stroke: styles.get(Self::STROKE),
thickness: styles.get(Self::THICKNESS), thickness: styles.get(Self::THICKNESS),
offset: styles.get(Self::OFFSET), offset: styles.get(Self::OFFSET),
extent: styles.get(Self::EXTENT), extent: styles.get(Self::EXTENT),
evade: styles.get(Self::EVADE), evade: styles.get(Self::EVADE),
}]) })
})) }))
} }
} }

View File

@ -29,14 +29,13 @@ impl LinkNode {
impl Show for LinkNode { impl Show for LinkNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> { fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
let args = [Value::Str(self.url.clone()), match &self.body {
Some(body) => Value::Content(body.clone()),
None => Value::None,
}];
let mut body = styles let mut body = styles
.show(self, ctx, [ .show::<Self, _>(ctx, args)?
Value::Str(self.url.clone()),
match &self.body {
Some(body) => Value::Content(body.clone()),
None => Value::None,
},
])?
.or_else(|| self.body.clone()) .or_else(|| self.body.clone())
.unwrap_or_else(|| { .unwrap_or_else(|| {
let url = &self.url; let url = &self.url;

View File

@ -13,7 +13,6 @@ pub use raw::*;
pub use shaping::*; pub use shaping::*;
use std::borrow::Cow; use std::borrow::Cow;
use std::ops::BitXor;
use ttf_parser::Tag; use ttf_parser::Tag;
@ -28,7 +27,7 @@ pub struct TextNode;
#[node] #[node]
impl TextNode { impl TextNode {
/// A prioritized sequence of font families. /// A prioritized sequence of font families.
#[variadic] #[property(referenced, variadic)]
pub const FAMILY: Vec<FontFamily> = vec![FontFamily::new("IBM Plex Sans")]; pub const FAMILY: Vec<FontFamily> = vec![FontFamily::new("IBM Plex Sans")];
/// Whether to allow font fallback when the primary font list contains no /// Whether to allow font fallback when the primary font list contains no
/// match. /// match.
@ -40,14 +39,13 @@ impl TextNode {
pub const WEIGHT: FontWeight = FontWeight::REGULAR; pub const WEIGHT: FontWeight = FontWeight::REGULAR;
/// The width of the glyphs. /// The width of the glyphs.
pub const STRETCH: FontStretch = FontStretch::NORMAL; pub const STRETCH: FontStretch = FontStretch::NORMAL;
/// The size of the glyphs.
#[property(shorthand, fold)]
pub const SIZE: FontSize = Length::pt(11.0);
/// The glyph fill color. /// The glyph fill color.
#[shorthand] #[property(shorthand)]
pub const FILL: Paint = Color::BLACK.into(); pub const FILL: Paint = Color::BLACK.into();
/// The size of the glyphs.
#[shorthand]
#[fold(Linear::compose)]
pub const SIZE: Linear = Length::pt(11.0).into();
/// The amount of space that should be added between characters. /// The amount of space that should be added between characters.
pub const TRACKING: Em = Em::zero(); pub const TRACKING: Em = Em::zero();
/// The ratio by which spaces should be stretched. /// The ratio by which spaces should be stretched.
@ -84,26 +82,24 @@ impl TextNode {
/// Whether to convert fractions. ("frac") /// Whether to convert fractions. ("frac")
pub const FRACTIONS: bool = false; pub const FRACTIONS: bool = false;
/// Raw OpenType features to apply. /// Raw OpenType features to apply.
#[property(fold)]
pub const FEATURES: Vec<(Tag, u32)> = vec![]; pub const FEATURES: Vec<(Tag, u32)> = vec![];
/// Whether the font weight should be increased by 300. /// Whether the font weight should be increased by 300.
#[skip] #[property(hidden, fold)]
#[fold(bool::bitxor)] pub const STRONG: Toggle = false;
pub const STRONG: bool = false;
/// Whether the the font style should be inverted. /// Whether the the font style should be inverted.
#[skip] #[property(hidden, fold)]
#[fold(bool::bitxor)] pub const EMPH: Toggle = false;
pub const EMPH: bool = false; /// A case transformation that should be applied to the text.
/// The case transformation that should be applied to the next. #[property(hidden)]
#[skip]
pub const CASE: Option<Case> = None; pub const CASE: Option<Case> = None;
/// Decorative lines.
#[skip]
#[fold(|a, b| a.into_iter().chain(b).collect())]
pub const LINES: Vec<Decoration> = vec![];
/// An URL the text should link to. /// An URL the text should link to.
#[skip] #[property(hidden, referenced)]
pub const LINK: Option<EcoString> = None; pub const LINK: Option<EcoString> = None;
/// Decorative lines.
#[property(hidden, fold)]
pub const DECO: Decoration = vec![];
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
// The text constructor is special: It doesn't create a text node. // The text constructor is special: It doesn't create a text node.
@ -113,44 +109,6 @@ impl TextNode {
} }
} }
/// Strong text, rendered in boldface.
#[derive(Debug, Hash)]
pub struct StrongNode(pub Content);
#[node(showable)]
impl StrongNode {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
Ok(Content::show(Self(args.expect("body")?)))
}
}
impl Show for StrongNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
Ok(styles
.show(self, ctx, [Value::Content(self.0.clone())])?
.unwrap_or_else(|| self.0.clone().styled(TextNode::STRONG, true)))
}
}
/// Emphasized text, rendered with an italic face.
#[derive(Debug, Hash)]
pub struct EmphNode(pub Content);
#[node(showable)]
impl EmphNode {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
Ok(Content::show(Self(args.expect("body")?)))
}
}
impl Show for EmphNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
Ok(styles
.show(self, ctx, [Value::Content(self.0.clone())])?
.unwrap_or_else(|| self.0.clone().styled(TextNode::EMPH, true)))
}
}
/// A font family like "Arial". /// A font family like "Arial".
#[derive(Clone, Eq, PartialEq, Hash)] #[derive(Clone, Eq, PartialEq, Hash)]
pub struct FontFamily(EcoString); pub struct FontFamily(EcoString);
@ -228,6 +186,26 @@ castable! {
Value::Relative(v) => Self::from_ratio(v.get() as f32), Value::Relative(v) => Self::from_ratio(v.get() as f32),
} }
/// The size of text.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct FontSize(pub Linear);
impl Fold for FontSize {
type Output = Length;
fn fold(self, outer: Self::Output) -> Self::Output {
self.0.rel.resolve(outer) + self.0.abs
}
}
castable! {
FontSize,
Expected: "linear",
Value::Length(v) => Self(v.into()),
Value::Relative(v) => Self(v.into()),
Value::Linear(v) => Self(v),
}
castable! { castable! {
Em, Em,
Expected: "float", Expected: "float",
@ -353,6 +331,15 @@ castable! {
.collect(), .collect(),
} }
impl Fold for Vec<(Tag, u32)> {
type Output = Self;
fn fold(mut self, outer: Self::Output) -> Self::Output {
self.extend(outer);
self
}
}
/// A case transformation on text. /// A case transformation on text.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Case { pub enum Case {
@ -371,3 +358,62 @@ impl Case {
} }
} }
} }
/// A toggle that turns on and off alternatingly if folded.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Toggle;
impl Fold for Toggle {
type Output = bool;
fn fold(self, outer: Self::Output) -> Self::Output {
!outer
}
}
impl Fold for Decoration {
type Output = Vec<Self>;
fn fold(self, mut outer: Self::Output) -> Self::Output {
outer.insert(0, self);
outer
}
}
/// Strong text, rendered in boldface.
#[derive(Debug, Hash)]
pub struct StrongNode(pub Content);
#[node(showable)]
impl StrongNode {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
Ok(Content::show(Self(args.expect("body")?)))
}
}
impl Show for StrongNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
Ok(styles
.show::<Self, _>(ctx, [Value::Content(self.0.clone())])?
.unwrap_or_else(|| self.0.clone().styled(TextNode::STRONG, Toggle)))
}
}
/// Emphasized text, rendered with an italic face.
#[derive(Debug, Hash)]
pub struct EmphNode(pub Content);
#[node(showable)]
impl EmphNode {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
Ok(Content::show(Self(args.expect("body")?)))
}
}
impl Show for EmphNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
Ok(styles
.show::<Self, _>(ctx, [Value::Content(self.0.clone())])?
.unwrap_or_else(|| self.0.clone().styled(TextNode::EMPH, Toggle)))
}
}

View File

@ -28,6 +28,7 @@ pub enum ParChild {
#[node] #[node]
impl ParNode { impl ParNode {
/// An ISO 639-1 language code. /// An ISO 639-1 language code.
#[property(referenced)]
pub const LANG: Option<EcoString> = None; pub const LANG: Option<EcoString> = None;
/// The direction for text and inline objects. /// The direction for text and inline objects.
pub const DIR: Dir = Dir::LTR; pub const DIR: Dir = Dir::LTR;
@ -611,7 +612,7 @@ fn breakpoints<'a>(
let mut lang = None; let mut lang = None;
if styles.get(ParNode::HYPHENATE).unwrap_or(styles.get(ParNode::JUSTIFY)) { if styles.get(ParNode::HYPHENATE).unwrap_or(styles.get(ParNode::JUSTIFY)) {
lang = styles lang = styles
.get_ref(ParNode::LANG) .get(ParNode::LANG)
.as_ref() .as_ref()
.and_then(|iso| iso.as_bytes().try_into().ok()) .and_then(|iso| iso.as_bytes().try_into().ok())
.and_then(hypher::Lang::from_iso); .and_then(hypher::Lang::from_iso);
@ -768,7 +769,7 @@ fn stack(
regions: &Regions, regions: &Regions,
styles: StyleChain, styles: StyleChain,
) -> Vec<Arc<Frame>> { ) -> Vec<Arc<Frame>> {
let em = styles.get(TextNode::SIZE).abs; let em = styles.get(TextNode::SIZE);
let leading = styles.get(ParNode::LEADING).resolve(em); let leading = styles.get(ParNode::LEADING).resolve(em);
let align = styles.get(ParNode::ALIGN); let align = styles.get(ParNode::ALIGN);
let justify = styles.get(ParNode::JUSTIFY); let justify = styles.get(ParNode::JUSTIFY);
@ -832,7 +833,7 @@ fn commit(
if let Some(glyph) = text.glyphs.first() { if let Some(glyph) = text.glyphs.first() {
if text.styles.get(TextNode::OVERHANG) { if text.styles.get(TextNode::OVERHANG) {
let start = text.dir.is_positive(); let start = text.dir.is_positive();
let em = text.styles.get(TextNode::SIZE).abs; let em = text.styles.get(TextNode::SIZE);
let amount = overhang(glyph.c, start) * glyph.x_advance.resolve(em); let amount = overhang(glyph.c, start) * glyph.x_advance.resolve(em);
offset -= amount; offset -= amount;
remaining += amount; remaining += amount;
@ -847,7 +848,7 @@ fn commit(
&& (reordered.len() > 1 || text.glyphs.len() > 1) && (reordered.len() > 1 || text.glyphs.len() > 1)
{ {
let start = !text.dir.is_positive(); let start = !text.dir.is_positive();
let em = text.styles.get(TextNode::SIZE).abs; let em = text.styles.get(TextNode::SIZE);
let amount = overhang(glyph.c, start) * glyph.x_advance.resolve(em); let amount = overhang(glyph.c, start) * glyph.x_advance.resolve(em);
remaining += amount; remaining += amount;
} }

View File

@ -3,7 +3,7 @@ use syntect::easy::HighlightLines;
use syntect::highlighting::{FontStyle, Highlighter, Style, Theme, ThemeSet}; use syntect::highlighting::{FontStyle, Highlighter, Style, Theme, ThemeSet};
use syntect::parsing::SyntaxSet; use syntect::parsing::SyntaxSet;
use super::{FontFamily, TextNode}; use super::{FontFamily, TextNode, Toggle};
use crate::library::prelude::*; use crate::library::prelude::*;
use crate::source::SourceId; use crate::source::SourceId;
use crate::syntax::{self, RedNode}; use crate::syntax::{self, RedNode};
@ -27,8 +27,10 @@ pub struct RawNode {
#[node(showable)] #[node(showable)]
impl RawNode { impl RawNode {
/// The raw text's font family. Just the normal text family if `none`. /// The raw text's font family. Just the normal text family if `none`.
#[property(referenced)]
pub const FAMILY: Smart<FontFamily> = Smart::Custom(FontFamily::new("IBM Plex Mono")); pub const FAMILY: Smart<FontFamily> = Smart::Custom(FontFamily::new("IBM Plex Mono"));
/// The language to syntax-highlight in. /// The language to syntax-highlight in.
#[property(referenced)]
pub const LANG: Option<EcoString> = None; pub const LANG: Option<EcoString> = None;
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
@ -41,7 +43,7 @@ impl RawNode {
impl Show for RawNode { impl Show for RawNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> { fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
let lang = styles.get_ref(Self::LANG).as_ref(); let lang = styles.get(Self::LANG).as_ref();
let foreground = THEME let foreground = THEME
.settings .settings
.foreground .foreground
@ -49,14 +51,16 @@ impl Show for RawNode {
.unwrap_or(Color::BLACK) .unwrap_or(Color::BLACK)
.into(); .into();
let mut content = if let Some(content) = styles.show(self, ctx, [ let args = [
Value::Str(self.text.clone()), Value::Str(self.text.clone()),
match lang { match lang {
Some(lang) => Value::Str(lang.clone()), Some(lang) => Value::Str(lang.clone()),
None => Value::None, None => Value::None,
}, },
Value::Bool(self.block), Value::Bool(self.block),
])? { ];
let mut content = if let Some(content) = styles.show::<Self, _>(ctx, args)? {
content content
} else if matches!( } else if matches!(
lang.map(|s| s.to_lowercase()).as_deref(), lang.map(|s| s.to_lowercase()).as_deref(),
@ -93,8 +97,8 @@ impl Show for RawNode {
}; };
let mut map = StyleMap::new(); let mut map = StyleMap::new();
if let Smart::Custom(family) = styles.get_cloned(Self::FAMILY) { if let Smart::Custom(family) = styles.get(Self::FAMILY) {
map.set_family(family, styles); map.set_family(family.clone(), styles);
} }
content = content.styled_with_map(map); content = content.styled_with_map(map);
@ -118,11 +122,11 @@ fn styled(piece: &str, foreground: Paint, style: Style) -> Content {
} }
if style.font_style.contains(FontStyle::BOLD) { if style.font_style.contains(FontStyle::BOLD) {
styles.set(TextNode::STRONG, true); styles.set(TextNode::STRONG, Toggle);
} }
if style.font_style.contains(FontStyle::ITALIC) { if style.font_style.contains(FontStyle::ITALIC) {
styles.set(TextNode::EMPH, true); styles.set(TextNode::EMPH, Toggle);
} }
if style.font_style.contains(FontStyle::UNDERLINE) { if style.font_style.contains(FontStyle::UNDERLINE) {

View File

@ -77,7 +77,7 @@ impl<'a> ShapedText<'a> {
for (face_id, group) in self.glyphs.as_ref().group_by_key(|g| g.face_id) { for (face_id, group) in self.glyphs.as_ref().group_by_key(|g| g.face_id) {
let pos = Point::new(offset, self.baseline); let pos = Point::new(offset, self.baseline);
let size = self.styles.get(TextNode::SIZE).abs; let size = self.styles.get(TextNode::SIZE);
let fill = self.styles.get(TextNode::FILL); let fill = self.styles.get(TextNode::FILL);
let glyphs = group let glyphs = group
.iter() .iter()
@ -99,7 +99,7 @@ impl<'a> ShapedText<'a> {
let width = text.width(); let width = text.width();
// Apply line decorations. // Apply line decorations.
for deco in self.styles.get_cloned(TextNode::LINES) { for deco in self.styles.get(TextNode::DECO) {
decorate(&mut frame, &deco, fonts, &text, pos, width); decorate(&mut frame, &deco, fonts, &text, pos, width);
} }
@ -108,7 +108,7 @@ impl<'a> ShapedText<'a> {
} }
// Apply link if it exists. // Apply link if it exists.
if let Some(url) = self.styles.get_ref(TextNode::LINK) { if let Some(url) = self.styles.get(TextNode::LINK) {
frame.link(url); frame.link(url);
} }
@ -127,7 +127,7 @@ impl<'a> ShapedText<'a> {
.filter(|g| g.is_space()) .filter(|g| g.is_space())
.map(|g| g.x_advance) .map(|g| g.x_advance)
.sum::<Em>() .sum::<Em>()
.resolve(self.styles.get(TextNode::SIZE).abs) .resolve(self.styles.get(TextNode::SIZE))
} }
/// Reshape a range of the shaped text, reusing information from this /// Reshape a range of the shaped text, reusing information from this
@ -154,7 +154,7 @@ impl<'a> ShapedText<'a> {
/// Push a hyphen to end of the text. /// Push a hyphen to end of the text.
pub fn push_hyphen(&mut self, fonts: &mut FontStore) { pub fn push_hyphen(&mut self, fonts: &mut FontStore) {
let size = self.styles.get(TextNode::SIZE).abs; let size = self.styles.get(TextNode::SIZE);
let variant = variant(self.styles); let variant = variant(self.styles);
families(self.styles).find_map(|family| { families(self.styles).find_map(|family| {
let face_id = fonts.select(family, variant)?; let face_id = fonts.select(family, variant)?;
@ -467,7 +467,7 @@ fn measure(
let mut top = Length::zero(); let mut top = Length::zero();
let mut bottom = Length::zero(); let mut bottom = Length::zero();
let size = styles.get(TextNode::SIZE).abs; let size = styles.get(TextNode::SIZE);
let top_edge = styles.get(TextNode::TOP_EDGE); let top_edge = styles.get(TextNode::TOP_EDGE);
let bottom_edge = styles.get(TextNode::BOTTOM_EDGE); let bottom_edge = styles.get(TextNode::BOTTOM_EDGE);
@ -537,7 +537,7 @@ fn families(styles: StyleChain) -> impl Iterator<Item = &str> + Clone {
let tail = if styles.get(TextNode::FALLBACK) { FALLBACKS } else { &[] }; let tail = if styles.get(TextNode::FALLBACK) { FALLBACKS } else { &[] };
styles styles
.get_ref(TextNode::FAMILY) .get(TextNode::FAMILY)
.iter() .iter()
.map(|family| family.as_str()) .map(|family| family.as_str())
.chain(tail.iter().copied()) .chain(tail.iter().copied())
@ -609,7 +609,7 @@ fn tags(styles: StyleChain) -> Vec<Feature> {
feat(b"frac", 1); feat(b"frac", 1);
} }
for &(tag, value) in styles.get_ref(TextNode::FEATURES).iter() { for (tag, value) in styles.get(TextNode::FEATURES) {
tags.push(Feature::new(tag, value, ..)) tags.push(Feature::new(tag, value, ..))
} }

View File

@ -13,7 +13,7 @@ use typst::eval::{Smart, StyleMap, Value};
use typst::frame::{Element, Frame}; use typst::frame::{Element, Frame};
use typst::geom::{Length, RgbaColor}; use typst::geom::{Length, RgbaColor};
use typst::library::layout::PageNode; use typst::library::layout::PageNode;
use typst::library::text::TextNode; use typst::library::text::{FontSize, TextNode};
use typst::loading::FsLoader; use typst::loading::FsLoader;
use typst::parse::Scanner; use typst::parse::Scanner;
use typst::source::SourceFile; use typst::source::SourceFile;
@ -67,7 +67,7 @@ fn main() {
styles.set(PageNode::TOP, Smart::Custom(Length::pt(10.0).into())); styles.set(PageNode::TOP, Smart::Custom(Length::pt(10.0).into()));
styles.set(PageNode::RIGHT, Smart::Custom(Length::pt(10.0).into())); styles.set(PageNode::RIGHT, Smart::Custom(Length::pt(10.0).into()));
styles.set(PageNode::BOTTOM, Smart::Custom(Length::pt(10.0).into())); styles.set(PageNode::BOTTOM, Smart::Custom(Length::pt(10.0).into()));
styles.set(TextNode::SIZE, Length::pt(10.0).into()); styles.set(TextNode::SIZE, FontSize(Length::pt(10.0).into()));
// Hook up an assert function into the global scope. // Hook up an assert function into the global scope.
let mut std = typst::library::new(); let mut std = typst::library::new();