From 29eb13ca6214461a4b0deb63d589cd39ad6d41c2 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Fri, 8 Apr 2022 17:08:30 +0200 Subject: [PATCH] Sum color and length into stroke --- macros/src/lib.rs | 26 +++-- src/eval/layout.rs | 8 +- src/eval/ops.rs | 45 ++++---- src/eval/raw.rs | 134 ++++++++++++++++++++--- src/eval/styles.rs | 44 ++++++-- src/eval/value.rs | 70 +++++++++++- src/export/pdf.rs | 6 +- src/export/render.rs | 4 +- src/frame.rs | 43 ++++---- src/geom/paint.rs | 28 ++++- src/library/graphics/line.rs | 17 +-- src/library/graphics/shape.rs | 15 +-- src/library/mod.rs | 62 ----------- src/library/prelude.rs | 2 +- src/library/structure/table.rs | 10 +- src/library/text/deco.rs | 27 ++--- tests/ref/code/repr.png | Bin 28698 -> 32209 bytes tests/ref/graphics/shape-fill-stroke.png | Bin 2893 -> 2997 bytes tests/ref/structure/table.png | Bin 1566 -> 4295 bytes tests/ref/text/deco.png | Bin 29421 -> 31937 bytes tests/typ/code/repr.typ | 34 +++--- tests/typ/graphics/line.typ | 2 +- tests/typ/graphics/shape-circle.typ | 2 +- tests/typ/graphics/shape-ellipse.typ | 2 +- tests/typ/graphics/shape-fill-stroke.typ | 24 +++- tests/typ/graphics/shape-rect.typ | 3 +- tests/typ/structure/table.typ | 9 +- tests/typ/text/deco.typ | 12 +- 28 files changed, 399 insertions(+), 230 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 429c5b09b..dbea1f831 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -155,6 +155,8 @@ fn process_const( let value_ty = &item.ty; let output_ty = if property.referenced { parse_quote!(&'a #value_ty) + } else if property.fold && property.resolve { + parse_quote!(<<#value_ty as eval::Resolve>::Output as eval::Fold>::Output) } else if property.fold { parse_quote!(<#value_ty as eval::Fold>::Output) } else if property.resolve { @@ -190,10 +192,13 @@ fn process_const( &*LAZY }) }; - } else if property.fold { + } else if property.resolve && property.fold { get = quote! { match values.next().cloned() { - Some(inner) => eval::Fold::fold(inner, Self::get(chain, values)), + Some(value) => eval::Fold::fold( + eval::Resolve::resolve(value, chain), + Self::get(chain, values), + ), None => #default, } }; @@ -202,6 +207,13 @@ fn process_const( let value = values.next().cloned().unwrap_or(#default); eval::Resolve::resolve(value, chain) }; + } else if property.fold { + get = quote! { + match values.next().cloned() { + Some(value) => eval::Fold::fold(value, Self::get(chain, values)), + None => #default, + } + }; } else { get = quote! { values.next().copied().unwrap_or(#default) @@ -267,8 +279,8 @@ struct Property { referenced: bool, shorthand: bool, variadic: bool, - fold: bool, resolve: bool, + fold: bool, } /// Parse a style property attribute. @@ -279,8 +291,8 @@ fn parse_property(item: &mut syn::ImplItemConst) -> Result { referenced: false, shorthand: false, variadic: false, - fold: false, resolve: false, + fold: false, }; if let Some(idx) = item @@ -296,8 +308,8 @@ fn parse_property(item: &mut syn::ImplItemConst) -> Result { "shorthand" => property.shorthand = true, "referenced" => property.referenced = true, "variadic" => property.variadic = true, - "fold" => property.fold = true, "resolve" => property.resolve = true, + "fold" => property.fold = true, _ => return Err(Error::new(ident.span(), "invalid attribute")), }, TokenTree::Punct(_) => {} @@ -314,10 +326,10 @@ fn parse_property(item: &mut syn::ImplItemConst) -> Result { )); } - if property.referenced as u8 + property.fold as u8 + property.resolve as u8 > 1 { + if property.referenced && (property.fold || property.resolve) { return Err(Error::new( span, - "referenced, fold and resolve are mutually exclusive", + "referenced is mutually exclusive with fold and resolve", )); } diff --git a/src/eval/layout.rs b/src/eval/layout.rs index f92a31f5f..117c269a7 100644 --- a/src/eval/layout.rs +++ b/src/eval/layout.rs @@ -7,8 +7,8 @@ use std::sync::Arc; use super::{Barrier, RawAlign, RawLength, Resolve, StyleChain}; use crate::diag::TypResult; -use crate::frame::{Element, Frame, Geometry, Shape, Stroke}; -use crate::geom::{Align, Length, Paint, Point, Relative, Sides, Size, Spec}; +use crate::frame::{Element, Frame, Geometry}; +use crate::geom::{Align, Length, Paint, Point, Relative, Sides, Size, Spec, Stroke}; use crate::library::graphics::MoveNode; use crate::library::layout::{AlignNode, PadNode}; use crate::util::Prehashed; @@ -349,7 +349,7 @@ impl Layout for FillNode { ) -> TypResult>> { let mut frames = self.child.layout(ctx, regions, styles)?; for frame in &mut frames { - let shape = Shape::filled(Geometry::Rect(frame.size), self.fill); + let shape = Geometry::Rect(frame.size).filled(self.fill); Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape)); } Ok(frames) @@ -374,7 +374,7 @@ impl Layout for StrokeNode { ) -> TypResult>> { let mut frames = self.child.layout(ctx, regions, styles)?; for frame in &mut frames { - let shape = Shape::stroked(Geometry::Rect(frame.size), self.stroke); + let shape = Geometry::Rect(frame.size).stroked(self.stroke); Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape)); } Ok(frames) diff --git a/src/eval/ops.rs b/src/eval/ops.rs index 898029495..4796e042e 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering; -use super::{Dynamic, RawAlign, StrExt, Value}; +use super::{Dynamic, RawAlign, RawStroke, Smart, StrExt, Value}; use crate::diag::StrResult; use crate::geom::{Numeric, Spec, SpecAxis}; use Value::*; @@ -90,25 +90,32 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult { (Array(a), Array(b)) => Array(a + b), (Dict(a), Dict(b)) => Dict(a + b), - (a, b) => { - if let (Dyn(a), Dyn(b)) = (&a, &b) { - // 1D alignments can be summed into 2D alignments. - if let (Some(&a), Some(&b)) = - (a.downcast::(), b.downcast::()) - { - return if a.axis() != b.axis() { - Ok(Dyn(Dynamic::new(match a.axis() { - SpecAxis::Horizontal => Spec { x: a, y: b }, - SpecAxis::Vertical => Spec { x: b, y: a }, - }))) - } else { - Err(format!("cannot add two {:?} alignments", a.axis())) - }; - } - } - - mismatch!("cannot add {} and {}", a, b); + (Color(color), Length(thickness)) | (Length(thickness), Color(color)) => { + Dyn(Dynamic::new(RawStroke { + paint: Smart::Custom(color.into()), + thickness: Smart::Custom(thickness), + })) } + + (Dyn(a), Dyn(b)) => { + // 1D alignments can be summed into 2D alignments. + if let (Some(&a), Some(&b)) = + (a.downcast::(), b.downcast::()) + { + if a.axis() != b.axis() { + Dyn(Dynamic::new(match a.axis() { + SpecAxis::Horizontal => Spec { x: a, y: b }, + SpecAxis::Vertical => Spec { x: b, y: a }, + })) + } else { + return Err(format!("cannot add two {:?} alignments", a.axis())); + } + } else { + mismatch!("cannot add {} and {}", a, b); + } + } + + (a, b) => mismatch!("cannot add {} and {}", a, b), }) } diff --git a/src/eval/raw.rs b/src/eval/raw.rs index 622a0562c..b0f46fc94 100644 --- a/src/eval/raw.rs +++ b/src/eval/raw.rs @@ -1,8 +1,11 @@ +use std::cmp::Ordering; use std::fmt::{self, Debug, Formatter}; use std::ops::{Add, Div, Mul, Neg}; -use super::{Resolve, StyleChain}; -use crate::geom::{Align, Em, Length, Numeric, Relative, SpecAxis}; +use super::{Fold, Resolve, Smart, StyleChain, Value}; +use crate::geom::{ + Align, Em, Get, Length, Numeric, Paint, Relative, Spec, SpecAxis, Stroke, +}; use crate::library::text::{ParNode, TextNode}; /// The unresolved alignment representation. @@ -49,6 +52,101 @@ impl Debug for RawAlign { } } +dynamic! { + RawAlign: "alignment", +} + +dynamic! { + Spec: "2d alignment", +} + +castable! { + Spec>, + Expected: "1d or 2d alignment", + @align: RawAlign => { + let mut aligns = Spec::default(); + aligns.set(align.axis(), Some(*align)); + aligns + }, + @aligns: Spec => aligns.map(Some), +} + +/// The unresolved stroke representation. +/// +/// In this representation, both fields are optional so that you can pass either +/// just a paint (`red`), just a thickness (`0.1em`) or both (`2pt + red`) where +/// this is expected. +#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] +pub struct RawStroke { + /// The stroke's paint. + pub paint: Smart, + /// The stroke's thickness. + pub thickness: Smart, +} + +impl RawStroke { + /// Unpack the stroke, filling missing fields with `default`. + pub fn unwrap_or(self, default: Stroke) -> Stroke { + Stroke { + paint: self.paint.unwrap_or(default.paint), + thickness: self.thickness.unwrap_or(default.thickness), + } + } + + /// Unpack the stroke, filling missing fields with the default values. + pub fn unwrap_or_default(self) -> Stroke { + self.unwrap_or(Stroke::default()) + } +} + +impl Resolve for RawStroke { + type Output = RawStroke; + + fn resolve(self, styles: StyleChain) -> Self::Output { + RawStroke { + paint: self.paint, + thickness: self.thickness.resolve(styles), + } + } +} + +// This faciliates RawStroke => Stroke. +impl Fold for RawStroke { + type Output = Self; + + fn fold(self, outer: Self::Output) -> Self::Output { + Self { + paint: self.paint.or(outer.paint), + thickness: self.thickness.or(outer.thickness), + } + } +} + +impl Debug for RawStroke { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match (self.paint, &self.thickness) { + (Smart::Custom(paint), Smart::Custom(thickness)) => { + write!(f, "{thickness:?} + {paint:?}") + } + (Smart::Custom(paint), Smart::Auto) => paint.fmt(f), + (Smart::Auto, Smart::Custom(thickness)) => thickness.fmt(f), + (Smart::Auto, Smart::Auto) => f.pad(""), + } + } +} + +dynamic! { + RawStroke: "stroke", + Value::Length(thickness) => Self { + paint: Smart::Auto, + thickness: Smart::Custom(thickness), + }, + Value::Color(color) => Self { + paint: Smart::Custom(color.into()), + thickness: Smart::Auto, + }, +} + /// The unresolved length representation. /// /// Currently supports absolute and em units, but support could quite easily be @@ -56,7 +154,7 @@ impl Debug for RawAlign { /// Probably, it would be a good idea to then move to an enum representation /// that has a small footprint and allocates for the rare case that units are /// mixed. -#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] pub struct RawLength { /// The absolute part. pub length: Length, @@ -101,6 +199,26 @@ impl Resolve for RawLength { } } +impl Numeric for RawLength { + fn zero() -> Self { + Self::zero() + } + + fn is_finite(self) -> bool { + self.length.is_finite() && self.em.is_finite() + } +} + +impl PartialOrd for RawLength { + fn partial_cmp(&self, other: &Self) -> Option { + if self.em.is_zero() && other.em.is_zero() { + self.length.partial_cmp(&other.length) + } else { + None + } + } +} + impl From for RawLength { fn from(length: Length) -> Self { Self { length, em: Em::zero() } @@ -119,16 +237,6 @@ impl From for Relative { } } -impl Numeric for RawLength { - fn zero() -> Self { - Self::zero() - } - - fn is_finite(self) -> bool { - self.length.is_finite() && self.em.is_finite() - } -} - impl Neg for RawLength { type Output = Self; diff --git a/src/eval/styles.rs b/src/eval/styles.rs index 575518f59..71293f40d 100644 --- a/src/eval/styles.rs +++ b/src/eval/styles.rs @@ -287,15 +287,6 @@ pub trait Key<'a>: 'static { ) -> Self::Output; } -/// A property that is folded to determine its final value. -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; -} - /// A property that is resolved with other properties from the style chain. pub trait Resolve { /// The type of the resolved output. @@ -354,6 +345,39 @@ where } } +/// A property that is folded to determine its final value. +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; +} + +impl Fold for Option +where + T: Fold, + T::Output: Default, +{ + type Output = Option; + + fn fold(self, outer: Self::Output) -> Self::Output { + self.map(|inner| inner.fold(outer.unwrap_or_default())) + } +} + +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())) + } +} + /// A show rule recipe. #[derive(Clone, PartialEq, Hash)] struct Recipe { @@ -472,7 +496,7 @@ impl<'a> StyleChain<'a> { /// Get the output value of a style property. /// /// Returns the property's default value if no map in the chain contains an - /// entry for it. Also takes care of folding and resolving and returns + /// entry for it. Also takes care of resolving and folding and returns /// references where applicable. pub fn get>(self, key: K) -> K::Output { K::get(self, self.values(key)) diff --git a/src/eval/value.rs b/src/eval/value.rs index 1851cf281..cc312c5a6 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -2,11 +2,16 @@ use std::any::Any; use std::cmp::Ordering; use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; +use std::num::NonZeroUsize; use std::sync::Arc; -use super::{ops, Args, Array, Content, Context, Dict, Func, Layout, RawLength, StrExt}; +use super::{ + ops, Args, Array, Content, Context, Dict, Func, Layout, LayoutNode, RawLength, StrExt, +}; use crate::diag::{with_alternative, At, StrResult, TypResult}; -use crate::geom::{Angle, Color, Em, Fraction, Length, Ratio, Relative, RgbaColor}; +use crate::geom::{ + Angle, Color, Dir, Em, Fraction, Length, Paint, Ratio, Relative, RgbaColor, +}; use crate::library::text::RawNode; use crate::syntax::{Span, Spanned}; use crate::util::EcoString; @@ -526,7 +531,7 @@ macro_rules! castable { $(@$dyn_in:ident: $dyn_type:ty => $dyn_out:expr,)* ) => { impl $crate::eval::Cast<$crate::eval::Value> for $type { - fn is(value: &Value) -> bool { + fn is(value: &$crate::eval::Value) -> bool { #[allow(unused_variables)] match value { $($pattern => true,)* @@ -637,6 +642,14 @@ impl Smart { } } + /// Keeps `self` if it contains a custom value, otherwise returns `other`. + pub fn or(self, other: Smart) -> Self { + match self { + Self::Custom(x) => Self::Custom(x), + Self::Auto => other, + } + } + /// Returns the contained custom value or a provided default value. pub fn unwrap_or(self, default: T) -> T { match self { @@ -655,6 +668,14 @@ impl Smart { Self::Custom(x) => x, } } + + /// Returns the contained custom value or the default value. + pub fn unwrap_or_default(self) -> T + where + T: Default, + { + self.unwrap_or_else(T::default) + } } impl Default for Smart { @@ -678,6 +699,49 @@ impl Cast for Smart { } } +dynamic! { + Dir: "direction", +} + +castable! { + usize, + Expected: "non-negative integer", + Value::Int(int) => int.try_into().map_err(|_| { + if int < 0 { + "must be at least zero" + } else { + "number too large" + } + })?, +} + +castable! { + NonZeroUsize, + Expected: "positive integer", + Value::Int(int) => Value::Int(int) + .cast::()? + .try_into() + .map_err(|_| "must be positive")?, +} + +castable! { + Paint, + Expected: "color", + Value::Color(color) => Paint::Solid(color), +} + +castable! { + String, + Expected: "string", + Value::Str(string) => string.into(), +} + +castable! { + LayoutNode, + Expected: "content", + Value::Content(content) => content.pack(), +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/export/pdf.rs b/src/export/pdf.rs index 95d20c518..067eb2775 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -16,8 +16,10 @@ use ttf_parser::{name_id, GlyphId, Tag}; use super::subset::subset; use crate::font::{find_name, FaceId, FontStore}; -use crate::frame::{Element, Frame, Geometry, Group, Shape, Stroke, Text}; -use crate::geom::{self, Color, Em, Length, Numeric, Paint, Point, Size, Transform}; +use crate::frame::{Element, Frame, Geometry, Group, Shape, Text}; +use crate::geom::{ + self, Color, Em, Length, Numeric, Paint, Point, Size, Stroke, Transform, +}; use crate::image::{Image, ImageId, ImageStore, RasterImage}; use crate::Context; diff --git a/src/export/render.rs b/src/export/render.rs index d6f82121d..c3b92d315 100644 --- a/src/export/render.rs +++ b/src/export/render.rs @@ -7,8 +7,8 @@ use tiny_skia as sk; use ttf_parser::{GlyphId, OutlineBuilder}; use usvg::FitTo; -use crate::frame::{Element, Frame, Geometry, Group, Shape, Stroke, Text}; -use crate::geom::{self, Length, Paint, PathElement, Size, Transform}; +use crate::frame::{Element, Frame, Geometry, Group, Shape, Text}; +use crate::geom::{self, Length, Paint, PathElement, Size, Stroke, Transform}; use crate::image::{Image, RasterImage, Svg}; use crate::Context; diff --git a/src/frame.rs b/src/frame.rs index a104c0695..9613e485d 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use crate::font::FaceId; use crate::geom::{ - Align, Em, Length, Numeric, Paint, Path, Point, Size, Spec, Transform, + Align, Em, Length, Numeric, Paint, Path, Point, Size, Spec, Stroke, Transform, }; use crate::image::ImageId; @@ -223,22 +223,6 @@ pub struct Shape { pub stroke: Option, } -impl Shape { - /// Create a filled shape without a stroke. - pub fn filled(geometry: Geometry, fill: Paint) -> Self { - Self { geometry, fill: Some(fill), stroke: None } - } - - /// Create a stroked shape without a fill. - pub fn stroked(geometry: Geometry, stroke: Stroke) -> Self { - Self { - geometry, - fill: None, - stroke: Some(stroke), - } - } -} - /// A shape's geometry. #[derive(Debug, Clone, Eq, PartialEq)] pub enum Geometry { @@ -252,11 +236,22 @@ pub enum Geometry { Path(Path), } -/// A stroke of a geometric shape. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Stroke { - /// The stroke's paint. - pub paint: Paint, - /// The stroke's thickness. - pub thickness: Length, +impl Geometry { + /// Fill the geometry without a stroke. + pub fn filled(self, fill: Paint) -> Shape { + Shape { + geometry: self, + fill: Some(fill), + stroke: None, + } + } + + /// Stroke the geometry without a fill. + pub fn stroked(self, stroke: Stroke) -> Shape { + Shape { + geometry: self, + fill: None, + stroke: Some(stroke), + } + } } diff --git a/src/geom/paint.rs b/src/geom/paint.rs index 3660d5282..351ef443d 100644 --- a/src/geom/paint.rs +++ b/src/geom/paint.rs @@ -5,7 +5,7 @@ use syntect::highlighting::Color as SynColor; use super::*; /// How a fill or stroke should be painted. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Copy, Clone, Eq, PartialEq, Hash)] pub enum Paint { /// A solid color. Solid(Color), @@ -20,6 +20,14 @@ where } } +impl Debug for Paint { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::Solid(color) => color.fmt(f), + } + } +} + /// A color in a dynamic format. #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub enum Color { @@ -234,6 +242,24 @@ impl From for Color { } } +/// A stroke of a geometric shape. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct Stroke { + /// The stroke's paint. + pub paint: Paint, + /// The stroke's thickness. + pub thickness: Length, +} + +impl Default for Stroke { + fn default() -> Self { + Self { + paint: Paint::Solid(Color::BLACK.into()), + thickness: Length::pt(1.0), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/library/graphics/line.rs b/src/library/graphics/line.rs index 1dd138e68..de2e4aa1d 100644 --- a/src/library/graphics/line.rs +++ b/src/library/graphics/line.rs @@ -12,10 +12,8 @@ pub struct LineNode { #[node] impl LineNode { /// How to stroke the line. - pub const STROKE: Paint = Color::BLACK.into(); - /// The line's thickness. - #[property(resolve)] - pub const THICKNESS: RawLength = Length::pt(1.0).into(); + #[property(resolve, fold)] + pub const STROKE: RawStroke = RawStroke::default(); fn construct(_: &mut Context, args: &mut Args) -> TypResult { let origin = args.named("origin")?.unwrap_or_default(); @@ -46,11 +44,7 @@ impl Layout for LineNode { regions: &Regions, styles: StyleChain, ) -> TypResult>> { - let thickness = styles.get(Self::THICKNESS); - let stroke = Some(Stroke { - paint: styles.get(Self::STROKE), - thickness, - }); + let stroke = styles.get(Self::STROKE).unwrap_or_default(); let origin = self .origin @@ -64,11 +58,10 @@ impl Layout for LineNode { .zip(regions.base) .map(|(l, b)| l.relative_to(b)); - let geometry = Geometry::Line(delta.to_point()); - let shape = Shape { geometry, fill: None, stroke }; - let target = regions.expand.select(regions.first, Size::zero()); let mut frame = Frame::new(target); + + let shape = Geometry::Line(delta.to_point()).stroked(stroke); frame.push(origin.to_point(), Element::Shape(shape)); Ok(vec![Arc::new(frame)]) diff --git a/src/library/graphics/shape.rs b/src/library/graphics/shape.rs index ec6f735bf..a159a3af3 100644 --- a/src/library/graphics/shape.rs +++ b/src/library/graphics/shape.rs @@ -24,10 +24,8 @@ impl ShapeNode { /// How to fill the shape. pub const FILL: Option = None; /// How to stroke the shape. - pub const STROKE: Smart> = Smart::Auto; - /// The stroke's thickness. - #[property(resolve)] - pub const THICKNESS: RawLength = Length::pt(1.0).into(); + #[property(resolve, fold)] + pub const STROKE: Smart> = Smart::Auto; /// How much to pad the shape's content. pub const PADDING: Relative = Relative::zero(); @@ -115,11 +113,10 @@ impl Layout for ShapeNode { // Add fill and/or stroke. let fill = styles.get(Self::FILL); - let thickness = styles.get(Self::THICKNESS); - let stroke = styles - .get(Self::STROKE) - .unwrap_or(fill.is_none().then(|| Color::BLACK.into())) - .map(|paint| Stroke { paint, thickness }); + let stroke = match styles.get(Self::STROKE) { + Smart::Auto => fill.is_none().then(Stroke::default), + Smart::Custom(stroke) => stroke.map(RawStroke::unwrap_or_default), + }; if fill.is_some() || stroke.is_some() { let geometry = if is_round(S) { diff --git a/src/library/mod.rs b/src/library/mod.rs index a5f0b50c4..0034b5815 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -124,65 +124,3 @@ pub fn new() -> Scope { std } - -dynamic! { - Dir: "direction", -} - -dynamic! { - RawAlign: "alignment", -} - -dynamic! { - Spec: "2d alignment", -} - -castable! { - Spec>, - Expected: "1d or 2d alignment", - @align: RawAlign => { - let mut aligns = Spec::default(); - aligns.set(align.axis(), Some(*align)); - aligns - }, - @aligns: Spec => aligns.map(Some), -} - -castable! { - usize, - Expected: "non-negative integer", - Value::Int(int) => int.try_into().map_err(|_| { - if int < 0 { - "must be at least zero" - } else { - "number too large" - } - })?, -} - -castable! { - NonZeroUsize, - Expected: "positive integer", - Value::Int(int) => Value::Int(int) - .cast::()? - .try_into() - .map_err(|_| "must be positive")?, -} - -castable! { - Paint, - Expected: "color", - Value::Color(color) => Paint::Solid(color), -} - -castable! { - String, - Expected: "string", - Value::Str(string) => string.into(), -} - -castable! { - LayoutNode, - Expected: "content", - Value::Content(content) => content.pack(), -} diff --git a/src/library/prelude.rs b/src/library/prelude.rs index d74a5d852..a1ebe6eff 100644 --- a/src/library/prelude.rs +++ b/src/library/prelude.rs @@ -10,7 +10,7 @@ pub use typst_macros::node; pub use crate::diag::{with_alternative, At, Error, StrResult, TypError, TypResult}; pub use crate::eval::{ Arg, Args, Array, Cast, Content, Dict, Fold, Func, Key, Layout, LayoutNode, Merge, - Node, RawAlign, RawLength, Regions, Resolve, Scope, Show, ShowNode, Smart, + Node, RawAlign, RawLength, RawStroke, Regions, Resolve, Scope, Show, ShowNode, Smart, StyleChain, StyleMap, StyleVec, Value, }; pub use crate::frame::*; diff --git a/src/library/structure/table.rs b/src/library/structure/table.rs index d0ab0716e..40f25749b 100644 --- a/src/library/structure/table.rs +++ b/src/library/structure/table.rs @@ -19,10 +19,8 @@ impl TableNode { /// The secondary cell fill color. pub const SECONDARY: Option = None; /// How to stroke the cells. - pub const STROKE: Option = Some(Color::BLACK.into()); - /// The stroke's thickness. - #[property(resolve)] - pub const THICKNESS: RawLength = Length::pt(1.0).into(); + #[property(resolve, fold)] + pub const STROKE: Option = Some(RawStroke::default()); /// How much to pad the cells's content. pub const PADDING: Relative = Length::pt(5.0).into(); @@ -48,7 +46,6 @@ impl TableNode { styles.set_opt(Self::PRIMARY, args.named("primary")?.or(fill)); styles.set_opt(Self::SECONDARY, args.named("secondary")?.or(fill)); styles.set_opt(Self::STROKE, args.named("stroke")?); - styles.set_opt(Self::THICKNESS, args.named("thickness")?); styles.set_opt(Self::PADDING, args.named("padding")?); Ok(styles) } @@ -63,8 +60,7 @@ impl Show for TableNode { let primary = styles.get(Self::PRIMARY); let secondary = styles.get(Self::SECONDARY); - let thickness = styles.get(Self::THICKNESS); - let stroke = styles.get(Self::STROKE).map(|paint| Stroke { paint, thickness }); + let stroke = styles.get(Self::STROKE).map(RawStroke::unwrap_or_default); let padding = styles.get(Self::PADDING); let cols = self.tracks.x.len().max(1); diff --git a/src/library/text/deco.rs b/src/library/text/deco.rs index f5ed47445..b8a0b3cbf 100644 --- a/src/library/text/deco.rs +++ b/src/library/text/deco.rs @@ -20,12 +20,10 @@ pub type OverlineNode = DecoNode; #[node(showable)] impl DecoNode { - /// Stroke color of the line, defaults to the text color if `None`. - #[property(shorthand)] - pub const STROKE: Option = None; - /// Thickness of the line's strokes, read from the font tables if `auto`. - #[property(shorthand, resolve)] - pub const THICKNESS: Smart = Smart::Auto; + /// How to stroke the line. The text color and thickness read from the font + /// tables if `auto`. + #[property(shorthand, resolve, fold)] + pub const STROKE: Smart = Smart::Auto; /// Position of the line relative to the baseline, read from the font tables /// if `auto`. #[property(resolve)] @@ -49,8 +47,7 @@ impl Show for DecoNode { .unwrap_or_else(|| { self.0.clone().styled(TextNode::DECO, Decoration { line: L, - stroke: styles.get(Self::STROKE), - thickness: styles.get(Self::THICKNESS), + stroke: styles.get(Self::STROKE).unwrap_or_default(), offset: styles.get(Self::OFFSET), extent: styles.get(Self::EXTENT), evade: styles.get(Self::EVADE), @@ -65,8 +62,7 @@ impl Show for DecoNode { #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Decoration { pub line: DecoLine, - pub stroke: Option, - pub thickness: Smart, + pub stroke: RawStroke, pub offset: Smart, pub extent: Length, pub evade: bool, @@ -103,11 +99,10 @@ pub fn decorate( let evade = deco.evade && deco.line != STRIKETHROUGH; let offset = deco.offset.unwrap_or(-metrics.position.at(text.size)); - - let stroke = Stroke { - paint: deco.stroke.unwrap_or(text.fill), - thickness: deco.thickness.unwrap_or(metrics.thickness.at(text.size)), - }; + let stroke = deco.stroke.unwrap_or(Stroke { + paint: text.fill, + thickness: metrics.thickness.at(text.size), + }); let gap_padding = 0.08 * text.size; let min_width = 0.162 * text.size; @@ -120,7 +115,7 @@ pub fn decorate( let target = Point::new(to - from, Length::zero()); if target.x >= min_width || !evade { - let shape = Shape::stroked(Geometry::Line(target), stroke); + let shape = Geometry::Line(target).stroked(stroke); frame.push(origin, Element::Shape(shape)); } }; diff --git a/tests/ref/code/repr.png b/tests/ref/code/repr.png index 822b096d3aa993fc92c001d964786cb68c88a3ea..4474149a608d9f4dbc5bbbfd1b81bc265d31d253 100644 GIT binary patch literal 32209 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