Refactor folding (#3294)

This commit is contained in:
Laurenz 2024-01-30 10:43:08 +01:00 committed by GitHub
parent a3e1c70e9e
commit f14288cacf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 306 additions and 412 deletions

View File

@ -106,7 +106,9 @@ impl Elem {
/// This includes: /// This includes:
/// - Fields that are not synthesized. /// - Fields that are not synthesized.
fn construct_fields(&self) -> impl Iterator<Item = &Field> + Clone { fn construct_fields(&self) -> impl Iterator<Item = &Field> + 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> {
field.output = parse_quote! { <#output as #foundations::Resolve>::Output }; 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)?; validate_attrs(&attrs)?;
Ok(field) Ok(field)
@ -555,32 +552,42 @@ fn create_style_chain_access(
let elem = &element.ident; let elem = &element.ident;
let Field { ty, default, enum_ident, .. } = field; let Field { ty, default, enum_ident, .. } = field;
let getter = match (field.fold, field.resolve, field.borrowed) {
(false, false, false) => quote! { get }, let getter = match (field.fold, field.borrowed) {
(false, false, true) => quote! { get_borrowed }, (false, false) => quote! { get },
(false, true, _) => quote! { get_resolve }, (false, true) => quote! { get_ref },
(true, false, _) => quote! { get_fold }, (true, _) => quote! { get_folded },
(true, true, _) => quote! { get_resolve_fold },
}; };
let default = default let mut default = match default {
.clone() Some(default) => quote! { #default },
.unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }); None => 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 },
));
quote! { let mut init = None;
#init 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>( styles.#getter::<#ty>(
<Self as #foundations::NativeElement>::elem(), <Self as #foundations::NativeElement>::elem(),
<#elem as #foundations::Fields>::Enum::#enum_ident as u8, <#elem as #foundations::Fields>::Enum::#enum_ident as u8,
#inherent, #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, variadic,
required, required,
default, default,
fold,
ty, ty,
output,
.. ..
} = field; } = field;
let named = !positional; let named = !positional;
let settable = field.settable(); let settable = field.settable();
let default_ty = if *fold { &output } else { &ty };
let default = quote_option(&settable.then(|| { let default = quote_option(&settable.then(|| {
let default = default let default = default
.clone() .clone()
.unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }); .unwrap_or_else(|| parse_quote! { ::std::default::Default::default() });
quote! { quote! {
|| { || {
let typed: #default_ty = #default; let typed: #ty = #default;
#foundations::IntoValue::into_value(typed) #foundations::IntoValue::into_value(typed)
} }
} }

View File

@ -239,14 +239,14 @@ impl<T: Resolve> Resolve for Smart<T> {
} }
} }
impl<T> Fold for Smart<T> impl<T: Fold> Fold for Smart<T> {
where fn fold(self, outer: Self) -> Self {
T: Fold, use Smart::Custom;
T::Output: Default, match (self, outer) {
{ (Custom(inner), Custom(outer)) => Custom(inner.fold(outer)),
type Output = Smart<T::Output>; // An explicit `auto` should be respected, thus we don't do
// `inner.or(outer)`.
fn fold(self, outer: Self::Output) -> Self::Output { (inner, _) => inner,
self.map(|inner| inner.fold(outer.unwrap_or_default())) }
} }
} }

View File

@ -138,7 +138,6 @@ impl Dict {
}; };
let mut msg = String::from(match unexpected.len() { let mut msg = String::from(match unexpected.len() {
0 => unreachable!(),
1 => "unexpected key ", 1 => "unexpected key ",
_ => "unexpected keys ", _ => "unexpected keys ",
}); });

View File

@ -6,7 +6,6 @@ use std::{iter, mem, ptr};
use comemo::Prehashed; use comemo::Prehashed;
use ecow::{eco_vec, EcoString, EcoVec}; use ecow::{eco_vec, EcoString, EcoVec};
use once_cell::sync::Lazy;
use smallvec::SmallVec; use smallvec::SmallVec;
use crate::diag::{SourceResult, Trace, Tracepoint}; use crate::diag::{SourceResult, Trace, Tracepoint};
@ -457,54 +456,47 @@ impl<'a> StyleChain<'a> {
Chainable::chain(local, self) Chainable::chain(local, self)
} }
/// Cast the first value for the given property in the chain.
pub fn get<T: Clone + 'static>(
self,
func: Element,
id: u8,
inherent: Option<&T>,
default: impl Fn() -> T,
) -> T {
self.properties::<T>(func, id, inherent)
.next()
.cloned()
.unwrap_or_else(default)
}
/// Cast the first value for the given property in the chain, /// Cast the first value for the given property in the chain,
/// returning a borrowed value if possible. /// returning a borrowed value.
pub fn get_borrowed<T: Clone>( pub fn get_ref<T: 'static>(
self, self,
func: Element, func: Element,
id: u8, id: u8,
inherent: Option<&'a T>, inherent: Option<&'a T>,
default: &'static Lazy<T>, default: impl Fn() -> &'a T,
) -> &'a T { ) -> &'a T {
self.properties::<T>(func, id, inherent) self.properties::<T>(func, id, inherent)
.next() .next()
.unwrap_or_else(|| default) .unwrap_or_else(default)
} }
/// Cast the first value for the given property in the chain. /// Cast the first value for the given property in the chain, taking
pub fn get<T: Clone>( /// `Fold` implementations into account.
pub fn get_folded<T: Fold + Clone + 'static>(
self, self,
func: Element, func: Element,
id: u8, id: u8,
inherent: Option<&T>, inherent: Option<&T>,
default: &'static Lazy<T>, default: impl Fn() -> T,
) -> 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<T: Clone + Resolve>(
self,
func: Element,
id: u8,
inherent: Option<&T>,
default: &'static Lazy<T>,
) -> 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<T: Clone + Fold + 'static>(
self,
func: Element,
id: u8,
inherent: Option<&T>,
default: impl Fn() -> T::Output,
) -> T::Output {
fn next<T: Fold>( fn next<T: Fold>(
mut values: impl Iterator<Item = T>, mut values: impl Iterator<Item = T>,
default: &impl Fn() -> T::Output, default: &impl Fn() -> T,
) -> T::Output { ) -> T {
values values
.next() .next()
.map(|value| value.fold(next(values, default))) .map(|value| value.fold(next(values, default)))
@ -513,36 +505,6 @@ impl<'a> StyleChain<'a> {
next(self.properties::<T>(func, id, inherent).cloned(), &default) next(self.properties::<T>(func, id, inherent).cloned(), &default)
} }
/// Cast the first value for the given property in the chain.
pub fn get_resolve_fold<T>(
self,
func: Element,
id: u8,
inherent: Option<&T>,
default: impl Fn() -> <T::Output as Fold>::Output,
) -> <T::Output as Fold>::Output
where
T: Resolve + Clone + 'static,
T::Output: Fold,
{
fn next<T>(
mut values: impl Iterator<Item = T>,
styles: StyleChain,
default: &impl Fn() -> <T::Output as Fold>::Output,
) -> <T::Output as Fold>::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::<T>(func, id, inherent).cloned(), self, &default)
}
/// Iterate over all style recipes in the chain. /// Iterate over all style recipes in the chain.
pub fn recipes(self) -> impl Iterator<Item = &'a Recipe> { pub fn recipes(self) -> impl Iterator<Item = &'a Recipe> {
self.entries().filter_map(Style::recipe) self.entries().filter_map(Style::recipe)
@ -925,38 +887,36 @@ impl<T: Resolve> Resolve for Option<T> {
/// #rect() /// #rect()
/// ``` /// ```
pub trait Fold { pub trait Fold {
/// The type of the folded output.
type Output;
/// Fold this inner value with an outer folded value. /// Fold this inner value with an outer folded value.
fn fold(self, outer: Self::Output) -> Self::Output; fn fold(self, outer: Self) -> Self;
} }
impl<T> Fold for Option<T> impl Fold for bool {
where fn fold(self, _: Self) -> Self {
T: Fold, self
T::Output: Default, }
{ }
type Output = Option<T::Output>;
fn fold(self, outer: Self::Output) -> Self::Output { impl<T: Fold> Fold for Option<T> {
self.map(|inner| inner.fold(outer.unwrap_or_default())) 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<T> Fold for Vec<T> { impl<T> Fold for Vec<T> {
type Output = Vec<T>; fn fold(mut self, outer: Self) -> Self {
fn fold(mut self, outer: Self::Output) -> Self::Output {
self.extend(outer); self.extend(outer);
self self
} }
} }
impl<T, const N: usize> Fold for SmallVec<[T; N]> { impl<T, const N: usize> Fold for SmallVec<[T; N]> {
type Output = SmallVec<[T; N]>; fn fold(mut self, outer: Self) -> Self {
fn fold(mut self, outer: Self::Output) -> Self::Output {
self.extend(outer); self.extend(outer);
self self
} }

View File

@ -215,9 +215,7 @@ impl Repr for Alignment {
} }
impl Fold for Alignment { impl Fold for Alignment {
type Output = Self; fn fold(self, outer: Self) -> Self {
fn fold(self, outer: Self::Output) -> Self::Output {
match (self, outer) { match (self, outer) {
(Self::H(x), Self::V(y) | Self::Both(_, y)) => Self::Both(x, y), (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), (Self::V(y), Self::H(x) | Self::Both(x, _)) => Self::Both(x, y),

View File

@ -3,7 +3,7 @@ use std::fmt::{self, Debug, Formatter};
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Deref, Not}; use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Deref, Not};
use crate::diag::bail; 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::layout::{Abs, Dir, Length, Ratio, Rel};
use crate::util::Get; use crate::util::Get;
@ -84,14 +84,6 @@ impl<T> Axes<T> {
{ {
f(&self.x) && f(&self.y) f(&self.x) && f(&self.y)
} }
/// Filter the individual fields with a mask.
pub fn filter(self, mask: Axes<bool>) -> Axes<Option<T>> {
Axes {
x: if mask.x { Some(self.x) } else { None },
y: if mask.y { Some(self.y) } else { None },
}
}
} }
impl<T: Default> Axes<T> { impl<T: Default> Axes<T> {
@ -198,16 +190,6 @@ cast! {
"vertical" => Self::Y, "vertical" => Self::Y,
} }
impl<T> Axes<Option<T>> {
/// Unwrap the individual fields.
pub fn unwrap_or(self, other: Axes<T>) -> Axes<T> {
Axes {
x: self.x.unwrap_or(other.x),
y: self.y.unwrap_or(other.y),
}
}
}
impl<T> Axes<Smart<T>> { impl<T> Axes<Smart<T>> {
/// Unwrap the individual fields. /// Unwrap the individual fields.
pub fn unwrap_or(self, other: Axes<T>) -> Axes<T> { pub fn unwrap_or(self, other: Axes<T>) -> Axes<T> {
@ -325,14 +307,3 @@ impl<T: Resolve> Resolve for Axes<T> {
self.map(|v| v.resolve(styles)) self.map(|v| v.resolve(styles))
} }
} }
impl<T: Fold> Fold for Axes<Option<T>> {
type Output = Axes<T::Output>;
fn fold(self, outer: Self::Output) -> Self::Output {
self.zip_map(outer, |inner, outer| match inner {
Some(value) => value.fold(outer),
None => outer,
})
}
}

View File

@ -133,7 +133,7 @@ impl Packed<BoxElem> {
// Apply inset. // Apply inset.
let mut body = self.body(styles).unwrap_or_default(); 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()) { if inset.iter().any(|v| !v.is_zero()) {
body = body.padded(inset.map(|side| side.map(Length::from))); body = body.padded(inset.map(|side| side.map(Length::from)));
} }
@ -154,20 +154,24 @@ impl Packed<BoxElem> {
// Prepare fill and stroke. // Prepare fill and stroke.
let fill = self.fill(styles); 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 // Clip the contents
if self.clip(styles) { 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 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)); frame.clip(clip_rect(size, radius, &stroke));
} }
// Add fill and/or stroke. // Add fill and/or stroke.
if fill.is_some() || stroke.iter().any(Option::is_some) { if fill.is_some() || stroke.iter().any(Option::is_some) {
let outset = self.outset(styles); let outset = self.outset(styles).unwrap_or_default();
let radius = self.radius(styles); let radius = self.radius(styles).unwrap_or_default();
frame.fill_and_stroke(fill, stroke, outset, radius, self.span()); frame.fill_and_stroke(fill, stroke, outset, radius, self.span());
} }
@ -350,7 +354,7 @@ impl LayoutMultiple for Packed<BlockElem> {
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
// Apply inset. // Apply inset.
let mut body = self.body(styles).unwrap_or_default(); 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()) { if inset.iter().any(|v| !v.is_zero()) {
body = body.clone().padded(inset.map(|side| side.map(Length::from))); body = body.clone().padded(inset.map(|side| side.map(Length::from)));
} }
@ -418,14 +422,18 @@ impl LayoutMultiple for Packed<BlockElem> {
// Prepare fill and stroke. // Prepare fill and stroke.
let fill = self.fill(styles); 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 // Clip the contents
if self.clip(styles) { if self.clip(styles) {
for frame in frames.iter_mut() { 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 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)); frame.clip(clip_rect(size, radius, &stroke));
} }
} }
@ -437,8 +445,8 @@ impl LayoutMultiple for Packed<BlockElem> {
skip = first.is_empty() && rest.iter().any(|frame| !frame.is_empty()); skip = first.is_empty() && rest.iter().any(|frame| !frame.is_empty());
} }
let outset = self.outset(styles); let outset = self.outset(styles).unwrap_or_default();
let radius = self.radius(styles); let radius = self.radius(styles).unwrap_or_default();
for frame in frames.iter_mut().skip(skip as usize) { for frame in frames.iter_mut().skip(skip as usize) {
frame.fill_and_stroke( frame.fill_and_stroke(
fill.clone(), fill.clone(),

View File

@ -80,6 +80,16 @@ impl<T> Corners<T> {
} }
} }
impl<T> Corners<Option<T>> {
/// Unwrap-or-default the individual corners.
pub fn unwrap_or_default(self) -> Corners<T>
where
T: Default,
{
self.map(Option::unwrap_or_default)
}
}
impl<T> Get<Corner> for Corners<T> { impl<T> Get<Corner> for Corners<T> {
type Component = T; type Component = T;
@ -133,20 +143,21 @@ impl<T: Reflect> Reflect for Corners<Option<T>> {
} }
} }
impl<T> IntoValue for Corners<T> impl<T> IntoValue for Corners<Option<T>>
where where
T: PartialEq + IntoValue, T: PartialEq + IntoValue,
{ {
fn into_value(self) -> Value { fn into_value(self) -> Value {
if self.is_uniform() { 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 dict = Dict::new();
let mut handle = |key: &str, component: T| { let mut handle = |key: &str, component: Option<T>| {
let value = component.into_value(); if let Some(c) = component {
if value != Value::None { dict.insert(key.into(), c.into_value());
dict.insert(key.into(), value);
} }
}; };
@ -228,12 +239,13 @@ impl<T: Resolve> Resolve for Corners<T> {
} }
impl<T: Fold> Fold for Corners<Option<T>> { impl<T: Fold> Fold for Corners<Option<T>> {
type Output = Corners<T::Output>; fn fold(self, outer: Self) -> Self {
self.zip(outer).map(|(inner, outer)| match (inner, outer) {
fn fold(self, outer: Self::Output) -> Self::Output { (Some(inner), Some(outer)) => Some(inner.fold(outer)),
self.zip(outer).map(|(inner, outer)| match inner { // Usually, folding an inner `None` with an `outer` preferres the
Some(value) => value.fold(outer), // explicit `None`. However, here `None` means unspecified and thus
None => outer, // we want `outer`.
(inner, outer) => inner.or(outer),
}) })
} }
} }

View File

@ -151,7 +151,7 @@ pub trait ResolvableCell {
y: usize, y: usize,
fill: &Option<Paint>, fill: &Option<Paint>,
align: Smart<Alignment>, align: Smart<Alignment>,
inset: Sides<Rel<Length>>, inset: Sides<Option<Rel<Length>>>,
styles: StyleChain, styles: StyleChain,
) -> Cell; ) -> Cell;
@ -204,7 +204,7 @@ impl CellGrid {
cells: &[T], cells: &[T],
fill: &Celled<Option<Paint>>, fill: &Celled<Option<Paint>>,
align: &Celled<Smart<Alignment>>, align: &Celled<Smart<Alignment>>,
inset: Sides<Rel<Length>>, inset: Sides<Option<Rel<Length>>>,
engine: &mut Engine, engine: &mut Engine,
styles: StyleChain, styles: StyleChain,
span: Span, span: Span,

View File

@ -13,8 +13,8 @@ use crate::foundations::{
cast, elem, scope, Array, Content, Fold, Packed, Show, Smart, StyleChain, Value, cast, elem, scope, Array, Content, Fold, Packed, Show, Smart, StyleChain, Value,
}; };
use crate::layout::{ use crate::layout::{
Abs, AlignElem, Alignment, Axes, Fragment, LayoutMultiple, Length, Regions, Rel, AlignElem, Alignment, Axes, Fragment, LayoutMultiple, Length, Regions, Rel, Sides,
Sides, Sizing, Sizing,
}; };
use crate::syntax::Span; use crate::syntax::Span;
use crate::util::NonZeroExt; use crate::util::NonZeroExt;
@ -264,7 +264,6 @@ pub struct GridElem {
/// ) /// )
/// ``` /// ```
#[fold] #[fold]
#[default(Sides::splat(Abs::pt(0.0).into()))]
pub inset: Sides<Option<Rel<Length>>>, pub inset: Sides<Option<Rel<Length>>>,
/// The contents of the grid cells. /// The contents of the grid cells.
@ -462,7 +461,7 @@ impl ResolvableCell for Packed<GridCell> {
y: usize, y: usize,
fill: &Option<Paint>, fill: &Option<Paint>,
align: Smart<Alignment>, align: Smart<Alignment>,
inset: Sides<Rel<Length>>, inset: Sides<Option<Rel<Length>>>,
styles: StyleChain, styles: StyleChain,
) -> Cell { ) -> Cell {
let cell = &mut *self; let cell = &mut *self;
@ -481,9 +480,8 @@ impl ResolvableCell for Packed<GridCell> {
Smart::Auto => cell.align(styles), Smart::Auto => cell.align(styles),
}); });
cell.push_inset(Smart::Custom( 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 } Cell { body: self.pack(), fill, colspan }
} }

View File

@ -5,7 +5,7 @@ use std::ops::{Add, Div, Mul, Neg};
use ecow::{eco_format, EcoString}; use ecow::{eco_format, EcoString};
use crate::diag::{At, Hint, SourceResult}; 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::layout::{Abs, Em};
use crate::syntax::Span; use crate::syntax::Span;
use crate::util::Numeric; use crate::util::Numeric;
@ -274,3 +274,9 @@ impl Resolve for Length {
self.abs + self.em.resolve(styles) self.abs + self.em.resolve(styles)
} }
} }
impl Fold for Length {
fn fold(self, _: Self) -> Self {
self
}
}

View File

@ -549,18 +549,11 @@ impl Margin {
} }
impl Fold for Margin { impl Fold for Margin {
type Output = Margin; fn fold(self, outer: Self) -> Self {
Margin {
fn fold(self, outer: Self::Output) -> Self::Output { sides: self.sides.fold(outer.sides),
let sides = two_sided: self.two_sided.fold(outer.two_sided),
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 }
} }
} }

View File

@ -259,19 +259,12 @@ where
} }
} }
impl Fold for Rel<Abs> { impl<T> Fold for Rel<T>
type Output = Self; where
T: Numeric + Fold,
fn fold(self, _: Self::Output) -> Self::Output { {
self fn fold(self, outer: Self) -> Self {
} Self { rel: self.rel, abs: self.abs.fold(outer.abs) }
}
impl Fold for Rel<Length> {
type Output = Self;
fn fold(self, _: Self::Output) -> Self::Output {
self
} }
} }

