diff --git a/crates/typst-macros/src/elem.rs b/crates/typst-macros/src/elem.rs index 722131b72..91147b8de 100644 --- a/crates/typst-macros/src/elem.rs +++ b/crates/typst-macros/src/elem.rs @@ -106,7 +106,9 @@ impl Elem { /// This includes: /// - Fields that are not synthesized. fn construct_fields(&self) -> impl Iterator + Clone { - self.real_fields().filter(|field| !field.synthesized) + self.real_fields().filter(|field| { + !field.synthesized && (!field.internal || field.parse.is_some()) + }) } } @@ -263,11 +265,6 @@ fn parse_field(field: &syn::Field) -> Result { field.output = parse_quote! { <#output as #foundations::Resolve>::Output }; } - if field.fold { - let output = &field.output; - field.output = parse_quote! { <#output as #foundations::Fold>::Output }; - } - validate_attrs(&attrs)?; Ok(field) @@ -555,32 +552,42 @@ fn create_style_chain_access( let elem = &element.ident; let Field { ty, default, enum_ident, .. } = field; - let getter = match (field.fold, field.resolve, field.borrowed) { - (false, false, false) => quote! { get }, - (false, false, true) => quote! { get_borrowed }, - (false, true, _) => quote! { get_resolve }, - (true, false, _) => quote! { get_fold }, - (true, true, _) => quote! { get_resolve_fold }, + + let getter = match (field.fold, field.borrowed) { + (false, false) => quote! { get }, + (false, true) => quote! { get_ref }, + (true, _) => quote! { get_folded }, }; - let default = default - .clone() - .unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }); - let (init, default) = field.fold.then(|| (None, quote! { || #default })).unwrap_or_else(|| ( - Some(quote! { - static DEFAULT: ::once_cell::sync::Lazy<#ty> = ::once_cell::sync::Lazy::new(|| #default); - }), - quote! { &DEFAULT }, - )); + let mut default = match default { + Some(default) => quote! { #default }, + None => quote! { ::std::default::Default::default() }, + }; - quote! { - #init + let mut init = None; + if field.borrowed { + init = Some(quote! { + static DEFAULT: ::once_cell::sync::Lazy<#ty> = ::once_cell::sync::Lazy::new(|| #default); + }); + default = quote! { &DEFAULT }; + } + + let mut value = quote! { styles.#getter::<#ty>( ::elem(), <#elem as #foundations::Fields>::Enum::#enum_ident as u8, #inherent, - #default, + || #default, ) + }; + + if field.resolve { + value = quote! { #foundations::Resolve::resolve(#value, styles) }; + } + + quote! { + #init + #value } } @@ -995,21 +1002,18 @@ fn create_param_info(field: &Field) -> TokenStream { variadic, required, default, - fold, ty, - output, .. } = field; let named = !positional; let settable = field.settable(); - let default_ty = if *fold { &output } else { &ty }; let default = quote_option(&settable.then(|| { let default = default .clone() .unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }); quote! { || { - let typed: #default_ty = #default; + let typed: #ty = #default; #foundations::IntoValue::into_value(typed) } } diff --git a/crates/typst/src/foundations/auto.rs b/crates/typst/src/foundations/auto.rs index fcd72999b..05f776d17 100644 --- a/crates/typst/src/foundations/auto.rs +++ b/crates/typst/src/foundations/auto.rs @@ -239,14 +239,14 @@ impl Resolve for Smart { } } -impl Fold for Smart -where - T: Fold, - T::Output: Default, -{ - type Output = Smart; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.map(|inner| inner.fold(outer.unwrap_or_default())) +impl Fold for Smart { + fn fold(self, outer: Self) -> Self { + use Smart::Custom; + match (self, outer) { + (Custom(inner), Custom(outer)) => Custom(inner.fold(outer)), + // An explicit `auto` should be respected, thus we don't do + // `inner.or(outer)`. + (inner, _) => inner, + } } } diff --git a/crates/typst/src/foundations/dict.rs b/crates/typst/src/foundations/dict.rs index 291b8f60a..da82889db 100644 --- a/crates/typst/src/foundations/dict.rs +++ b/crates/typst/src/foundations/dict.rs @@ -138,7 +138,6 @@ impl Dict { }; let mut msg = String::from(match unexpected.len() { - 0 => unreachable!(), 1 => "unexpected key ", _ => "unexpected keys ", }); diff --git a/crates/typst/src/foundations/styles.rs b/crates/typst/src/foundations/styles.rs index f44a320f3..83279a7b1 100644 --- a/crates/typst/src/foundations/styles.rs +++ b/crates/typst/src/foundations/styles.rs @@ -6,7 +6,6 @@ use std::{iter, mem, ptr}; use comemo::Prehashed; use ecow::{eco_vec, EcoString, EcoVec}; -use once_cell::sync::Lazy; use smallvec::SmallVec; use crate::diag::{SourceResult, Trace, Tracepoint}; @@ -457,54 +456,47 @@ impl<'a> StyleChain<'a> { Chainable::chain(local, self) } + /// Cast the first value for the given property in the chain. + pub fn get( + self, + func: Element, + id: u8, + inherent: Option<&T>, + default: impl Fn() -> T, + ) -> T { + self.properties::(func, id, inherent) + .next() + .cloned() + .unwrap_or_else(default) + } + /// Cast the first value for the given property in the chain, - /// returning a borrowed value if possible. - pub fn get_borrowed( + /// returning a borrowed value. + pub fn get_ref( self, func: Element, id: u8, inherent: Option<&'a T>, - default: &'static Lazy, + default: impl Fn() -> &'a T, ) -> &'a T { self.properties::(func, id, inherent) .next() - .unwrap_or_else(|| default) + .unwrap_or_else(default) } - /// Cast the first value for the given property in the chain. - pub fn get( + /// Cast the first value for the given property in the chain, taking + /// `Fold` implementations into account. + pub fn get_folded( self, func: Element, id: u8, inherent: Option<&T>, - default: &'static Lazy, + default: impl Fn() -> T, ) -> T { - self.get_borrowed(func, id, inherent, default).clone() - } - - /// Cast the first value for the given property in the chain. - pub fn get_resolve( - self, - func: Element, - id: u8, - inherent: Option<&T>, - default: &'static Lazy, - ) -> T::Output { - self.get(func, id, inherent, default).resolve(self) - } - - /// Cast the first value for the given property in the chain. - pub fn get_fold( - self, - func: Element, - id: u8, - inherent: Option<&T>, - default: impl Fn() -> T::Output, - ) -> T::Output { fn next( mut values: impl Iterator, - default: &impl Fn() -> T::Output, - ) -> T::Output { + default: &impl Fn() -> T, + ) -> T { values .next() .map(|value| value.fold(next(values, default))) @@ -513,36 +505,6 @@ impl<'a> StyleChain<'a> { next(self.properties::(func, id, inherent).cloned(), &default) } - /// Cast the first value for the given property in the chain. - pub fn get_resolve_fold( - self, - func: Element, - id: u8, - inherent: Option<&T>, - default: impl Fn() -> ::Output, - ) -> ::Output - where - T: Resolve + Clone + 'static, - T::Output: Fold, - { - fn next( - mut values: impl Iterator, - styles: StyleChain, - default: &impl Fn() -> ::Output, - ) -> ::Output - where - T: Resolve + 'static, - T::Output: Fold, - { - values - .next() - .map(|value| value.resolve(styles).fold(next(values, styles, default))) - .unwrap_or_else(default) - } - - next(self.properties::(func, id, inherent).cloned(), self, &default) - } - /// Iterate over all style recipes in the chain. pub fn recipes(self) -> impl Iterator { self.entries().filter_map(Style::recipe) @@ -925,38 +887,36 @@ impl Resolve for Option { /// #rect() /// ``` pub trait Fold { - /// The type of the folded output. - type Output; - /// Fold this inner value with an outer folded value. - fn fold(self, outer: Self::Output) -> Self::Output; + fn fold(self, outer: Self) -> Self; } -impl Fold for Option -where - T: Fold, - T::Output: Default, -{ - type Output = Option; +impl Fold for bool { + fn fold(self, _: Self) -> Self { + self + } +} - fn fold(self, outer: Self::Output) -> Self::Output { - self.map(|inner| inner.fold(outer.unwrap_or_default())) +impl Fold for Option { + fn fold(self, outer: Self) -> Self { + match (self, outer) { + (Some(inner), Some(outer)) => Some(inner.fold(outer)), + // An explicit `None` should be respected, thus we don't do + // `inner.or(outer)`. + (inner, _) => inner, + } } } impl Fold for Vec { - type Output = Vec; - - fn fold(mut self, outer: Self::Output) -> Self::Output { + fn fold(mut self, outer: Self) -> Self { self.extend(outer); self } } impl Fold for SmallVec<[T; N]> { - type Output = SmallVec<[T; N]>; - - fn fold(mut self, outer: Self::Output) -> Self::Output { + fn fold(mut self, outer: Self) -> Self { self.extend(outer); self } diff --git a/crates/typst/src/layout/align.rs b/crates/typst/src/layout/align.rs index e6b56f187..934bfa194 100644 --- a/crates/typst/src/layout/align.rs +++ b/crates/typst/src/layout/align.rs @@ -215,9 +215,7 @@ impl Repr for Alignment { } impl Fold for Alignment { - type Output = Self; - - fn fold(self, outer: Self::Output) -> Self::Output { + fn fold(self, outer: Self) -> Self { match (self, outer) { (Self::H(x), Self::V(y) | Self::Both(_, y)) => Self::Both(x, y), (Self::V(y), Self::H(x) | Self::Both(x, _)) => Self::Both(x, y), diff --git a/crates/typst/src/layout/axes.rs b/crates/typst/src/layout/axes.rs index e5c47edd6..82cb4aee5 100644 --- a/crates/typst/src/layout/axes.rs +++ b/crates/typst/src/layout/axes.rs @@ -3,7 +3,7 @@ use std::fmt::{self, Debug, Formatter}; use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Deref, Not}; use crate::diag::bail; -use crate::foundations::{array, cast, Array, Fold, Resolve, Smart, StyleChain}; +use crate::foundations::{array, cast, Array, Resolve, Smart, StyleChain}; use crate::layout::{Abs, Dir, Length, Ratio, Rel}; use crate::util::Get; @@ -84,14 +84,6 @@ impl Axes { { f(&self.x) && f(&self.y) } - - /// Filter the individual fields with a mask. - pub fn filter(self, mask: Axes) -> Axes> { - Axes { - x: if mask.x { Some(self.x) } else { None }, - y: if mask.y { Some(self.y) } else { None }, - } - } } impl Axes { @@ -198,16 +190,6 @@ cast! { "vertical" => Self::Y, } -impl Axes> { - /// Unwrap the individual fields. - pub fn unwrap_or(self, other: Axes) -> Axes { - Axes { - x: self.x.unwrap_or(other.x), - y: self.y.unwrap_or(other.y), - } - } -} - impl Axes> { /// Unwrap the individual fields. pub fn unwrap_or(self, other: Axes) -> Axes { @@ -325,14 +307,3 @@ impl Resolve for Axes { self.map(|v| v.resolve(styles)) } } - -impl Fold for Axes> { - type Output = Axes; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.zip_map(outer, |inner, outer| match inner { - Some(value) => value.fold(outer), - None => outer, - }) - } -} diff --git a/crates/typst/src/layout/container.rs b/crates/typst/src/layout/container.rs index 7a673a4af..c4de09fe5 100644 --- a/crates/typst/src/layout/container.rs +++ b/crates/typst/src/layout/container.rs @@ -133,7 +133,7 @@ impl Packed { // Apply inset. let mut body = self.body(styles).unwrap_or_default(); - let inset = self.inset(styles); + let inset = self.inset(styles).unwrap_or_default(); if inset.iter().any(|v| !v.is_zero()) { body = body.padded(inset.map(|side| side.map(Length::from))); } @@ -154,20 +154,24 @@ impl Packed { // Prepare fill and stroke. let fill = self.fill(styles); - let stroke = self.stroke(styles).map(|s| s.map(Stroke::unwrap_or_default)); + let stroke = self + .stroke(styles) + .unwrap_or_default() + .map(|s| s.map(Stroke::unwrap_or_default)); // Clip the contents if self.clip(styles) { - let outset = self.outset(styles).relative_to(frame.size()); + let outset = + self.outset(styles).unwrap_or_default().relative_to(frame.size()); let size = frame.size() + outset.sum_by_axis(); - let radius = self.radius(styles); + let radius = self.radius(styles).unwrap_or_default(); frame.clip(clip_rect(size, radius, &stroke)); } // Add fill and/or stroke. if fill.is_some() || stroke.iter().any(Option::is_some) { - let outset = self.outset(styles); - let radius = self.radius(styles); + let outset = self.outset(styles).unwrap_or_default(); + let radius = self.radius(styles).unwrap_or_default(); frame.fill_and_stroke(fill, stroke, outset, radius, self.span()); } @@ -350,7 +354,7 @@ impl LayoutMultiple for Packed { ) -> SourceResult { // Apply inset. let mut body = self.body(styles).unwrap_or_default(); - let inset = self.inset(styles); + let inset = self.inset(styles).unwrap_or_default(); if inset.iter().any(|v| !v.is_zero()) { body = body.clone().padded(inset.map(|side| side.map(Length::from))); } @@ -418,14 +422,18 @@ impl LayoutMultiple for Packed { // Prepare fill and stroke. let fill = self.fill(styles); - let stroke = self.stroke(styles).map(|s| s.map(Stroke::unwrap_or_default)); + let stroke = self + .stroke(styles) + .unwrap_or_default() + .map(|s| s.map(Stroke::unwrap_or_default)); // Clip the contents if self.clip(styles) { for frame in frames.iter_mut() { - let outset = self.outset(styles).relative_to(frame.size()); + let outset = + self.outset(styles).unwrap_or_default().relative_to(frame.size()); let size = frame.size() + outset.sum_by_axis(); - let radius = self.radius(styles); + let radius = self.radius(styles).unwrap_or_default(); frame.clip(clip_rect(size, radius, &stroke)); } } @@ -437,8 +445,8 @@ impl LayoutMultiple for Packed { skip = first.is_empty() && rest.iter().any(|frame| !frame.is_empty()); } - let outset = self.outset(styles); - let radius = self.radius(styles); + let outset = self.outset(styles).unwrap_or_default(); + let radius = self.radius(styles).unwrap_or_default(); for frame in frames.iter_mut().skip(skip as usize) { frame.fill_and_stroke( fill.clone(), diff --git a/crates/typst/src/layout/corners.rs b/crates/typst/src/layout/corners.rs index e674b04d6..85fd05032 100644 --- a/crates/typst/src/layout/corners.rs +++ b/crates/typst/src/layout/corners.rs @@ -80,6 +80,16 @@ impl Corners { } } +impl Corners> { + /// Unwrap-or-default the individual corners. + pub fn unwrap_or_default(self) -> Corners + where + T: Default, + { + self.map(Option::unwrap_or_default) + } +} + impl Get for Corners { type Component = T; @@ -133,20 +143,21 @@ impl Reflect for Corners> { } } -impl IntoValue for Corners +impl IntoValue for Corners> where T: PartialEq + IntoValue, { fn into_value(self) -> Value { if self.is_uniform() { - return self.top_left.into_value(); + if let Some(top_left) = self.top_left { + return top_left.into_value(); + } } let mut dict = Dict::new(); - let mut handle = |key: &str, component: T| { - let value = component.into_value(); - if value != Value::None { - dict.insert(key.into(), value); + let mut handle = |key: &str, component: Option| { + if let Some(c) = component { + dict.insert(key.into(), c.into_value()); } }; @@ -228,12 +239,13 @@ impl Resolve for Corners { } impl Fold for Corners> { - type Output = Corners; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.zip(outer).map(|(inner, outer)| match inner { - Some(value) => value.fold(outer), - None => outer, + fn fold(self, outer: Self) -> Self { + self.zip(outer).map(|(inner, outer)| match (inner, outer) { + (Some(inner), Some(outer)) => Some(inner.fold(outer)), + // Usually, folding an inner `None` with an `outer` preferres the + // explicit `None`. However, here `None` means unspecified and thus + // we want `outer`. + (inner, outer) => inner.or(outer), }) } } diff --git a/crates/typst/src/layout/grid/layout.rs b/crates/typst/src/layout/grid/layout.rs index f9957fcb1..c17bbda53 100644 --- a/crates/typst/src/layout/grid/layout.rs +++ b/crates/typst/src/layout/grid/layout.rs @@ -151,7 +151,7 @@ pub trait ResolvableCell { y: usize, fill: &Option, align: Smart, - inset: Sides>, + inset: Sides>>, styles: StyleChain, ) -> Cell; @@ -204,7 +204,7 @@ impl CellGrid { cells: &[T], fill: &Celled>, align: &Celled>, - inset: Sides>, + inset: Sides>>, engine: &mut Engine, styles: StyleChain, span: Span, diff --git a/crates/typst/src/layout/grid/mod.rs b/crates/typst/src/layout/grid/mod.rs index 544d43a8f..ee2eeecfd 100644 --- a/crates/typst/src/layout/grid/mod.rs +++ b/crates/typst/src/layout/grid/mod.rs @@ -13,8 +13,8 @@ use crate::foundations::{ cast, elem, scope, Array, Content, Fold, Packed, Show, Smart, StyleChain, Value, }; use crate::layout::{ - Abs, AlignElem, Alignment, Axes, Fragment, LayoutMultiple, Length, Regions, Rel, - Sides, Sizing, + AlignElem, Alignment, Axes, Fragment, LayoutMultiple, Length, Regions, Rel, Sides, + Sizing, }; use crate::syntax::Span; use crate::util::NonZeroExt; @@ -264,7 +264,6 @@ pub struct GridElem { /// ) /// ``` #[fold] - #[default(Sides::splat(Abs::pt(0.0).into()))] pub inset: Sides>>, /// The contents of the grid cells. @@ -462,7 +461,7 @@ impl ResolvableCell for Packed { y: usize, fill: &Option, align: Smart, - inset: Sides>, + inset: Sides>>, styles: StyleChain, ) -> Cell { let cell = &mut *self; @@ -481,9 +480,8 @@ impl ResolvableCell for Packed { Smart::Auto => cell.align(styles), }); cell.push_inset(Smart::Custom( - cell.inset(styles).map_or(inset, |inner| inner.fold(inset)).map(Some), + cell.inset(styles).map_or(inset, |inner| inner.fold(inset)), )); - Cell { body: self.pack(), fill, colspan } } diff --git a/crates/typst/src/layout/length.rs b/crates/typst/src/layout/length.rs index 94e85248a..7365ca195 100644 --- a/crates/typst/src/layout/length.rs +++ b/crates/typst/src/layout/length.rs @@ -5,7 +5,7 @@ use std::ops::{Add, Div, Mul, Neg}; use ecow::{eco_format, EcoString}; use crate::diag::{At, Hint, SourceResult}; -use crate::foundations::{func, scope, ty, Repr, Resolve, StyleChain, Styles}; +use crate::foundations::{func, scope, ty, Fold, Repr, Resolve, StyleChain, Styles}; use crate::layout::{Abs, Em}; use crate::syntax::Span; use crate::util::Numeric; @@ -274,3 +274,9 @@ impl Resolve for Length { self.abs + self.em.resolve(styles) } } + +impl Fold for Length { + fn fold(self, _: Self) -> Self { + self + } +} diff --git a/crates/typst/src/layout/page.rs b/crates/typst/src/layout/page.rs index 7e94984b2..bb9097a65 100644 --- a/crates/typst/src/layout/page.rs +++ b/crates/typst/src/layout/page.rs @@ -549,18 +549,11 @@ impl Margin { } impl Fold for Margin { - type Output = Margin; - - fn fold(self, outer: Self::Output) -> Self::Output { - let sides = - self.sides - .zip(outer.sides) - .map(|(inner, outer)| match (inner, outer) { - (Some(value), Some(outer)) => Some(value.fold(outer)), - _ => inner.or(outer), - }); - let two_sided = self.two_sided.or(outer.two_sided); - Margin { sides, two_sided } + fn fold(self, outer: Self) -> Self { + Margin { + sides: self.sides.fold(outer.sides), + two_sided: self.two_sided.fold(outer.two_sided), + } } } diff --git a/crates/typst/src/layout/rel.rs b/crates/typst/src/layout/rel.rs index 8f9371020..df58df86d 100644 --- a/crates/typst/src/layout/rel.rs +++ b/crates/typst/src/layout/rel.rs @@ -259,19 +259,12 @@ where } } -impl Fold for Rel { - type Output = Self; - - fn fold(self, _: Self::Output) -> Self::Output { - self - } -} - -impl Fold for Rel { - type Output = Self; - - fn fold(self, _: Self::Output) -> Self::Output { - self +impl Fold for Rel +where + T: Numeric + Fold, +{ + fn fold(self, outer: Self) -> Self { + Self { rel: self.rel, abs: self.abs.fold(outer.abs) } } } diff --git a/crates/typst/src/layout/sides.rs b/crates/typst/src/layout/sides.rs index 937919c4b..bb35ffb3e 100644 --- a/crates/typst/src/layout/sides.rs +++ b/crates/typst/src/layout/sides.rs @@ -94,6 +94,16 @@ impl Sides { } } +impl Sides> { + /// Unwrap-or-default the individual sides. + pub fn unwrap_or_default(self) -> Sides + where + T: Default, + { + self.map(Option::unwrap_or_default) + } +} + impl Sides> { /// Evaluate the sides relative to the given `size`. pub fn relative_to(self, size: Size) -> Sides { @@ -159,20 +169,21 @@ impl Reflect for Sides> { } } -impl IntoValue for Sides +impl IntoValue for Sides> where T: PartialEq + IntoValue, { fn into_value(self) -> Value { if self.is_uniform() { - return self.left.into_value(); + if let Some(left) = self.left { + return left.into_value(); + } } let mut dict = Dict::new(); - let mut handle = |key: &str, component: T| { - let value = component.into_value(); - if value != Value::None { - dict.insert(key.into(), value); + let mut handle = |key: &str, component: Option| { + if let Some(c) = component { + dict.insert(key.into(), c.into_value()); } }; @@ -233,12 +244,13 @@ impl Resolve for Sides { } impl Fold for Sides> { - type Output = Sides; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.zip(outer).map(|(inner, outer)| match inner { - Some(value) => value.fold(outer), - None => outer, + fn fold(self, outer: Self) -> Self { + self.zip(outer).map(|(inner, outer)| match (inner, outer) { + (Some(inner), Some(outer)) => Some(inner.fold(outer)), + // Usually, folding an inner `None` with an `outer` preferres the + // explicit `None`. However, here `None` means unspecified and thus + // we want `outer`. + (inner, outer) => inner.or(outer), }) } } diff --git a/crates/typst/src/math/cancel.rs b/crates/typst/src/math/cancel.rs index 039ef752a..695ff567a 100644 --- a/crates/typst/src/math/cancel.rs +++ b/crates/typst/src/math/cancel.rs @@ -97,7 +97,7 @@ pub struct CancelElem { #[fold] #[default(Stroke { // Default stroke has 0.5pt for better visuals. - thickness: Smart::Custom(Abs::pt(0.5)), + thickness: Smart::Custom(Abs::pt(0.5).into()), ..Default::default() })] pub stroke: Stroke, diff --git a/crates/typst/src/math/matrix.rs b/crates/typst/src/math/matrix.rs index 616cc7381..97b01491f 100644 --- a/crates/typst/src/math/matrix.rs +++ b/crates/typst/src/math/matrix.rs @@ -425,11 +425,10 @@ fn layout_mat_body( }; let (hline, vline, stroke) = match augment { - Some(v) => { - // need to get stroke here for ownership - let stroke = v.stroke_or(default_stroke); - - (v.hline, v.vline, stroke) + Some(augment) => { + // We need to get stroke here for ownership. + let stroke = augment.stroke.unwrap_or_default().unwrap_or(default_stroke); + (augment.hline, augment.vline, stroke) } _ => (AugmentOffsets::default(), AugmentOffsets::default(), default_stroke), }; @@ -593,11 +592,19 @@ pub struct Augment { pub stroke: Smart>, } -impl Augment { - fn stroke_or(&self, fallback: FixedStroke) -> FixedStroke { - match &self.stroke { - Smart::Custom(v) => v.clone().unwrap_or(fallback), - Smart::Auto => fallback, +impl Fold for Augment { + fn fold(self, outer: Self) -> Self { + Self { + stroke: match (self.stroke, outer.stroke) { + (Smart::Custom(inner), Smart::Custom(outer)) => { + Smart::Custom(inner.fold(outer)) + } + // Usually, folding an inner `auto` with an `outer` preferres + // the explicit `auto`. However, here `auto` means unspecified + // and thus we want `outer`. + (inner, outer) => inner.or(outer), + }, + ..self } } } @@ -614,21 +621,6 @@ impl Resolve for Augment { } } -impl Fold for Augment { - type Output = Augment; - - fn fold(mut self, outer: Self::Output) -> Self::Output { - // Special case for handling `auto` strokes in subsequent `Augment`. - if self.stroke.is_auto() && outer.stroke.is_custom() { - self.stroke = outer.stroke; - } else { - self.stroke = self.stroke.fold(outer.stroke); - } - - self - } -} - cast! { Augment, self => { @@ -637,13 +629,11 @@ cast! { return self.vline.0[0].into_value(); } - let d = dict! { - "hline" => self.hline.into_value(), - "vline" => self.vline.into_value(), - "stroke" => self.stroke.into_value() - }; - - d.into_value() + dict! { + "hline" => self.hline, + "vline" => self.vline, + "stroke" => self.stroke, + }.into_value() }, v: isize => Augment { hline: AugmentOffsets::default(), @@ -651,15 +641,15 @@ cast! { stroke: Smart::Auto, }, mut dict: Dict => { - // need the transpose for the defaults to work - let hline = dict.take("hline").ok().map(AugmentOffsets::from_value) - .transpose().unwrap_or_default().unwrap_or_default(); - let vline = dict.take("vline").ok().map(AugmentOffsets::from_value) - .transpose().unwrap_or_default().unwrap_or_default(); - - let stroke = dict.take("stroke").ok().map(Stroke::from_value) - .transpose()?.map(Smart::Custom).unwrap_or(Smart::Auto); - + let mut take = |key| dict.take(key).ok().map(AugmentOffsets::from_value).transpose(); + let hline = take("hline")?.unwrap_or_default(); + let vline = take("vline")?.unwrap_or_default(); + let stroke = dict.take("stroke") + .ok() + .map(Stroke::from_value) + .transpose()? + .map(Smart::Custom) + .unwrap_or(Smart::Auto); Augment { hline, vline, stroke } }, } diff --git a/crates/typst/src/model/bibliography.rs b/crates/typst/src/model/bibliography.rs index 94681fe1d..6e20e98ab 100644 --- a/crates/typst/src/model/bibliography.rs +++ b/crates/typst/src/model/bibliography.rs @@ -323,7 +323,6 @@ impl LocalName for Packed { } /// A loaded bibliography. -#[ty] #[derive(Clone, PartialEq)] pub struct Bibliography { map: Arc>, @@ -422,12 +421,6 @@ impl Hash for Bibliography { } } -impl Repr for Bibliography { - fn repr(&self) -> EcoString { - "..".into() - } -} - /// Format a BibLaTeX loading error. fn format_biblatex_error(path: &str, src: &str, errors: Vec) -> EcoString { let Some(error) = errors.first() else { diff --git a/crates/typst/src/model/emph.rs b/crates/typst/src/model/emph.rs index b67643dee..a4c916ed4 100644 --- a/crates/typst/src/model/emph.rs +++ b/crates/typst/src/model/emph.rs @@ -36,6 +36,6 @@ pub struct EmphElem { impl Show for Packed { #[typst_macros::time(name = "emph", span = self.span())] fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult { - Ok(self.body().clone().styled(TextElem::set_emph(ItalicToggle))) + Ok(self.body().clone().styled(TextElem::set_emph(ItalicToggle(true)))) } } diff --git a/crates/typst/src/model/enum.rs b/crates/typst/src/model/enum.rs index 622501db5..0c1a31c57 100644 --- a/crates/typst/src/model/enum.rs +++ b/crates/typst/src/model/enum.rs @@ -1,10 +1,10 @@ use std::str::FromStr; +use smallvec::{smallvec, SmallVec}; + use crate::diag::{bail, SourceResult}; use crate::engine::Engine; -use crate::foundations::{ - cast, elem, scope, Array, Content, Fold, Packed, Smart, StyleChain, -}; +use crate::foundations::{cast, elem, scope, Array, Content, Packed, Smart, StyleChain}; use crate::layout::{ Alignment, Axes, BlockElem, Cell, CellGrid, Em, Fragment, GridLayouter, HAlignment, LayoutMultiple, Length, Regions, Sizing, Spacing, VAlignment, @@ -199,7 +199,7 @@ pub struct EnumElem { /// The numbers of parent items. #[internal] #[fold] - parents: Parent, + parents: SmallVec<[usize; 4]>, } #[scope] @@ -229,6 +229,8 @@ impl LayoutMultiple for Packed { let mut cells = vec![]; let mut number = self.start(styles); let mut parents = self.parents(styles); + parents.reverse(); + let full = self.full(styles); // Horizontally align based on the given respective parameter. @@ -263,7 +265,7 @@ impl LayoutMultiple for Packed { cells.push(Cell::from(resolved)); cells.push(Cell::from(Content::empty())); cells.push(Cell::from( - item.body().clone().styled(EnumElem::set_parents(Parent(number))), + item.body().clone().styled(EnumElem::set_parents(smallvec![number])), )); number = number.saturating_add(1); } @@ -309,21 +311,3 @@ cast! { }, v: Content => v.unpack::().unwrap_or_else(Self::new), } - -#[derive(Debug, Clone, Copy, PartialEq, Hash)] -struct Parent(usize); - -cast! { - Parent, - self => self.0.into_value(), - v: usize => Self(v), -} - -impl Fold for Parent { - type Output = Vec; - - fn fold(self, mut outer: Self::Output) -> Self::Output { - outer.push(self.0); - outer - } -} diff --git a/crates/typst/src/model/list.rs b/crates/typst/src/model/list.rs index 71a586b94..c849fa65f 100644 --- a/crates/typst/src/model/list.rs +++ b/crates/typst/src/model/list.rs @@ -150,7 +150,7 @@ impl LayoutMultiple for Packed { .unwrap_or_else(|| *BlockElem::below_in(styles).amount()) }; - let depth = self.depth(styles); + let Depth(depth) = self.depth(styles); let marker = self .marker(styles) .resolve(engine, depth)? @@ -162,8 +162,9 @@ impl LayoutMultiple for Packed { cells.push(Cell::from(Content::empty())); cells.push(Cell::from(marker.clone())); cells.push(Cell::from(Content::empty())); - cells - .push(Cell::from(item.body().clone().styled(ListElem::set_depth(Depth)))); + cells.push(Cell::from( + item.body().clone().styled(ListElem::set_depth(Depth(1))), + )); } let stroke = None; @@ -235,19 +236,11 @@ cast! { v: Func => Self::Func(v), } -#[derive(Debug, Clone, Copy, PartialEq, Hash)] -struct Depth; - -cast! { - Depth, - self => Value::None, - _: Value => Self, -} +#[derive(Debug, Default, Clone, Copy, PartialEq, Hash)] +struct Depth(usize); impl Fold for Depth { - type Output = usize; - - fn fold(self, outer: Self::Output) -> Self::Output { - outer + 1 + fn fold(self, outer: Self) -> Self { + Self(outer.0 + self.0) } } diff --git a/crates/typst/src/model/table.rs b/crates/typst/src/model/table.rs index ecda0527d..256459c74 100644 --- a/crates/typst/src/model/table.rs +++ b/crates/typst/src/model/table.rs @@ -194,7 +194,7 @@ pub struct TableElem { /// ) /// ``` #[fold] - #[default(Sides::splat(Abs::pt(5.0).into()))] + #[default(Sides::splat(Some(Abs::pt(5.0).into())))] pub inset: Sides>>, /// The contents of the table cells. @@ -378,7 +378,7 @@ impl ResolvableCell for Packed { y: usize, fill: &Option, align: Smart, - inset: Sides>, + inset: Sides>>, styles: StyleChain, ) -> Cell { let cell = &mut *self; @@ -397,9 +397,8 @@ impl ResolvableCell for Packed { Smart::Auto => cell.align(styles), }); cell.push_inset(Smart::Custom( - cell.inset(styles).map_or(inset, |inner| inner.fold(inset)).map(Some), + cell.inset(styles).map_or(inset, |inner| inner.fold(inset)), )); - Cell { body: self.pack(), fill, colspan } } diff --git a/crates/typst/src/text/deco.rs b/crates/typst/src/text/deco.rs index 3872334d3..7873f97b5 100644 --- a/crates/typst/src/text/deco.rs +++ b/crates/typst/src/text/deco.rs @@ -1,13 +1,10 @@ use kurbo::{BezPath, Line, ParamCurve}; +use smallvec::smallvec; use ttf_parser::{GlyphId, OutlineBuilder}; -use ecow::{eco_format, EcoString}; - use crate::diag::SourceResult; use crate::engine::Engine; -use crate::foundations::{ - elem, ty, Content, Fold, Packed, Repr, Show, Smart, StyleChain, -}; +use crate::foundations::{elem, Content, Packed, Show, Smart, StyleChain}; use crate::layout::{Abs, Em, Frame, FrameItem, Length, Point, Size}; use crate::syntax::Span; use crate::text::{ @@ -89,7 +86,7 @@ pub struct UnderlineElem { impl Show for Packed { #[typst_macros::time(name = "underline", span = self.span())] fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult { - Ok(self.body().clone().styled(TextElem::set_deco(Decoration { + Ok(self.body().clone().styled(TextElem::set_deco(smallvec![Decoration { line: DecoLine::Underline { stroke: self.stroke(styles).unwrap_or_default(), offset: self.offset(styles), @@ -97,7 +94,7 @@ impl Show for Packed { background: self.background(styles), }, extent: self.extent(styles), - }))) + }]))) } } @@ -181,7 +178,7 @@ pub struct OverlineElem { impl Show for Packed { #[typst_macros::time(name = "overline", span = self.span())] fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult { - Ok(self.body().clone().styled(TextElem::set_deco(Decoration { + Ok(self.body().clone().styled(TextElem::set_deco(smallvec![Decoration { line: DecoLine::Overline { stroke: self.stroke(styles).unwrap_or_default(), offset: self.offset(styles), @@ -189,7 +186,7 @@ impl Show for Packed { background: self.background(styles), }, extent: self.extent(styles), - }))) + }]))) } } @@ -258,7 +255,7 @@ pub struct StrikeElem { impl Show for Packed { #[typst_macros::time(name = "strike", span = self.span())] fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult { - Ok(self.body().clone().styled(TextElem::set_deco(Decoration { + Ok(self.body().clone().styled(TextElem::set_deco(smallvec![Decoration { // Note that we do not support evade option for strikethrough. line: DecoLine::Strikethrough { stroke: self.stroke(styles).unwrap_or_default(), @@ -266,7 +263,7 @@ impl Show for Packed { background: self.background(styles), }, extent: self.extent(styles), - }))) + }]))) } } @@ -328,14 +325,14 @@ pub struct HighlightElem { impl Show for Packed { #[typst_macros::time(name = "highlight", span = self.span())] fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult { - Ok(self.body().clone().styled(TextElem::set_deco(Decoration { + Ok(self.body().clone().styled(TextElem::set_deco(smallvec![Decoration { line: DecoLine::Highlight { fill: self.fill(styles), top_edge: self.top_edge(styles), bottom_edge: self.bottom_edge(styles), }, extent: self.extent(styles), - }))) + }]))) } } @@ -343,28 +340,12 @@ impl Show for Packed { /// /// Can be positioned over, under, or on top of text, or highlight the text with /// a background. -#[ty] #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Decoration { line: DecoLine, extent: Abs, } -impl Fold for Decoration { - type Output = Vec; - - fn fold(self, mut outer: Self::Output) -> Self::Output { - outer.insert(0, self); - outer - } -} - -impl Repr for Decoration { - fn repr(&self) -> EcoString { - eco_format!("{self:?}") - } -} - /// A kind of decorative line. #[derive(Debug, Clone, Eq, PartialEq, Hash)] enum DecoLine { diff --git a/crates/typst/src/text/mod.rs b/crates/typst/src/text/mod.rs index 23a685d84..eab010666 100644 --- a/crates/typst/src/text/mod.rs +++ b/crates/typst/src/text/mod.rs @@ -32,6 +32,7 @@ use std::fmt::{self, Debug, Formatter}; use ecow::{eco_format, EcoString}; use rustybuzz::{Feature, Tag}; +use smallvec::SmallVec; use ttf_parser::Rect; use crate::diag::{bail, SourceResult, StrResult}; @@ -39,8 +40,9 @@ use crate::engine::Engine; use crate::foundations::Packed; use crate::foundations::{ cast, category, elem, Args, Array, Cast, Category, Construct, Content, Dict, Fold, - NativeElement, Never, PlainText, Repr, Resolve, Scope, Set, Smart, StyleChain, Value, + NativeElement, Never, PlainText, Repr, Resolve, Scope, Set, Smart, StyleChain, }; +use crate::layout::Em; use crate::layout::{Abs, Axis, Dir, Length, Rel}; use crate::model::ParElem; use crate::syntax::Spanned; @@ -214,7 +216,8 @@ pub struct TextElem { /// ``` #[parse(args.named_or_find("size")?)] #[fold] - #[default(Abs::pt(11.0))] + #[default(TextSize(Abs::pt(11.0).into()))] + #[resolve] #[ghost] pub size: TextSize, @@ -628,7 +631,7 @@ pub struct TextElem { /// Whether the font style should be inverted. #[internal] #[fold] - #[default(false)] + #[default(ItalicToggle(false))] #[ghost] pub emph: ItalicToggle, @@ -636,7 +639,7 @@ pub struct TextElem { #[internal] #[fold] #[ghost] - pub deco: Decoration, + pub deco: SmallVec<[Decoration; 1]>, /// A case transformation that should be applied to the text. #[internal] @@ -763,12 +766,12 @@ pub(crate) fn variant(styles: StyleChain) -> FontVariant { TextElem::stretch_in(styles), ); - let delta = TextElem::delta_in(styles); + let WeightDelta(delta) = TextElem::delta_in(styles); variant.weight = variant .weight .thicken(delta.clamp(i16::MIN as i64, i16::MAX as i64) as i16); - if TextElem::emph_in(styles) { + if TextElem::emph_in(styles).0 { variant.style = match variant.style { FontStyle::Normal => FontStyle::Italic, FontStyle::Italic => FontStyle::Normal, @@ -784,10 +787,20 @@ pub(crate) fn variant(styles: StyleChain) -> FontVariant { pub struct TextSize(pub Length); impl Fold for TextSize { + fn fold(self, outer: Self) -> Self { + // Multiply the two linear functions. + Self(Length { + em: Em::new(self.0.em.get() * outer.0.em.get()), + abs: self.0.em.get() * outer.0.abs + self.0.abs, + }) + } +} + +impl Resolve for TextSize { type Output = Abs; - fn fold(self, outer: Self::Output) -> Self::Output { - self.0.em.at(outer) + self.0.abs + fn resolve(self, styles: StyleChain) -> Self::Output { + self.0.resolve(styles) } } @@ -1056,11 +1069,8 @@ cast! { } impl Fold for FontFeatures { - type Output = Self; - - fn fold(mut self, outer: Self::Output) -> Self::Output { - self.0.extend(outer.0); - self + fn fold(self, outer: Self) -> Self { + Self(self.0.fold(outer.0)) } } @@ -1133,36 +1143,20 @@ pub(crate) fn features(styles: StyleChain) -> Vec { /// A toggle that turns on and off alternatingly if folded. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct ItalicToggle; - -cast! { - ItalicToggle, - self => Value::None, - _: Value => Self, -} +pub struct ItalicToggle(pub bool); impl Fold for ItalicToggle { - type Output = bool; - - fn fold(self, outer: Self::Output) -> Self::Output { - !outer + fn fold(self, outer: Self) -> Self { + Self(self.0 ^ outer.0) } } /// A delta that is summed up when folded. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] pub struct WeightDelta(pub i64); -cast! { - WeightDelta, - self => self.0.into_value(), - v: i64 => Self(v), -} - impl Fold for WeightDelta { - type Output = i64; - - fn fold(self, outer: Self::Output) -> Self::Output { - outer + self.0 + fn fold(self, outer: Self) -> Self { + Self(outer.0 + self.0) } } diff --git a/crates/typst/src/text/raw.rs b/crates/typst/src/text/raw.rs index b032f6664..28fcf8914 100644 --- a/crates/typst/src/text/raw.rs +++ b/crates/typst/src/text/raw.rs @@ -658,11 +658,8 @@ cast! { } impl Fold for SyntaxPaths { - type Output = Self; - - fn fold(mut self, outer: Self::Output) -> Self::Output { - self.0.extend(outer.0); - self + fn fold(self, outer: Self) -> Self { + Self(self.0.fold(outer.0)) } } diff --git a/crates/typst/src/visualize/shape.rs b/crates/typst/src/visualize/shape.rs index 89de33986..de1eac166 100644 --- a/crates/typst/src/visualize/shape.rs +++ b/crates/typst/src/visualize/shape.rs @@ -114,7 +114,7 @@ pub struct RectElem { /// See the [box's documentation]($box.outset) for more details. #[resolve] #[fold] - #[default(Sides::splat(Abs::pt(5.0).into()))] + #[default(Sides::splat(Some(Abs::pt(5.0).into())))] pub inset: Sides>>, /// How much to expand the rectangle's size without affecting the layout. @@ -219,7 +219,7 @@ pub struct SquareElem { /// [box's documentation]($box.inset) for more details. #[resolve] #[fold] - #[default(Sides::splat(Abs::pt(5.0).into()))] + #[default(Sides::splat(Some(Abs::pt(5.0).into())))] pub inset: Sides>>, /// How much to expand the square's size without affecting the layout. See @@ -298,7 +298,7 @@ pub struct EllipseElem { /// [box's documentation]($box.inset) for more details. #[resolve] #[fold] - #[default(Sides::splat(Abs::pt(5.0).into()))] + #[default(Sides::splat(Some(Abs::pt(5.0).into())))] pub inset: Sides>>, /// How much to expand the ellipse's size without affecting the layout. See @@ -331,10 +331,10 @@ impl LayoutSingle for Packed { &self.body(styles), Axes::new(self.width(styles), self.height(styles)), self.fill(styles), - self.stroke(styles).map(Sides::splat), + self.stroke(styles).map(|s| Sides::splat(Some(s))), self.inset(styles), self.outset(styles), - Corners::splat(Rel::zero()), + Corners::splat(None), self.span(), ) } @@ -403,7 +403,7 @@ pub struct CircleElem { /// [box's documentation]($box.inset) for more details. #[resolve] #[fold] - #[default(Sides::splat(Abs::pt(5.0).into()))] + #[default(Sides::splat(Some(Abs::pt(5.0).into())))] pub inset: Sides>>, /// How much to expand the circle's size without affecting the layout. See @@ -434,10 +434,10 @@ impl LayoutSingle for Packed { &self.body(styles), Axes::new(self.width(styles), self.height(styles)), self.fill(styles), - self.stroke(styles).map(Sides::splat), + self.stroke(styles).map(|s| Sides::splat(Some(s))), self.inset(styles), self.outset(styles), - Corners::splat(Rel::zero()), + Corners::splat(None), self.span(), ) } @@ -453,18 +453,21 @@ fn layout( body: &Option, sizing: Axes>>, fill: Option, - stroke: Smart>>>, - mut inset: Sides>, - outset: Sides>, - radius: Corners>, + stroke: Smart>>>>, + inset: Sides>>, + outset: Sides>>, + radius: Corners>>, span: Span, ) -> SourceResult { let resolved = sizing .zip_map(regions.base(), |s, r| s.map(|v| v.resolve(styles).relative_to(r))); let mut frame; + let mut inset = inset.unwrap_or_default(); + if let Some(child) = body { let region = resolved.unwrap_or(regions.base()); + if kind.is_round() { inset = inset.map(|side| side + Ratio::new(0.5 - SQRT_2 / 4.0)); } @@ -507,19 +510,27 @@ fn layout( let stroke = match stroke { Smart::Auto if fill.is_none() => Sides::splat(Some(FixedStroke::default())), Smart::Auto => Sides::splat(None), - Smart::Custom(strokes) => strokes.map(|s| s.map(Stroke::unwrap_or_default)), + Smart::Custom(strokes) => { + strokes.unwrap_or_default().map(|s| s.map(Stroke::unwrap_or_default)) + } }; // Add fill and/or stroke. if fill.is_some() || stroke.iter().any(Option::is_some) { if kind.is_round() { - let outset = outset.relative_to(frame.size()); + let outset = outset.unwrap_or_default().relative_to(frame.size()); let size = frame.size() + outset.sum_by_axis(); let pos = Point::new(-outset.left, -outset.top); let shape = ellipse(size, fill, stroke.left); frame.prepend(pos, FrameItem::Shape(shape, span)); } else { - frame.fill_and_stroke(fill, stroke, outset, radius, span); + frame.fill_and_stroke( + fill, + stroke, + outset.unwrap_or_default(), + radius.unwrap_or_default(), + span, + ); } } diff --git a/crates/typst/src/visualize/stroke.rs b/crates/typst/src/visualize/stroke.rs index 1d43a7b3c..d060ad090 100644 --- a/crates/typst/src/visualize/stroke.rs +++ b/crates/typst/src/visualize/stroke.rs @@ -330,6 +330,19 @@ impl Repr for Stroke { } } +impl Fold for Stroke { + fn fold(self, outer: Self) -> Self { + Self { + paint: self.paint.or(outer.paint), + thickness: self.thickness.or(outer.thickness), + cap: self.cap.or(outer.cap), + join: self.join.or(outer.join), + dash: self.dash.or(outer.dash), + miter_limit: self.miter_limit.or(outer.miter_limit), + } + } +} + impl Resolve for Stroke { type Output = Stroke; @@ -345,21 +358,6 @@ impl Resolve for Stroke { } } -impl Fold for Stroke { - type Output = Self; - - fn fold(self, outer: Self::Output) -> Self::Output { - Self { - paint: self.paint.or(outer.paint), - thickness: self.thickness.or(outer.thickness), - cap: self.cap.or(outer.cap), - join: self.join.or(outer.join), - dash: self.dash.or(outer.dash), - miter_limit: self.miter_limit.or(outer.miter_limit), - } - } -} - cast! { type Stroke, thickness: Length => Self {