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:
/// - Fields that are not synthesized.
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 };
}
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>(
<Self as #foundations::NativeElement>::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)
}
}

View File

@ -239,14 +239,14 @@ impl<T: Resolve> Resolve for Smart<T> {
}
}
impl<T> Fold for Smart<T>
where
T: Fold,
T::Output: Default,
{
type Output = Smart<T::Output>;
fn fold(self, outer: Self::Output) -> Self::Output {
self.map(|inner| inner.fold(outer.unwrap_or_default()))
impl<T: Fold> Fold for Smart<T> {
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,
}
}
}

View File

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

View File

@ -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<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,
/// returning a borrowed value if possible.
pub fn get_borrowed<T: Clone>(
/// returning a borrowed value.
pub fn get_ref<T: 'static>(
self,
func: Element,
id: u8,
inherent: Option<&'a T>,
default: &'static Lazy<T>,
default: impl Fn() -> &'a T,
) -> &'a T {
self.properties::<T>(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<T: Clone>(
/// Cast the first value for the given property in the chain, taking
/// `Fold` implementations into account.
pub fn get_folded<T: Fold + Clone + 'static>(
self,
func: Element,
id: u8,
inherent: Option<&T>,
default: &'static Lazy<T>,
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<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>(
mut values: impl Iterator<Item = T>,
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::<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.
pub fn recipes(self) -> impl Iterator<Item = &'a Recipe> {
self.entries().filter_map(Style::recipe)
@ -925,38 +887,36 @@ impl<T: Resolve> Resolve for Option<T> {
/// #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<T> Fold for Option<T>
where
T: Fold,
T::Output: Default,
{
type Output = Option<T::Output>;
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<T: Fold> Fold for Option<T> {
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> {
type Output = Vec<T>;
fn fold(mut self, outer: Self::Output) -> Self::Output {
fn fold(mut self, outer: Self) -> Self {
self.extend(outer);
self
}
}
impl<T, const N: usize> 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
}

View File

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

View File

@ -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<T> Axes<T> {
{
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> {
@ -198,16 +190,6 @@ cast! {
"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>> {
/// Unwrap the individual fields.
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))
}
}
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.
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<BoxElem> {
// 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<BlockElem> {
) -> SourceResult<Fragment> {
// 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<BlockElem> {
// 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<BlockElem> {
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(),

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> {
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
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<T>| {
if let Some(c) = component {
dict.insert(key.into(), c.into_value());
}
};
@ -228,12 +239,13 @@ impl<T: Resolve> Resolve for Corners<T> {
}
impl<T: Fold> Fold for Corners<Option<T>> {
type Output = Corners<T::Output>;
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),
})
}
}

View File

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

View File

@ -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<Option<Rel<Length>>>,
/// The contents of the grid cells.
@ -462,7 +461,7 @@ impl ResolvableCell for Packed<GridCell> {
y: usize,
fill: &Option<Paint>,
align: Smart<Alignment>,
inset: Sides<Rel<Length>>,
inset: Sides<Option<Rel<Length>>>,
styles: StyleChain,
) -> Cell {
let cell = &mut *self;
@ -481,9 +480,8 @@ impl ResolvableCell for Packed<GridCell> {
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 }
}

View File

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

View File

@ -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),
}
}
}

View File

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

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>> {
/// Evaluate the sides relative to the given `size`.
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
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<T>| {
if let Some(c) = component {
dict.insert(key.into(), c.into_value());
}
};
@ -233,12 +244,13 @@ impl<T: Resolve> Resolve for Sides<T> {
}
impl<T: Fold> Fold for Sides<Option<T>> {
type Output = Sides<T::Output>;
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),
})
}
}

View File

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

View File