View File

@ -94,6 +94,16 @@ impl<T: Add> Sides<T> {
} }
} }
impl<T> Sides<Option<T>> {
/// Unwrap-or-default the individual sides.
pub fn unwrap_or_default(self) -> Sides<T>
where
T: Default,
{
self.map(Option::unwrap_or_default)
}
}
impl Sides<Rel<Abs>> { impl Sides<Rel<Abs>> {
/// Evaluate the sides relative to the given `size`. /// Evaluate the sides relative to the given `size`.
pub fn relative_to(self, size: Size) -> Sides<Abs> { pub fn relative_to(self, size: Size) -> Sides<Abs> {
@ -159,20 +169,21 @@ impl<T: Reflect> Reflect for Sides<Option<T>> {
} }
} }
impl<T> IntoValue for Sides<T> impl<T> IntoValue for Sides<Option<T>>
where where
T: PartialEq + IntoValue, T: PartialEq + IntoValue,
{ {
fn into_value(self) -> Value { fn into_value(self) -> Value {
if self.is_uniform() { 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 dict = Dict::new();
let mut handle = |key: &str, component: T| { let mut handle = |key: &str, component: Option<T>| {
let value = component.into_value(); if let Some(c) = component {
if value != Value::None { dict.insert(key.into(), c.into_value());
dict.insert(key.into(), value);
} }
}; };
@ -233,12 +244,13 @@ impl<T: Resolve> Resolve for Sides<T> {
} }
impl<T: Fold> Fold for Sides<Option<T>> { impl<T: Fold> Fold for Sides<Option<T>> {
type Output = Sides<T::Output>; fn fold(self, outer: Self) -> Self {
self.zip(outer).map(|(inner, outer)| match (inner, outer) {
fn fold(self, outer: Self::Output) -> Self::Output { (Some(inner), Some(outer)) => Some(inner.fold(outer)),
self.zip(outer).map(|(inner, outer)| match inner { // Usually, folding an inner `None` with an `outer` preferres the
Some(value) => value.fold(outer), // explicit `None`. However, here `None` means unspecified and thus
None => outer, // we want `outer`.
(inner, outer) => inner.or(outer),
}) })
} }
} }

View File

@ -97,7 +97,7 @@ pub struct CancelElem {
#[fold] #[fold]
#[default(Stroke { #[default(Stroke {
// Default stroke has 0.5pt for better visuals. // 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() ..Default::default()
})] })]
pub stroke: Stroke, pub stroke: Stroke,

View File

@ -425,11 +425,10 @@ fn layout_mat_body(
}; };
let (hline, vline, stroke) = match augment { let (hline, vline, stroke) = match augment {
Some(v) => { Some(augment) => {
// need to get stroke here for ownership // We need to get stroke here for ownership.
let stroke = v.stroke_or(default_stroke); let stroke = augment.stroke.unwrap_or_default().unwrap_or(default_stroke);
(augment.hline, augment.vline, stroke)
(v.hline, v.vline, stroke)
} }
_ => (AugmentOffsets::default(), AugmentOffsets::default(), default_stroke), _ => (AugmentOffsets::default(), AugmentOffsets::default(), default_stroke),
}; };
@ -593,11 +592,19 @@ pub struct Augment<T: Numeric = Length> {
pub stroke: Smart<Stroke<T>>, pub stroke: Smart<Stroke<T>>,
} }
impl Augment<Abs> { impl<T: Numeric + Fold> Fold for Augment<T> {
fn stroke_or(&self, fallback: FixedStroke) -> FixedStroke { fn fold(self, outer: Self) -> Self {
match &self.stroke { Self {
Smart::Custom(v) => v.clone().unwrap_or(fallback), stroke: match (self.stroke, outer.stroke) {
Smart::Auto => fallback, (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<Abs> {
type Output = Augment<Abs>;
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! { cast! {
Augment, Augment,
self => { self => {
@ -637,13 +629,11 @@ cast! {
return self.vline.0[0].into_value(); return self.vline.0[0].into_value();
} }
let d = dict! { dict! {
"hline" => self.hline.into_value(), "hline" => self.hline,
"vline" => self.vline.into_value(), "vline" => self.vline,
"stroke" => self.stroke.into_value() "stroke" => self.stroke,
}; }.into_value()
d.into_value()
}, },
v: isize => Augment { v: isize => Augment {
hline: AugmentOffsets::default(), hline: AugmentOffsets::default(),
@ -651,15 +641,15 @@ cast! {
stroke: Smart::Auto, stroke: Smart::Auto,
}, },
mut dict: Dict => { mut dict: Dict => {
// need the transpose for the defaults to work let mut take = |key| dict.take(key).ok().map(AugmentOffsets::from_value).transpose();
let hline = dict.take("hline").ok().map(AugmentOffsets::from_value) let hline = take("hline")?.unwrap_or_default();
.transpose().unwrap_or_default().unwrap_or_default(); let vline = take("vline")?.unwrap_or_default();
let vline = dict.take("vline").ok().map(AugmentOffsets::from_value) let stroke = dict.take("stroke")
.transpose().unwrap_or_default().unwrap_or_default(); .ok()
.map(Stroke::from_value)
let stroke = dict.take("stroke").ok().map(Stroke::from_value) .transpose()?
.transpose()?.map(Smart::Custom).unwrap_or(Smart::Auto); .map(Smart::Custom)
.unwrap_or(Smart::Auto);
Augment { hline, vline, stroke } Augment { hline, vline, stroke }
}, },
} }

View File

@ -323,7 +323,6 @@ impl LocalName for Packed<BibliographyElem> {
} }
/// A loaded bibliography. /// A loaded bibliography.
#[ty]
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct Bibliography { pub struct Bibliography {
map: Arc<IndexMap<PicoStr, hayagriva::Entry>>, map: Arc<IndexMap<PicoStr, hayagriva::Entry>>,
@ -422,12 +421,6 @@ impl Hash for Bibliography {
} }
} }
impl Repr for Bibliography {
fn repr(&self) -> EcoString {
"..".into()
}
}
/// Format a BibLaTeX loading error. /// Format a BibLaTeX loading error.
fn format_biblatex_error(path: &str, src: &str, errors: Vec<BibLaTeXError>) -> EcoString { fn format_biblatex_error(path: &str, src: &str, errors: Vec<BibLaTeXError>) -> EcoString {
let Some(error) = errors.first() else { let Some(error) = errors.first() else {

View File

@ -36,6 +36,6 @@ pub struct EmphElem {
impl Show for Packed<EmphElem> { impl Show for Packed<EmphElem> {
#[typst_macros::time(name = "emph", span = self.span())] #[typst_macros::time(name = "emph", span = self.span())]
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
Ok(self.body().clone().styled(TextElem::set_emph(ItalicToggle))) Ok(self.body().clone().styled(TextElem::set_emph(ItalicToggle(true))))
} }
} }

View File

@ -1,10 +1,10 @@
use std::str::FromStr; use std::str::FromStr;
use smallvec::{smallvec, SmallVec};
use crate::diag::{bail, SourceResult}; use crate::diag::{bail, SourceResult};
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{cast, elem, scope, Array, Content, Packed, Smart, StyleChain};
cast, elem, scope, Array, Content, Fold, Packed, Smart, StyleChain,
};
use crate::layout::{ use crate::layout::{
Alignment, Axes, BlockElem, Cell, CellGrid, Em, Fragment, GridLayouter, HAlignment, Alignment, Axes, BlockElem, Cell, CellGrid, Em, Fragment, GridLayouter, HAlignment,
LayoutMultiple, Length, Regions, Sizing, Spacing, VAlignment, LayoutMultiple, Length, Regions, Sizing, Spacing, VAlignment,
@ -199,7 +199,7 @@ pub struct EnumElem {
/// The numbers of parent items. /// The numbers of parent items.
#[internal] #[internal]
#[fold] #[fold]
parents: Parent, parents: SmallVec<[usize; 4]>,
} }
#[scope] #[scope]
@ -229,6 +229,8 @@ impl LayoutMultiple for Packed<EnumElem> {
let mut cells = vec![]; let mut cells = vec![];
let mut number = self.start(styles); let mut number = self.start(styles);
let mut parents = self.parents(styles); let mut parents = self.parents(styles);
parents.reverse();
let full = self.full(styles); let full = self.full(styles);
// Horizontally align based on the given respective parameter. // Horizontally align based on the given respective parameter.
@ -263,7 +265,7 @@ impl LayoutMultiple for Packed<EnumElem> {
cells.push(Cell::from(resolved)); cells.push(Cell::from(resolved));
cells.push(Cell::from(Content::empty())); cells.push(Cell::from(Content::empty()));
cells.push(Cell::from( 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); number = number.saturating_add(1);
} }
@ -309,21 +311,3 @@ cast! {
}, },
v: Content => v.unpack::<Self>().unwrap_or_else(Self::new), v: Content => v.unpack::<Self>().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<usize>;
fn fold(self, mut outer: Self::Output) -> Self::Output {
outer.push(self.0);
outer
}
}

View File

@ -150,7 +150,7 @@ impl LayoutMultiple for Packed<ListElem> {
.unwrap_or_else(|| *BlockElem::below_in(styles).amount()) .unwrap_or_else(|| *BlockElem::below_in(styles).amount())
}; };
let depth = self.depth(styles); let Depth(depth) = self.depth(styles);
let marker = self let marker = self
.marker(styles) .marker(styles)
.resolve(engine, depth)? .resolve(engine, depth)?
@ -162,8 +162,9 @@ impl LayoutMultiple for Packed<ListElem> {
cells.push(Cell::from(Content::empty())); cells.push(Cell::from(Content::empty()));
cells.push(Cell::from(marker.clone())); cells.push(Cell::from(marker.clone()));
cells.push(Cell::from(Content::empty())); cells.push(Cell::from(Content::empty()));
cells cells.push(Cell::from(
.push(Cell::from(item.body().clone().styled(ListElem::set_depth(Depth)))); item.body().clone().styled(ListElem::set_depth(Depth(1))),
));
} }
let stroke = None; let stroke = None;
@ -235,19 +236,11 @@ cast! {
v: Func => Self::Func(v), v: Func => Self::Func(v),
} }
#[derive(Debug, Clone, Copy, PartialEq, Hash)] #[derive(Debug, Default, Clone, Copy, PartialEq, Hash)]
struct Depth; struct Depth(usize);
cast! {
Depth,
self => Value::None,
_: Value => Self,
}
impl Fold for Depth { impl Fold for Depth {
type Output = usize; fn fold(self, outer: Self) -> Self {
Self(outer.0 + self.0)
fn fold(self, outer: Self::Output) -> Self::Output {
outer + 1
} }
} }

View File

@ -194,7 +194,7 @@ pub struct TableElem {
/// ) /// )
/// ``` /// ```
#[fold] #[fold]
#[default(Sides::splat(Abs::pt(5.0).into()))] #[default(Sides::splat(Some(Abs::pt(5.0).into())))]
pub inset: Sides<Option<Rel<Length>>>, pub inset: Sides<Option<Rel<Length>>>,
/// The contents of the table cells. /// The contents of the table cells.
@ -378,7 +378,7 @@ impl ResolvableCell for Packed<TableCell> {
y: usize, y: usize,
fill: &Option<Paint>, fill: &Option<Paint>,
align: Smart<Alignment>, align: Smart<Alignment>,
inset: Sides<Rel<Length>>, inset: Sides<Option<Rel<Length>>>,
styles: StyleChain, styles: StyleChain,
) -> Cell { ) -> Cell {
let cell = &mut *self; let cell = &mut *self;
@ -397,9 +397,8 @@ impl ResolvableCell for Packed<TableCell> {
Smart::Auto => cell.align(styles), Smart::Auto => cell.align(styles),
}); });
cell.push_inset(Smart::Custom( 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 } Cell { body: self.pack(), fill, colspan }
} }

View File

@ -1,13 +1,10 @@
use kurbo::{BezPath, Line, ParamCurve}; use kurbo::{BezPath, Line, ParamCurve};
use smallvec::smallvec;
use ttf_parser::{GlyphId, OutlineBuilder}; use ttf_parser::{GlyphId, OutlineBuilder};
use ecow::{eco_format, EcoString};
use crate::diag::SourceResult; use crate::diag::SourceResult;
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{elem, Content, Packed, Show, Smart, StyleChain};
elem, ty, Content, Fold, Packed, Repr, Show, Smart, StyleChain,
};
use crate::layout::{Abs, Em, Frame, FrameItem, Length, Point, Size}; use crate::layout::{Abs, Em, Frame, FrameItem, Length, Point, Size};
use crate::syntax::Span; use crate::syntax::Span;
use crate::text::{ use crate::text::{
@ -89,7 +86,7 @@ pub struct UnderlineElem {
impl Show for Packed<UnderlineElem> { impl Show for Packed<UnderlineElem> {
#[typst_macros::time(name = "underline", span = self.span())] #[typst_macros::time(name = "underline", span = self.span())]
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
Ok(self.body().clone().styled(TextElem::set_deco(Decoration { Ok(self.body().clone().styled(TextElem::set_deco(smallvec![Decoration {
line: DecoLine::Underline { line: DecoLine::Underline {
stroke: self.stroke(styles).unwrap_or_default(), stroke: self.stroke(styles).unwrap_or_default(),
offset: self.offset(styles), offset: self.offset(styles),
@ -97,7 +94,7 @@ impl Show for Packed<UnderlineElem> {
background: self.background(styles), background: self.background(styles),
}, },
extent: self.extent(styles), extent: self.extent(styles),
}))) }])))
} }
} }
@ -181,7 +178,7 @@ pub struct OverlineElem {
impl Show for Packed<OverlineElem> { impl Show for Packed<OverlineElem> {
#[typst_macros::time(name = "overline", span = self.span())] #[typst_macros::time(name = "overline", span = self.span())]
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
Ok(self.body().clone().styled(TextElem::set_deco(Decoration { Ok(self.body().clone().styled(TextElem::set_deco(smallvec![Decoration {
line: DecoLine::Overline { line: DecoLine::Overline {
stroke: self.stroke(styles).unwrap_or_default(), stroke: self.stroke(styles).unwrap_or_default(),
offset: self.offset(styles), offset: self.offset(styles),
@ -189,7 +186,7 @@ impl Show for Packed<OverlineElem> {
background: self.background(styles), background: self.background(styles),
}, },
extent: self.extent(styles), extent: self.extent(styles),
}))) }])))
} }
} }
@ -258,7 +255,7 @@ pub struct StrikeElem {
impl Show for Packed<StrikeElem> { impl Show for Packed<StrikeElem> {
#[typst_macros::time(name = "strike", span = self.span())] #[typst_macros::time(name = "strike", span = self.span())]
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
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. // Note that we do not support evade option for strikethrough.
line: DecoLine::Strikethrough { line: DecoLine::Strikethrough {
stroke: self.stroke(styles).unwrap_or_default(), stroke: self.stroke(styles).unwrap_or_default(),
@ -266,7 +263,7 @@ impl Show for Packed<StrikeElem> {
background: self.background(styles), background: self.background(styles),
}, },
extent: self.extent(styles), extent: self.extent(styles),
}))) }])))
} }
} }
@ -328,14 +325,14 @@ pub struct HighlightElem {
impl Show for Packed<HighlightElem> { impl Show for Packed<HighlightElem> {
#[typst_macros::time(name = "highlight", span = self.span())] #[typst_macros::time(name = "highlight", span = self.span())]
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
Ok(self.body().clone().styled(TextElem::set_deco(Decoration { Ok(self.body().clone().styled(TextElem::set_deco(smallvec![Decoration {
line: DecoLine::Highlight { line: DecoLine::Highlight {
fill: self.fill(styles), fill: self.fill(styles),
top_edge: self.top_edge(styles), top_edge: self.top_edge(styles),
bottom_edge: self.bottom_edge(styles), bottom_edge: self.bottom_edge(styles),
}, },
extent: self.extent(styles), extent: self.extent(styles),
}))) }])))
} }
} }
@ -343,28 +340,12 @@ impl Show for Packed<HighlightElem> {
/// ///
/// Can be positioned over, under, or on top of text, or highlight the text with /// Can be positioned over, under, or on top of text, or highlight the text with
/// a background. /// a background.
#[ty]
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Decoration { pub struct Decoration {
line: DecoLine, line: DecoLine,
extent: Abs, extent: Abs,
} }
impl Fold for Decoration {
type Output = Vec<Self>;
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. /// A kind of decorative line.
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
enum DecoLine { enum DecoLine {

View File

@ -32,6 +32,7 @@ use std::fmt::{self, Debug, Formatter};
use ecow::{eco_format, EcoString}; use ecow::{eco_format, EcoString};
use rustybuzz::{Feature, Tag}; use rustybuzz::{Feature, Tag};
use smallvec::SmallVec;
use ttf_parser::Rect; use ttf_parser::Rect;
use crate::diag::{bail, SourceResult, StrResult}; use crate::diag::{bail, SourceResult, StrResult};
@ -39,8 +40,9 @@ use crate::engine::Engine;
use crate::foundations::Packed; use crate::foundations::Packed;
use crate::foundations::{ use crate::foundations::{
cast, category, elem, Args, Array, Cast, Category, Construct, Content, Dict, Fold, 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::layout::{Abs, Axis, Dir, Length, Rel};
use crate::model::ParElem; use crate::model::ParElem;
use crate::syntax::Spanned; use crate::syntax::Spanned;
@ -214,7 +216,8 @@ pub struct TextElem {
/// ``` /// ```
#[parse(args.named_or_find("size")?)] #[parse(args.named_or_find("size")?)]
#[fold] #[fold]
#[default(Abs::pt(11.0))] #[default(TextSize(Abs::pt(11.0).into()))]
#[resolve]
#[ghost] #[ghost]
pub size: TextSize, pub size: TextSize,
@ -628,7 +631,7 @@ pub struct TextElem {
/// Whether the font style should be inverted. /// Whether the font style should be inverted.
#[internal] #[internal]
#[fold] #[fold]
#[default(false)] #[default(ItalicToggle(false))]
#[ghost] #[ghost]
pub emph: ItalicToggle, pub emph: ItalicToggle,
@ -636,7 +639,7 @@ pub struct TextElem {
#[internal] #[internal]
#[fold] #[fold]
#[ghost] #[ghost]
pub deco: Decoration, pub deco: SmallVec<[Decoration; 1]>,
/// A case transformation that should be applied to the text. /// A case transformation that should be applied to the text.
#[internal] #[internal]
@ -763,12 +766,12 @@ pub(crate) fn variant(styles: StyleChain) -> FontVariant {
TextElem::stretch_in(styles), TextElem::stretch_in(styles),
); );
let delta = TextElem::delta_in(styles); let WeightDelta(delta) = TextElem::delta_in(styles);
variant.weight = variant variant.weight = variant
.weight .weight
.thicken(delta.clamp(i16::MIN as i64, i16::MAX as i64) as i16); .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 { variant.style = match variant.style {
FontStyle::Normal => FontStyle::Italic, FontStyle::Normal => FontStyle::Italic,
FontStyle::Italic => FontStyle::Normal, FontStyle::Italic => FontStyle::Normal,
@ -784,10 +787,20 @@ pub(crate) fn variant(styles: StyleChain) -> FontVariant {
pub struct TextSize(pub Length); pub struct TextSize(pub Length);
impl Fold for TextSize { 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; type Output = Abs;
fn fold(self, outer: Self::Output) -> Self::Output { fn resolve(self, styles: StyleChain) -> Self::Output {
self.0.em.at(outer) + self.0.abs self.0.resolve(styles)
} }
} }
@ -1056,11 +1069,8 @@ cast! {
} }
impl Fold for FontFeatures { impl Fold for FontFeatures {
type Output = Self; fn fold(self, outer: Self) -> Self {
Self(self.0.fold(outer.0))
fn fold(mut self, outer: Self::Output) -> Self::Output {
self.0.extend(outer.0);
self
} }
} }
@ -1133,36 +1143,20 @@ pub(crate) fn features(styles: StyleChain) -> Vec<Feature> {
/// A toggle that turns on and off alternatingly if folded. /// A toggle that turns on and off alternatingly if folded.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct ItalicToggle; pub struct ItalicToggle(pub bool);
cast! {
ItalicToggle,
self => Value::None,
_: Value => Self,
}
impl Fold for ItalicToggle { impl Fold for ItalicToggle {
type Output = bool; fn fold(self, outer: Self) -> Self {
Self(self.0 ^ outer.0)
fn fold(self, outer: Self::Output) -> Self::Output {
!outer
} }
} }
/// A delta that is summed up when folded. /// 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); pub struct WeightDelta(pub i64);
cast! {
WeightDelta,
self => self.0.into_value(),
v: i64 => Self(v),
}
impl Fold for WeightDelta { impl Fold for WeightDelta {
type Output = i64; fn fold(self, outer: Self) -> Self {
Self(outer.0 + self.0)
fn fold(self, outer: Self::Output) -> Self::Output {
outer + self.0
} }
} }

View File

@ -658,11 +658,8 @@ cast! {
} }
impl Fold for SyntaxPaths { impl Fold for SyntaxPaths {
type Output = Self; fn fold(self, outer: Self) -> Self {
Self(self.0.fold(outer.0))
fn fold(mut self, outer: Self::Output) -> Self::Output {
self.0.extend(outer.0);
self
} }
} }

View File

@ -114,7 +114,7 @@ pub struct RectElem {
/// See the [box's documentation]($box.outset) for more details. /// See the [box's documentation]($box.outset) for more details.
#[resolve] #[resolve]
#[fold] #[fold]
#[default(Sides::splat(Abs::pt(5.0).into()))] #[default(Sides::splat(Some(Abs::pt(5.0).into())))]
pub inset: Sides<Option<Rel<Length>>>, pub inset: Sides<Option<Rel<Length>>>,
/// How much to expand the rectangle's size without affecting the layout. /// 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. /// [box's documentation]($box.inset) for more details.
#[resolve] #[resolve]
#[fold] #[fold]
#[default(Sides::splat(Abs::pt(5.0).into()))] #[default(Sides::splat(Some(Abs::pt(5.0).into())))]
pub inset: Sides<Option<Rel<Length>>>, pub inset: Sides<Option<Rel<Length>>>,
/// How much to expand the square's size without affecting the layout. See /// 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. /// [box's documentation]($box.inset) for more details.
#[resolve] #[resolve]
#[fold] #[fold]
#[default(Sides::splat(Abs::pt(5.0).into()))] #[default(Sides::splat(Some(Abs::pt(5.0).into())))]
pub inset: Sides<Option<Rel<Length>>>, pub inset: Sides<Option<Rel<Length>>>,
/// How much to expand the ellipse's size without affecting the layout. See /// How much to expand the ellipse's size without affecting the layout. See
@ -331,10 +331,10 @@ impl LayoutSingle for Packed<EllipseElem> {
&self.body(styles), &self.body(styles),
Axes::new(self.width(styles), self.height(styles)), Axes::new(self.width(styles), self.height(styles)),
self.fill(styles), self.fill(styles),
self.stroke(styles).map(Sides::splat), self.stroke(styles).map(|s| Sides::splat(Some(s))),
self.inset(styles), self.inset(styles),
self.outset(styles), self.outset(styles),
Corners::splat(Rel::zero()), Corners::splat(None),
self.span(), self.span(),
) )
} }
@ -403,7 +403,7 @@ pub struct CircleElem {
/// [box's documentation]($box.inset) for more details. /// [box's documentation]($box.inset) for more details.
#[resolve] #[resolve]
#[fold] #[fold]
#[default(Sides::splat(Abs::pt(5.0).into()))] #[default(Sides::splat(Some(Abs::pt(5.0).into())))]
pub inset: Sides<Option<Rel<Length>>>, pub inset: Sides<Option<Rel<Length>>>,
/// How much to expand the circle's size without affecting the layout. See /// How much to expand the circle's size without affecting the layout. See
@ -434,10 +434,10 @@ impl LayoutSingle for Packed<CircleElem> {
&self.body(styles), &self.body(styles),
Axes::new(self.width(styles), self.height(styles)), Axes::new(self.width(styles), self.height(styles)),
self.fill(styles), self.fill(styles),
self.stroke(styles).map(Sides::splat), self.stroke(styles).map(|s| Sides::splat(Some(s))),
self.inset(styles), self.inset(styles),
self.outset(styles), self.outset(styles),
Corners::splat(Rel::zero()), Corners::splat(None),
self.span(), self.span(),
) )
} }
@ -453,18 +453,21 @@ fn layout(
body: &Option<Content>, body: &Option<Content>,
sizing: Axes<Smart<Rel<Length>>>, sizing: Axes<Smart<Rel<Length>>>,
fill: Option<Paint>, fill: Option<Paint>,
stroke: Smart<Sides<Option<Stroke<Abs>>>>, stroke: Smart<Sides<Option<Option<Stroke<Abs>>>>>,
mut inset: Sides<Rel<Abs>>, inset: Sides<Option<Rel<Abs>>>,
outset: Sides<Rel<Abs>>, outset: Sides<Option<Rel<Abs>>>,
radius: Corners<Rel<Abs>>, radius: Corners<Option<Rel<Abs>>>,
span: Span, span: Span,
) -> SourceResult<Frame> { ) -> SourceResult<Frame> {
let resolved = sizing let resolved = sizing
.zip_map(regions.base(), |s, r| s.map(|v| v.resolve(styles).relative_to(r))); .zip_map(regions.base(), |s, r| s.map(|v| v.resolve(styles).relative_to(r)));
let mut frame; let mut frame;
let mut inset = inset.unwrap_or_default();
if let Some(child) = body { if let Some(child) = body {
let region = resolved.unwrap_or(regions.base()); let region = resolved.unwrap_or(regions.base());
if kind.is_round() { if kind.is_round() {
inset = inset.map(|side| side + Ratio::new(0.5 - SQRT_2 / 4.0)); inset = inset.map(|side| side + Ratio::new(0.5 - SQRT_2 / 4.0));
} }
@ -507,19 +510,27 @@ fn layout(
let stroke = match stroke { let stroke = match stroke {
Smart::Auto if fill.is_none() => Sides::splat(Some(FixedStroke::default())), Smart::Auto if fill.is_none() => Sides::splat(Some(FixedStroke::default())),
Smart::Auto => Sides::splat(None), 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. // Add fill and/or stroke.
if fill.is_some() || stroke.iter().any(Option::is_some) { if fill.is_some() || stroke.iter().any(Option::is_some) {
if kind.is_round() { 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 size = frame.size() + outset.sum_by_axis();
let pos = Point::new(-outset.left, -outset.top); let pos = Point::new(-outset.left, -outset.top);
let shape = ellipse(size, fill, stroke.left); let shape = ellipse(size, fill, stroke.left);
frame.prepend(pos, FrameItem::Shape(shape, span)); frame.prepend(pos, FrameItem::Shape(shape, span));
} else { } 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,
);
} }
} }

View File

@ -330,6 +330,19 @@ impl<T: Numeric + Repr> Repr for Stroke<T> {
} }
} }
impl<T: Numeric + Fold> Fold for Stroke<T> {
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 { impl Resolve for Stroke {
type Output = Stroke<Abs>; type Output = Stroke<Abs>;
@ -345,21 +358,6 @@ impl Resolve for Stroke {
} }
} }
impl Fold for Stroke<Abs> {
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! { cast! {
type Stroke, type Stroke,
thickness: Length => Self { thickness: Length => Self {