@ -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<T: Numeric = Length> {
pub stroke: Smart<Stroke<T>>,
}
impl Augment<Abs> {
fn stroke_or(&self, fallback: FixedStroke) -> FixedStroke {
match &self.stroke {
Smart::Custom(v) => v.clone().unwrap_or(fallback),
Smart::Auto => fallback,
impl<T: Numeric + Fold> Fold for Augment<T> {
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<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! {
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 }
},
}

View File

@ -323,7 +323,6 @@ impl LocalName for Packed<BibliographyElem> {
}
/// A loaded bibliography.
#[ty]
#[derive(Clone, PartialEq)]
pub struct Bibliography {
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.
fn format_biblatex_error(path: &str, src: &str, errors: Vec<BibLaTeXError>) -> EcoString {
let Some(error) = errors.first() else {

View File

@ -36,6 +36,6 @@ pub struct EmphElem {
impl Show for Packed<EmphElem> {
#[typst_macros::time(name = "emph", span = self.span())]
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 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<EnumElem> {
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<EnumElem> {
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::<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())
};
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<ListElem> {
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)
}
}

View File

@ -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<Option<Rel<Length>>>,
/// The contents of the table cells.
@ -378,7 +378,7 @@ impl ResolvableCell for Packed<TableCell> {
y: usize,
fill: &Option<Paint>,
align: Smart<Alignment>,
inset: Sides<Rel<Length>>,
inset: Sides<Option<Rel<Length>>>,
styles: StyleChain,
) -> Cell {
let cell = &mut *self;
@ -397,9 +397,8 @@ impl ResolvableCell for Packed<TableCell> {
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 }
}

View File

@ -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<UnderlineElem> {
#[typst_macros::time(name = "underline", span = self.span())]
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 {
stroke: self.stroke(styles).unwrap_or_default(),
offset: self.offset(styles),
@ -97,7 +94,7 @@ impl Show for Packed<UnderlineElem> {
background: self.background(styles),
},
extent: self.extent(styles),
})))
}])))
}
}
@ -181,7 +178,7 @@ pub struct OverlineElem {
impl Show for Packed<OverlineElem> {
#[typst_macros::time(name = "overline", span = self.span())]
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 {
stroke: self.stroke(styles).unwrap_or_default(),
offset: self.offset(styles),
@ -189,7 +186,7 @@ impl Show for Packed<OverlineElem> {
background: self.background(styles),
},
extent: self.extent(styles),
})))
}])))
}
}
@ -258,7 +255,7 @@ pub struct StrikeElem {
impl Show for Packed<StrikeElem> {
#[typst_macros::time(name = "strike", span = self.span())]
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.
line: DecoLine::Strikethrough {
stroke: self.stroke(styles).unwrap_or_default(),
@ -266,7 +263,7 @@ impl Show for Packed<StrikeElem> {
background: self.background(styles),
},
extent: self.extent(styles),
})))
}])))
}
}
@ -328,14 +325,14 @@ pub struct HighlightElem {
impl Show for Packed<HighlightElem> {
#[typst_macros::time(name = "highlight", span = self.span())]
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 {
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<HighlightElem> {
///
/// 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<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.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
enum DecoLine {

View File

@ -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<Feature> {
/// 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)
}
}

View File

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

View File

@ -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<Option<Rel<Length>>>,
/// 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<Option<Rel<Length>>>,
/// 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<Option<Rel<Length>>>,
/// 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),
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<Option<Rel<Length>>>,
/// 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),
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<Content>,
sizing: Axes<Smart<Rel<Length>>>,
fill: Option<Paint>,
stroke: Smart<Sides<Option<Stroke<Abs>>>>,
mut inset: Sides<Rel<Abs>>,
outset: Sides<Rel<Abs>>,
radius: Corners<Rel<Abs>>,
stroke: Smart<Sides<Option<Option<Stroke<Abs>>>>>,
inset: Sides<Option<Rel<Abs>>>,
outset: Sides<Option<Rel<Abs>>>,
radius: Corners<Option<Rel<Abs>>>,
span: Span,
) -> SourceResult<Frame> {
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,
);
}
}

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 {
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! {
type Stroke,
thickness: Length => Self {