diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/doc.rs | 132 | ||||
| -rw-r--r-- | src/eval/cast.rs | 474 | ||||
| -rw-r--r-- | src/eval/func.rs | 2 | ||||
| -rw-r--r-- | src/eval/library.rs | 2 | ||||
| -rw-r--r-- | src/eval/mod.rs | 2 | ||||
| -rw-r--r-- | src/eval/str.rs | 10 | ||||
| -rw-r--r-- | src/eval/value.rs | 68 | ||||
| -rw-r--r-- | src/font/mod.rs | 25 | ||||
| -rw-r--r-- | src/font/variant.rs | 80 | ||||
| -rw-r--r-- | src/geom/abs.rs | 4 | ||||
| -rw-r--r-- | src/geom/align.rs | 48 | ||||
| -rw-r--r-- | src/geom/axes.rs | 35 | ||||
| -rw-r--r-- | src/geom/corners.rs | 97 | ||||
| -rw-r--r-- | src/geom/dir.rs | 4 | ||||
| -rw-r--r-- | src/geom/em.rs | 16 | ||||
| -rw-r--r-- | src/geom/length.rs | 8 | ||||
| -rw-r--r-- | src/geom/mod.rs | 40 | ||||
| -rw-r--r-- | src/geom/paint.rs | 23 | ||||
| -rw-r--r-- | src/geom/rel.rs | 28 | ||||
| -rw-r--r-- | src/geom/shape.rs | 35 | ||||
| -rw-r--r-- | src/geom/sides.rs | 85 | ||||
| -rw-r--r-- | src/geom/smart.rs | 59 | ||||
| -rw-r--r-- | src/geom/stroke.rs | 34 | ||||
| -rw-r--r-- | src/ide/complete.rs | 5 | ||||
| -rw-r--r-- | src/lib.rs | 3 | ||||
| -rw-r--r-- | src/model/content.rs | 412 | ||||
| -rw-r--r-- | src/model/mod.rs | 2 | ||||
| -rw-r--r-- | src/model/realize.rs | 7 | ||||
| -rw-r--r-- | src/model/styles.rs | 337 | ||||
| -rw-r--r-- | src/model/typeset.rs | 3 | ||||
| -rw-r--r-- | src/util/mod.rs | 5 |
31 files changed, 1112 insertions, 973 deletions
@@ -7,14 +7,14 @@ use std::sync::Arc; use ecow::EcoString; -use crate::eval::{dict, Dict, Value}; +use crate::eval::{cast_from_value, cast_to_value, dict, Dict, Value}; use crate::font::Font; use crate::geom::{ - self, rounded_rect, Abs, Align, Axes, Color, Corners, Dir, Em, Geometry, Numeric, - Paint, Point, Rel, RgbaColor, Shape, Sides, Size, Stroke, Transform, + self, rounded_rect, Abs, Align, Axes, Color, Corners, Dir, Em, Geometry, Length, + Numeric, Paint, Point, Rel, RgbaColor, Shape, Sides, Size, Stroke, Transform, }; use crate::image::Image; -use crate::model::{capable, node, Content, Fold, StableId, StyleChain}; +use crate::model::{node, Content, Fold, StableId, StyleChain}; /// A finished document with metadata and page frames. #[derive(Debug, Default, Clone, Hash)] @@ -274,7 +274,7 @@ impl Frame { if self.is_empty() { return; } - for meta in styles.get(Meta::DATA) { + for meta in styles.get(MetaNode::DATA) { if matches!(meta, Meta::Hidden) { self.clear(); break; @@ -283,6 +283,14 @@ impl Frame { } } + /// Add a background fill. + pub fn fill(&mut self, fill: Paint) { + self.prepend( + Point::zero(), + Element::Shape(Geometry::Rect(self.size()).filled(fill)), + ); + } + /// Add a fill and stroke with optional radius and outset to the frame. pub fn fill_and_stroke( &mut self, @@ -533,6 +541,15 @@ impl FromStr for Lang { } } +cast_from_value! { + Lang, + string: EcoString => Self::from_str(&string)?, +} + +cast_to_value! { + v: Lang => v.as_str().into() +} + /// An identifier for a region somewhere in the world. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct Region([u8; 2]); @@ -559,8 +576,16 @@ impl FromStr for Region { } } +cast_from_value! { + Region, + string: EcoString => Self::from_str(&string)?, +} + +cast_to_value! { + v: Region => v.as_str().into() +} + /// Meta information that isn't visible or renderable. -#[capable] #[derive(Debug, Clone, Hash)] pub enum Meta { /// An internal or external link. @@ -572,12 +597,16 @@ pub enum Meta { Hidden, } +/// Host for metadata. #[node] -impl Meta { +pub struct MetaNode { /// Metadata that should be attached to all elements affected by this style /// property. - #[property(fold, skip)] - pub const DATA: Vec<Meta> = vec![]; + #[settable] + #[fold] + #[skip] + #[default] + pub data: Vec<Meta>, } impl Fold for Vec<Meta> { @@ -589,6 +618,16 @@ impl Fold for Vec<Meta> { } } +cast_from_value! { + Meta: "meta", +} + +impl PartialEq for Meta { + fn eq(&self, other: &Self) -> bool { + crate::util::hash128(self) == crate::util::hash128(other) + } +} + /// A link destination. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum Destination { @@ -598,6 +637,19 @@ pub enum Destination { Url(EcoString), } +cast_from_value! { + Destination, + loc: Location => Self::Internal(loc), + string: EcoString => Self::Url(string), +} + +cast_to_value! { + v: Destination => match v { + Destination::Internal(loc) => loc.into(), + Destination::Url(url) => url.into(), + } +} + /// A physical location in a document. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct Location { @@ -607,53 +659,21 @@ pub struct Location { pub pos: Point, } -impl Location { - /// Encode into a user-facing dictionary. - pub fn encode(&self) -> Dict { - dict! { - "page" => Value::Int(self.page.get() as i64), - "x" => Value::Length(self.pos.x.into()), - "y" => Value::Length(self.pos.y.into()), - } - } +cast_from_value! { + Location, + mut dict: Dict => { + let page = dict.take("page")?.cast()?; + let x: Length = dict.take("x")?.cast()?; + let y: Length = dict.take("y")?.cast()?; + dict.finish(&["page", "x", "y"])?; + Self { page, pos: Point::new(x.abs, y.abs) } + }, } -/// Standard semantic roles. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum Role { - /// A paragraph. - Paragraph, - /// A heading of the given level and whether it should be part of the - /// outline. - Heading { level: NonZeroUsize, outlined: bool }, - /// A generic block-level subdivision. - GenericBlock, - /// A generic inline subdivision. - GenericInline, - /// A list and whether it is ordered. - List { ordered: bool }, - /// A list item. Must have a list parent. - ListItem, - /// The label of a list item. Must have a list item parent. - ListLabel, - /// The body of a list item. Must have a list item parent. - ListItemBody, - /// A mathematical formula. - Formula, - /// A table. - Table, - /// A table row. Must have a table parent. - TableRow, - /// A table cell. Must have a table row parent. - TableCell, - /// A code fragment. - Code, - /// A page header. - Header, - /// A page footer. - Footer, - /// A page background. - Background, - /// A page foreground. - Foreground, +cast_to_value! { + v: Location => Value::Dict(dict! { + "page" => Value::Int(v.page.get() as i64), + "x" => Value::Length(v.pos.x.into()), + "y" => Value::Length(v.pos.y.into()), + }) } diff --git a/src/eval/cast.rs b/src/eval/cast.rs index 77521f7f..840ceb05 100644 --- a/src/eval/cast.rs +++ b/src/eval/cast.rs @@ -1,18 +1,12 @@ +pub use typst_macros::{cast_from_value, cast_to_value}; + use std::num::NonZeroUsize; use std::ops::Add; -use std::str::FromStr; use ecow::EcoString; -use super::{castable, Array, Dict, Func, Regex, Str, Value}; +use super::{Array, Str, Value}; use crate::diag::StrResult; -use crate::doc::{Destination, Lang, Location, Region}; -use crate::font::{FontStretch, FontStyle, FontWeight}; -use crate::geom::{ - Axes, Color, Corners, Dir, GenAlign, Get, Length, Paint, PartialStroke, Point, Ratio, - Rel, Sides, Smart, -}; -use crate::model::{Content, Label, Selector, Transform}; use crate::syntax::Spanned; /// Cast from a value to a specific type. @@ -32,88 +26,6 @@ pub trait Cast<V = Value>: Sized { } } -/// Describes a possible value for a cast. -#[derive(Debug, Clone, Hash)] -pub enum CastInfo { - /// Any value is okay. - Any, - /// A specific value, plus short documentation for that value. - Value(Value, &'static str), - /// Any value of a type. - Type(&'static str), - /// Multiple alternatives. - Union(Vec<Self>), -} - -impl CastInfo { - /// Produce an error message describing what was expected and what was - /// found. - pub fn error(&self, found: &Value) -> EcoString { - fn accumulate( - info: &CastInfo, - found: &Value, - parts: &mut Vec<EcoString>, - matching_type: &mut bool, - ) { - match info { - CastInfo::Any => parts.push("anything".into()), - CastInfo::Value(value, _) => { - parts.push(value.repr().into()); - if value.type_name() == found.type_name() { - *matching_type = true; - } - } - CastInfo::Type(ty) => parts.push((*ty).into()), - CastInfo::Union(options) => { - for option in options { - accumulate(option, found, parts, matching_type); - } - } - } - } - - let mut matching_type = false; - let mut parts = vec![]; - accumulate(self, found, &mut parts, &mut matching_type); - - let mut msg = String::from("expected "); - if parts.is_empty() { - msg.push_str(" nothing"); - } - - crate::diag::comma_list(&mut msg, &parts, "or"); - - if !matching_type { - msg.push_str(", found "); - msg.push_str(found.type_name()); - } - - msg.into() - } -} - -impl Add for CastInfo { - type Output = Self; - - fn add(self, rhs: Self) -> Self { - Self::Union(match (self, rhs) { - (Self::Union(mut lhs), Self::Union(rhs)) => { - lhs.extend(rhs); - lhs - } - (Self::Union(mut lhs), rhs) => { - lhs.push(rhs); - lhs - } - (lhs, Self::Union(mut rhs)) => { - rhs.insert(0, lhs); - rhs - } - (lhs, rhs) => vec![lhs, rhs], - }) - } -} - impl Cast for Value { fn is(_: &Value) -> bool { true @@ -157,43 +69,15 @@ impl<T: Cast> Cast<Spanned<Value>> for Spanned<T> { } } -castable! { - Dir: "direction", -} - -castable! { - GenAlign: "alignment", -} - -castable! { - Regex: "regular expression", +cast_to_value! { + v: u8 => Value::Int(v as i64) } -castable! { - Selector: "selector", - text: EcoString => Self::text(&text), - label: Label => Self::Label(label), - func: Func => func.select(None)?, - regex: Regex => Self::Regex(regex), +cast_to_value! { + v: u16 => Value::Int(v as i64) } -castable! { - Axes<GenAlign>: "2d alignment", -} - -castable! { - PartialStroke: "stroke", - thickness: Length => Self { - paint: Smart::Auto, - thickness: Smart::Custom(thickness), - }, - color: Color => Self { - paint: Smart::Custom(color.into()), - thickness: Smart::Auto, - }, -} - -castable! { +cast_from_value! { u32, int: i64 => int.try_into().map_err(|_| { if int < 0 { @@ -204,7 +88,15 @@ castable! { })?, } -castable! { +cast_to_value! { + v: u32 => Value::Int(v as i64) +} + +cast_to_value! { + v: i32 => Value::Int(v as i64) +} + +cast_from_value! { usize, int: i64 => int.try_into().map_err(|_| { if int < 0 { @@ -215,7 +107,11 @@ castable! { })?, } -castable! { +cast_to_value! { + v: usize => Value::Int(v as i64) +} + +cast_from_value! { NonZeroUsize, int: i64 => int .try_into() @@ -227,12 +123,11 @@ castable! { })?, } -castable! { - Paint, - color: Color => Self::Solid(color), +cast_to_value! { + v: NonZeroUsize => Value::Int(v.get() as i64) } -castable! { +cast_from_value! { char, string: Str => { let mut chars = string.chars(); @@ -243,131 +138,30 @@ castable! { }, } -castable! { - EcoString, - string: Str => string.into(), -} - -castable! { - String, - string: Str => string.into(), -} - -castable! { - Transform, - content: Content => Self::Content(content), - func: Func => { - if func.argc().map_or(false, |count| count != 1) { - Err("function must have exactly one parameter")? - } - Self::Func(func) - }, -} - -castable! { - Axes<Option<GenAlign>>, - align: GenAlign => { - let mut aligns = Axes::default(); - aligns.set(align.axis(), Some(align)); - aligns - }, - aligns: Axes<GenAlign> => aligns.map(Some), -} - -castable! { - Axes<Rel<Length>>, - array: Array => { - let mut iter = array.into_iter(); - match (iter.next(), iter.next(), iter.next()) { - (Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?), - _ => Err("point array must contain exactly two entries")?, - } - }, -} - -castable! { - Location, - mut dict: Dict => { - let page = dict.take("page")?.cast()?; - let x: Length = dict.take("x")?.cast()?; - let y: Length = dict.take("y")?.cast()?; - dict.finish(&["page", "x", "y"])?; - Self { page, pos: Point::new(x.abs, y.abs) } - }, -} - -castable! { - Destination, - loc: Location => Self::Internal(loc), - string: EcoString => Self::Url(string), +cast_to_value! { + v: char => Value::Str(v.into()) } -castable! { - FontStyle, - /// The default, typically upright style. - "normal" => Self::Normal, - /// A cursive style with custom letterform. - "italic" => Self::Italic, - /// Just a slanted version of the normal style. - "oblique" => Self::Oblique, +cast_to_value! { + v: &str => Value::Str(v.into()) } -castable! { - FontWeight, - v: i64 => Self::from_number(v.clamp(0, u16::MAX as i64) as u16), - /// Thin weight (100). - "thin" => Self::THIN, - /// Extra light weight (200). - "extralight" => Self::EXTRALIGHT, - /// Light weight (300). - "light" => Self::LIGHT, - /// Regular weight (400). - "regular" => Self::REGULAR, - /// Medium weight (500). - "medium" => Self::MEDIUM, - /// Semibold weight (600). - "semibold" => Self::SEMIBOLD, - /// Bold weight (700). - "bold" => Self::BOLD, - /// Extrabold weight (800). - "extrabold" => Self::EXTRABOLD, - /// Black weight (900). - "black" => Self::BLACK, +cast_from_value! { + EcoString, + v: Str => v.into(), } -castable! { - FontStretch, - v: Ratio => Self::from_ratio(v.get() as f32), +cast_to_value! { + v: EcoString => Value::Str(v.into()) } -castable! { - Lang, - string: EcoString => Self::from_str(&string)?, +cast_from_value! { + String, + v: Str => v.into(), } -castable! { - Region, - string: EcoString => Self::from_str(&string)?, -} - -/// Castable from [`Value::None`]. -pub struct NoneValue; - -impl Cast for NoneValue { - fn is(value: &Value) -> bool { - matches!(value, Value::None) - } - - fn cast(value: Value) -> StrResult<Self> { - match value { - Value::None => Ok(Self), - _ => <Self as Cast>::error(value), - } - } - - fn describe() -> CastInfo { - CastInfo::Type("none") - } +cast_to_value! { + v: String => Value::Str(v.into()) } impl<T: Cast> Cast for Option<T> { @@ -388,126 +182,130 @@ impl<T: Cast> Cast for Option<T> { } } -/// Castable from [`Value::Auto`]. -pub struct AutoValue; +impl<T: Into<Value>> From<Option<T>> for Value { + fn from(v: Option<T>) -> Self { + match v { + Some(v) => v.into(), + None => Value::None, + } + } +} -impl Cast for AutoValue { +impl<T: Cast> Cast for Vec<T> { fn is(value: &Value) -> bool { - matches!(value, Value::Auto) + Array::is(value) } fn cast(value: Value) -> StrResult<Self> { - match value { - Value::Auto => Ok(Self), - _ => <Self as Cast>::error(value), - } + value.cast::<Array>()?.into_iter().map(Value::cast).collect() } fn describe() -> CastInfo { - CastInfo::Type("auto") + <Array as Cast>::describe() } } -impl<T: Cast> Cast for Smart<T> { - fn is(value: &Value) -> bool { - matches!(value, Value::Auto) || T::is(value) +impl<T: Into<Value>> From<Vec<T>> for Value { + fn from(v: Vec<T>) -> Self { + Value::Array(v.into_iter().map(Into::into).collect()) } +} - fn cast(value: Value) -> StrResult<Self> { - match value { - Value::Auto => Ok(Self::Auto), - v if T::is(&v) => Ok(Self::Custom(T::cast(v)?)), - _ => <Self as Cast>::error(value), +/// Describes a possible value for a cast. +#[derive(Debug, Clone, Hash)] +pub enum CastInfo { + /// Any value is okay. + Any, + /// A specific value, plus short documentation for that value. + Value(Value, &'static str), + /// Any value of a type. + Type(&'static str), + /// Multiple alternatives. + Union(Vec<Self>), +} + +impl CastInfo { + /// Produce an error message describing what was expected and what was + /// found. + pub fn error(&self, found: &Value) -> EcoString { + fn accumulate( + info: &CastInfo, + found: &Value, + parts: &mut Vec<EcoString>, + matching_type: &mut bool, + ) { + match info { + CastInfo::Any => parts.push("anything".into()), + CastInfo::Value(value, _) => { + parts.push(value.repr().into()); + if value.type_name() == found.type_name() { + *matching_type = true; + } + } + CastInfo::Type(ty) => parts.push((*ty).into()), + CastInfo::Union(options) => { + for option in options { + accumulate(option, found, parts, matching_type); + } + } + } } - } - fn describe() -> CastInfo { - T::describe() + CastInfo::Type("auto") - } -} + let mut matching_type = false; + let mut parts = vec![]; + accumulate(self, found, &mut parts, &mut matching_type); -impl<T> Cast for Sides<Option<T>> -where - T: Cast + Copy, -{ - fn is(value: &Value) -> bool { - matches!(value, Value::Dict(_)) || T::is(value) - } + let mut msg = String::from("expected "); + if parts.is_empty() { + msg.push_str(" nothing"); + } - fn cast(mut value: Value) -> StrResult<Self> { - if let Value::Dict(dict) = &mut value { - let mut take = |key| dict.take(key).ok().map(T::cast).transpose(); - - let rest = take("rest")?; - let x = take("x")?.or(rest); - let y = take("y")?.or(rest); - let sides = Sides { - left: take("left")?.or(x), - top: take("top")?.or(y), - right: take("right")?.or(x), - bottom: take("bottom")?.or(y), - }; - - dict.finish(&["left", "top", "right", "bottom", "x", "y", "rest"])?; - - Ok(sides) - } else if T::is(&value) { - Ok(Self::splat(Some(T::cast(value)?))) - } else { - <Self as Cast>::error(value) + crate::diag::comma_list(&mut msg, &parts, "or"); + + if !matching_type { + msg.push_str(", found "); + msg.push_str(found.type_name()); } + + msg.into() } +} - fn describe() -> CastInfo { - T::describe() + CastInfo::Type("dictionary") +impl Add for CastInfo { + type Output = Self; + + fn add(self, rhs: Self) -> Self { + Self::Union(match (self, rhs) { + (Self::Union(mut lhs), Self::Union(rhs)) => { + lhs.extend(rhs); + lhs + } + (Self::Union(mut lhs), rhs) => { + lhs.push(rhs); + lhs + } + (lhs, Self::Union(mut rhs)) => { + rhs.insert(0, lhs); + rhs + } + (lhs, rhs) => vec![lhs, rhs], + }) } } -impl<T> Cast for Corners<Option<T>> -where - T: Cast + Copy, -{ - fn is(value: &Value) -> bool { - matches!(value, Value::Dict(_)) || T::is(value) +/// Castable from nothing. +pub enum Never {} + +impl Cast for Never { + fn is(_: &Value) -> bool { + false } - fn cast(mut value: Value) -> StrResult<Self> { - if let Value::Dict(dict) = &mut value { - let mut take = |key| dict.take(key).ok().map(T::cast).transpose(); - - let rest = take("rest")?; - let left = take("left")?.or(rest); - let top = take("top")?.or(rest); - let right = take("right")?.or(rest); - let bottom = take("bottom")?.or(rest); - let corners = Corners { - top_left: take("top-left")?.or(top).or(left), - top_right: take("top-right")?.or(top).or(right), - bottom_right: take("bottom-right")?.or(bottom).or(right), - bottom_left: take("bottom-left")?.or(bottom).or(left), - }; - - dict.finish(&[ - "top-left", - "top-right", - "bottom-right", - "bottom-left", - "left", - "top", - "right", - "bottom", - "rest", - ])?; - - Ok(corners) - } else if T::is(&value) { - Ok(Self::splat(Some(T::cast(value)?))) - } else { - <Self as Cast>::error(value) - } + fn cast(value: Value) -> StrResult<Self> { + <Self as Cast>::error(value) } fn describe() -> CastInfo { - T::describe() + CastInfo::Type("dictionary") + CastInfo::Union(vec![]) } } diff --git a/src/eval/func.rs b/src/eval/func.rs index e5280932..8243b4f6 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -1,3 +1,5 @@ +pub use typst_macros::func; + use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; diff --git a/src/eval/library.rs b/src/eval/library.rs index adfcc6e7..75787348 100644 --- a/src/eval/library.rs +++ b/src/eval/library.rs @@ -44,7 +44,7 @@ pub struct LangItems { /// The id of the text node. pub text_id: NodeId, /// Get the string if this is a text node. - pub text_str: fn(&Content) -> Option<&str>, + pub text_str: fn(&Content) -> Option<EcoString>, /// A smart quote: `'` or `"`. pub smart_quote: fn(double: bool) -> Content, /// A paragraph break. diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 2cf6f4d1..8180f11d 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -20,8 +20,6 @@ mod ops; mod scope; mod symbol; -pub use typst_macros::{castable, func}; - pub use self::args::*; pub use self::array::*; pub use self::cast::*; diff --git a/src/eval/str.rs b/src/eval/str.rs index 63ea5dc8..0d5d71b9 100644 --- a/src/eval/str.rs +++ b/src/eval/str.rs @@ -6,7 +6,7 @@ use std::ops::{Add, AddAssign, Deref}; use ecow::EcoString; use unicode_segmentation::UnicodeSegmentation; -use super::{castable, dict, Array, Dict, Value}; +use super::{cast_from_value, dict, Array, Dict, Value}; use crate::diag::StrResult; use crate::geom::GenAlign; @@ -479,6 +479,10 @@ impl Hash for Regex { } } +cast_from_value! { + Regex: "regular expression", +} + /// A pattern which can be searched for in a string. #[derive(Debug, Clone)] pub enum StrPattern { @@ -488,7 +492,7 @@ pub enum StrPattern { Regex(Regex), } -castable! { +cast_from_value! { StrPattern, text: Str => Self::Str(text), regex: Regex => Self::Regex(regex), @@ -504,7 +508,7 @@ pub enum StrSide { End, } -castable! { +cast_from_value! { StrSide, align: GenAlign => match align { GenAlign::Start => Self::Start, diff --git a/src/eval/value.rs b/src/eval/value.rs index 5e06da76..9b9bc314 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -4,15 +4,15 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; -use ecow::{eco_format, EcoString}; +use ecow::eco_format; use siphasher::sip128::{Hasher128, SipHasher}; use super::{ - format_str, ops, Args, Array, Cast, CastInfo, Content, Dict, Func, Label, Module, - Str, Symbol, + cast_to_value, format_str, ops, Args, Array, Cast, CastInfo, Content, Dict, Func, + Label, Module, Str, Symbol, }; use crate::diag::StrResult; -use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor}; +use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel}; use crate::syntax::{ast, Span}; /// A computational value. @@ -122,6 +122,7 @@ impl Value { Self::Dict(dict) => dict.at(&field).cloned(), Self::Content(content) => content .field(&field) + .cloned() .ok_or_else(|| eco_format!("unknown field `{field}`")), Self::Module(module) => module.get(&field).cloned(), v => Err(eco_format!("cannot access fields on type {}", v.type_name())), @@ -241,60 +242,6 @@ impl Hash for Value { } } -impl From<i32> for Value { - fn from(v: i32) -> Self { - Self::Int(v as i64) - } -} - -impl From<usize> for Value { - fn from(v: usize) -> Self { - Self::Int(v as i64) - } -} - -impl From<Abs> for Value { - fn from(v: Abs) -> Self { - Self::Length(v.into()) - } -} - -impl From<Em> for Value { - fn from(v: Em) -> Self { - Self::Length(v.into()) - } -} - -impl From<RgbaColor> for Value { - fn from(v: RgbaColor) -> Self { - Self::Color(v.into()) - } -} - -impl From<&str> for Value { - fn from(v: &str) -> Self { - Self::Str(v.into()) - } -} - -impl From<EcoString> for Value { - fn from(v: EcoString) -> Self { - Self::Str(v.into()) - } -} - -impl From<String> for Value { - fn from(v: String) -> Self { - Self::Str(v.into()) - } -} - -impl From<Dynamic> for Value { - fn from(v: Dynamic) -> Self { - Self::Dyn(v) - } -} - /// A dynamic value. #[derive(Clone, Hash)] pub struct Dynamic(Arc<dyn Bounds>); @@ -336,6 +283,10 @@ impl PartialEq for Dynamic { } } +cast_to_value! { + v: Dynamic => Value::Dyn(v) +} + trait Bounds: Debug + Sync + Send + 'static { fn as_any(&self) -> &dyn Any; fn dyn_eq(&self, other: &Dynamic) -> bool; @@ -462,6 +413,7 @@ primitive! { Args: "arguments", Args } mod tests { use super::*; use crate::eval::{array, dict}; + use crate::geom::RgbaColor; #[track_caller] fn test(value: impl Into<Value>, exp: &str) { diff --git a/src/font/mod.rs b/src/font/mod.rs index bedc107d..94ec170e 100644 --- a/src/font/mod.rs +++ b/src/font/mod.rs @@ -12,6 +12,7 @@ use std::sync::Arc; use ttf_parser::GlyphId; +use crate::eval::{cast_from_value, cast_to_value, Value}; use crate::geom::Em; use crate::util::Buffer; @@ -249,3 +250,27 @@ pub enum VerticalFontMetric { /// present and falls back to the descender from the `hhea` table otherwise. Descender, } + +cast_from_value! { + VerticalFontMetric, + /// The font's ascender, which typically exceeds the height of all glyphs. + "ascender" => Self::Ascender, + /// The approximate height of uppercase letters. + "cap-height" => Self::CapHeight, + /// The approximate height of non-ascending lowercase letters. + "x-height" => Self::XHeight, + /// The baseline on which the letters rest. + "baseline" => Self::Baseline, + /// The font's ascender, which typically exceeds the depth of all glyphs. + "descender" => Self::Descender, +} + +cast_to_value! { + v: VerticalFontMetric => Value::from(match v { + VerticalFontMetric::Ascender => "ascender", + VerticalFontMetric::CapHeight => "cap-height", + VerticalFontMetric::XHeight => "x-height", + VerticalFontMetric::Baseline => "baseline" , + VerticalFontMetric::Descender => "descender", + }) +} diff --git a/src/font/variant.rs b/src/font/variant.rs index aa9ff141..4eda80ad 100644 --- a/src/font/variant.rs +++ b/src/font/variant.rs @@ -2,6 +2,9 @@ use std::fmt::{self, Debug, Formatter}; use serde::{Deserialize, Serialize}; +use crate::eval::{cast_from_value, cast_to_value, Value}; +use crate::geom::Ratio; + /// Properties that distinguish a font from other fonts in the same family. #[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[derive(Serialize, Deserialize)] @@ -59,6 +62,24 @@ impl Default for FontStyle { } } +cast_from_value! { + FontStyle, + /// The default, typically upright style. + "normal" => Self::Normal, + /// A cursive style with custom letterform. + "italic" => Self::Italic, + /// Just a slanted version of the normal style. + "oblique" => Self::Oblique, +} + +cast_to_value! { + v: FontStyle => Value::from(match v { + FontStyle::Normal => "normal", + FontStyle::Italic => "italic", + FontStyle::Oblique => "oblique", + }) +} + /// The weight of a font. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[derive(Serialize, Deserialize)] @@ -127,6 +148,44 @@ impl Debug for FontWeight { } } +cast_from_value! { + FontWeight, + v: i64 => Self::from_number(v.clamp(0, u16::MAX as i64) as u16), + /// Thin weight (100). + "thin" => Self::THIN, + /// Extra light weight (200). + "extralight" => Self::EXTRALIGHT, + /// Light weight (300). + "light" => Self::LIGHT, + /// Regular weight (400). + "regular" => Self::REGULAR, + /// Medium weight (500). + "medium" => Self::MEDIUM, + /// Semibold weight (600). + "semibold" => Self::SEMIBOLD, + /// Bold weight (700). + "bold" => Self::BOLD, + /// Extrabold weight (800). + "extrabold" => Self::EXTRABOLD, + /// Black weight (900). + "black" => Self::BLACK, +} + +cast_to_value! { + v: FontWeight => Value::from(match v { + FontWeight::THIN => "thin", + FontWeight::EXTRALIGHT => "extralight", + FontWeight::LIGHT => "light", + FontWeight::REGULAR => "regular", + FontWeight::MEDIUM => "medium", + FontWeight::SEMIBOLD => "semibold", + FontWeight::BOLD => "bold", + FontWeight::EXTRABOLD => "extrabold", + FontWeight::BLACK => "black", + _ => return v.to_number().into(), + }) +} + /// The width of a font. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[derive(Serialize, Deserialize)] @@ -163,8 +222,8 @@ impl FontStretch { /// Create a font stretch from a ratio between 0.5 and 2.0, clamping it if /// necessary. - pub fn from_ratio(ratio: f32) -> Self { - Self((ratio.max(0.5).min(2.0) * 1000.0) as u16) + pub fn from_ratio(ratio: Ratio) -> Self { + Self((ratio.get().max(0.5).min(2.0) * 1000.0) as u16) } /// Create a font stretch from an OpenType-style number between 1 and 9, @@ -184,12 +243,12 @@ impl FontStretch { } /// The ratio between 0.5 and 2.0 corresponding to this stretch. - pub fn to_ratio(self) -> f32 { - self.0 as f32 / 1000.0 + pub fn to_ratio(self) -> Ratio { + Ratio::new(self.0 as f64 / 1000.0) } /// The absolute ratio distance between this and another font stretch. - pub fn distance(self, other: Self) -> f32 { + pub fn distance(self, other: Self) -> Ratio { (self.to_ratio() - other.to_ratio()).abs() } } @@ -202,10 +261,19 @@ impl Default for FontStretch { impl Debug for FontStretch { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}%", 100.0 * self.to_ratio()) + self.to_ratio().fmt(f) } } +cast_from_value! { + FontStretch, + v: Ratio => Self::from_ratio(v), +} + +cast_to_value! { + v: FontStretch => v.to_ratio().into() +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/geom/abs.rs b/src/geom/abs.rs index 4429e46d..34c3d010 100644 --- a/src/geom/abs.rs +++ b/src/geom/abs.rs @@ -214,6 +214,10 @@ impl<'a> Sum<&'a Self> for Abs { } } +cast_to_value! { + v: Abs => Value::Length(v.into()) +} + /// Different units of absolute measurement. #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub enum AbsUnit { diff --git a/src/geom/align.rs b/src/geom/align.rs index 1e9bde52..b14e6775 100644 --- a/src/geom/align.rs +++ b/src/geom/align.rs @@ -115,3 +115,51 @@ impl Debug for GenAlign { } } } + +cast_from_value! { + GenAlign: "alignment", +} + +cast_from_value! { + Axes<GenAlign>: "2d alignment", +} + +cast_from_value! { + Axes<Option<GenAlign>>, + align: GenAlign => { + let mut aligns = Axes::default(); + aligns.set(align.axis(), Some(align)); + aligns + }, + aligns: Axes<GenAlign> => aligns.map(Some), +} + +cast_to_value! { + v: Axes<Option<GenAlign>> => match (v.x, v.y) { + (Some(x), Some(y)) => Axes::new(x, y).into(), + (Some(x), None) => x.into(), + (None, Some(y)) => y.into(), + (None, None) => Value::None, + } +} + +impl Resolve for GenAlign { + type Output = Align; + + fn resolve(self, styles: StyleChain) -> Self::Output { + let dir = item!(dir)(styles); + match self { + Self::Start => dir.start().into(), + Self::End => dir.end().into(), + Self::Specific(align) => align, + } + } +} + +impl Fold for GenAlign { + type Output = Self; + + fn fold(self, _: Self::Output) -> Self::Output { + self + } +} diff --git a/src/geom/axes.rs b/src/geom/axes.rs index 48f8c0e8..92bd0388 100644 --- a/src/geom/axes.rs +++ b/src/geom/axes.rs @@ -2,6 +2,7 @@ use std::any::Any; use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not}; use super::*; +use crate::eval::Array; /// A container with a horizontal and vertical component. #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] @@ -272,3 +273,37 @@ impl BitAndAssign for Axes<bool> { self.y &= rhs.y; } } + +cast_from_value! { + Axes<Rel<Length>>, + array: Array => { + let mut iter = array.into_iter(); + match (iter.next(), iter.next(), iter.next()) { + (Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?), + _ => Err("point array must contain exactly two entries")?, + } + }, +} + +cast_to_value! { + v: Axes<Rel<Length>> => Value::Array(array![v.x, v.y]) +} + +impl<T: Resolve> Resolve for Axes<T> { + type Output = Axes<T::Output>; + + fn resolve(self, styles: StyleChain) -> Self::Output { + 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(outer).map(|(inner, outer)| match inner { + Some(value) => value.fold(outer), + None => outer, + }) + } +} diff --git a/src/geom/corners.rs b/src/geom/corners.rs index 386acbfb..844d3047 100644 --- a/src/geom/corners.rs +++ b/src/geom/corners.rs @@ -107,3 +107,100 @@ pub enum Corner { /// The bottom left corner. BottomLeft, } + +impl<T> Cast for Corners<Option<T>> +where + T: Cast + Copy, +{ + fn is(value: &Value) -> bool { + matches!(value, Value::Dict(_)) || T::is(value) + } + + fn cast(mut value: Value) -> StrResult<Self> { + if let Value::Dict(dict) = &mut value { + let mut take = |key| dict.take(key).ok().map(T::cast).transpose(); + + let rest = take("rest")?; + let left = take("left")?.or(rest); + let top = take("top")?.or(rest); + let right = take("right")?.or(rest); + let bottom = take("bottom")?.or(rest); + let corners = Corners { + top_left: take("top-left")?.or(top).or(left), + top_right: take("top-right")?.or(top).or(right), + bottom_right: take("bottom-right")?.or(bottom).or(right), + bottom_left: take("bottom-left")?.or(bottom).or(left), + }; + + dict.finish(&[ + "top-left", + "top-right", + "bottom-right", + "bottom-left", + "left", + "top", + "right", + "bottom", + "rest", + ])?; + + Ok(corners) + } else if T::is(&value) { + Ok(Self::splat(Some(T::cast(value)?))) + } else { + <Self as Cast>::error(value) + } + } + + fn describe() -> CastInfo { + T::describe() + CastInfo::Type("dictionary") + } +} + +impl<T: Resolve> Resolve for Corners<T> { + type Output = Corners<T::Output>; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|v| v.resolve(styles)) + } +} + +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, + }) + } +} + +impl<T> From<Corners<Option<T>>> for Value +where + T: PartialEq + Into<Value>, +{ + fn from(corners: Corners<Option<T>>) -> Self { + if corners.is_uniform() { + if let Some(value) = corners.top_left { + return value.into(); + } + } + + let mut dict = Dict::new(); + if let Some(top_left) = corners.top_left { + dict.insert("top-left".into(), top_left.into()); + } + if let Some(top_right) = corners.top_right { + dict.insert("top-right".into(), top_right.into()); + } + if let Some(bottom_right) = corners.bottom_right { + dict.insert("bottom-right".into(), bottom_right.into()); + } + if let Some(bottom_left) = corners.bottom_left { + dict.insert("bottom-left".into(), bottom_left.into()); + } + + Value::Dict(dict) + } +} diff --git a/src/geom/dir.rs b/src/geom/dir.rs index b2fd6e5a..bc4d66e1 100644 --- a/src/geom/dir.rs +++ b/src/geom/dir.rs @@ -73,3 +73,7 @@ impl Debug for Dir { }) } } + +cast_from_value! { + Dir: "direction", +} diff --git a/src/geom/em.rs b/src/geom/em.rs index 9f5aff39..2c63c81d 100644 --- a/src/geom/em.rs +++ b/src/geom/em.rs @@ -134,3 +134,19 @@ impl Sum for Em { Self(iter.map(|s| s.0).sum()) } } + +cast_to_value! { + v: Em => Value::Length(v.into()) +} + +impl Resolve for Em { + type Output = Abs; + + fn resolve(self, styles: StyleChain) -> Self::Output { + if self.is_zero() { + Abs::zero() + } else { + self.at(item!(em)(styles)) + } + } +} diff --git a/src/geom/length.rs b/src/geom/length.rs index ae615f14..f70ea263 100644 --- a/src/geom/length.rs +++ b/src/geom/length.rs @@ -124,3 +124,11 @@ assign_impl!(Length += Length); assign_impl!(Length -= Length); assign_impl!(Length *= f64); assign_impl!(Length /= f64); + +impl Resolve for Length { + type Output = Abs; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.abs + self.em.resolve(styles) + } +} diff --git a/src/geom/mod.rs b/src/geom/mod.rs index ebe4436c..b7daaa1b 100644 --- a/src/geom/mod.rs +++ b/src/geom/mod.rs @@ -19,6 +19,7 @@ mod ratio; mod rel; mod rounded; mod scalar; +mod shape; mod sides; mod size; mod smart; @@ -42,6 +43,7 @@ pub use self::ratio::*; pub use self::rel::*; pub use self::rounded::*; pub use self::scalar::*; +pub use self::shape::*; pub use self::sides::*; pub use self::size::*; pub use self::smart::*; @@ -55,6 +57,10 @@ use std::hash::{Hash, Hasher}; use std::iter::Sum; use std::ops::*; +use crate::diag::StrResult; +use crate::eval::{array, cast_from_value, cast_to_value, Cast, CastInfo, Dict, Value}; +use crate::model::{Fold, Resolve, StyleChain}; + /// Generic access to a structure's components. pub trait Get<Index> { /// The structure's component type. @@ -72,40 +78,6 @@ pub trait Get<Index> { } } -/// A geometric shape with optional fill and stroke. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct Shape { - /// The shape's geometry. - pub geometry: Geometry, - /// The shape's background fill. - pub fill: Option<Paint>, - /// The shape's border stroke. - pub stroke: Option<Stroke>, -} - -/// A shape's geometry. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum Geometry { - /// A line to a point (relative to its position). - Line(Point), - /// A rectangle with its origin in the topleft corner. - Rect(Size), - /// A bezier path. - Path(Path), -} - -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) } - } -} - /// A numeric type. pub trait Numeric: Sized diff --git a/src/geom/paint.rs b/src/geom/paint.rs index b4064438..c01b21da 100644 --- a/src/geom/paint.rs +++ b/src/geom/paint.rs @@ -9,10 +9,7 @@ pub enum Paint { Solid(Color), } -impl<T> From<T> for Paint -where - T: Into<Color>, -{ +impl<T: Into<Color>> From<T> for Paint { fn from(t: T) -> Self { Self::Solid(t.into()) } @@ -26,6 +23,15 @@ impl Debug for Paint { } } +cast_from_value! { + Paint, + color: Color => Self::Solid(color), +} + +cast_to_value! { + Paint::Solid(color): Paint => Value::Color(color) +} + /// A color in a dynamic format. #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub enum Color { @@ -274,15 +280,16 @@ impl Debug for RgbaColor { } } -impl<T> From<T> for Color -where - T: Into<RgbaColor>, -{ +impl<T: Into<RgbaColor>> From<T> for Color { fn from(rgba: T) -> Self { Self::Rgba(rgba.into()) } } +cast_to_value! { + v: RgbaColor => Value::Color(v.into()) +} + /// An 8-bit CMYK color. #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct CmykColor { diff --git a/src/geom/rel.rs b/src/geom/rel.rs index a8e75d1c..7288f380 100644 --- a/src/geom/rel.rs +++ b/src/geom/rel.rs @@ -199,3 +199,31 @@ impl<T: Numeric> Add<Ratio> for Rel<T> { self + Rel::from(other) } } + +impl<T> Resolve for Rel<T> +where + T: Resolve + Numeric, + <T as Resolve>::Output: Numeric, +{ + type Output = Rel<<T as Resolve>::Output>; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|abs| abs.resolve(styles)) + } +} + +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 + } +} diff --git a/src/geom/shape.rs b/src/geom/shape.rs new file mode 100644 index 00000000..5658c21f --- /dev/null +++ b/src/geom/shape.rs @@ -0,0 +1,35 @@ +use super::*; + +/// A geometric shape with optional fill and stroke. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct Shape { + /// The shape's geometry. + pub geometry: Geometry, + /// The shape's background fill. + pub fill: Option<Paint>, + /// The shape's border stroke. + pub stroke: Option<Stroke>, +} + +/// A shape's geometry. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum Geometry { + /// A line to a point (relative to its position). + Line(Point), + /// A rectangle with its origin in the topleft corner. + Rect(Size), + /// A bezier path. + Path(Path), +} + +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/sides.rs b/src/geom/sides.rs index 40327a42..247d9a98 100644 --- a/src/geom/sides.rs +++ b/src/geom/sides.rs @@ -177,3 +177,88 @@ impl Side { } } } + +impl<T> Cast for Sides<Option<T>> +where + T: Default + Cast + Copy, +{ + fn is(value: &Value) -> bool { + matches!(value, Value::Dict(_)) || T::is(value) + } + + fn cast(mut value: Value) -> StrResult<Self> { + if let Value::Dict(dict) = &mut value { + let mut take = |key| dict.take(key).ok().map(T::cast).transpose(); + + let rest = take("rest")?; + let x = take("x")?.or(rest); + let y = take("y")?.or(rest); + let sides = Sides { + left: take("left")?.or(x), + top: take("top")?.or(y), + right: take("right")?.or(x), + bottom: take("bottom")?.or(y), + }; + + dict.finish(&["left", "top", "right", "bottom", "x", "y", "rest"])?; + + Ok(sides) + } else if T::is(&value) { + Ok(Self::splat(Some(T::cast(value)?))) + } else { + <Self as Cast>::error(value) + } + } + + fn describe() -> CastInfo { + T::describe() + CastInfo::Type("dictionary") + } +} + +impl<T> From<Sides<Option<T>>> for Value +where + T: PartialEq + Into<Value>, +{ + fn from(sides: Sides<Option<T>>) -> Self { + if sides.is_uniform() { + if let Some(value) = sides.left { + return value.into(); + } + } + + let mut dict = Dict::new(); + if let Some(left) = sides.left { + dict.insert("left".into(), left.into()); + } + if let Some(top) = sides.top { + dict.insert("top".into(), top.into()); + } + if let Some(right) = sides.right { + dict.insert("right".into(), right.into()); + } + if let Some(bottom) = sides.bottom { + dict.insert("bottom".into(), bottom.into()); + } + + Value::Dict(dict) + } +} + +impl<T: Resolve> Resolve for Sides<T> { + type Output = Sides<T::Output>; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|v| v.resolve(styles)) + } +} + +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, + }) + } +} diff --git a/src/geom/smart.rs b/src/geom/smart.rs index e115e99d..c977d651 100644 --- a/src/geom/smart.rs +++ b/src/geom/smart.rs @@ -31,6 +31,18 @@ impl<T> Smart<T> { } } + /// Map the contained custom value with `f` if it contains a custom value, + /// otherwise returns `default`. + pub fn map_or<F, U>(self, default: U, f: F) -> U + where + F: FnOnce(T) -> U, + { + match self { + Self::Auto => default, + Self::Custom(x) => f(x), + } + } + /// Keeps `self` if it contains a custom value, otherwise returns `other`. pub fn or(self, other: Smart<T>) -> Self { match self { @@ -72,3 +84,50 @@ impl<T> Default for Smart<T> { Self::Auto } } + +impl<T: Cast> Cast for Smart<T> { + fn is(value: &Value) -> bool { + matches!(value, Value::Auto) || T::is(value) + } + + fn cast(value: Value) -> StrResult<Self> { + match value { + Value::Auto => Ok(Self::Auto), + v if T::is(&v) => Ok(Self::Custom(T::cast(v)?)), + _ => <Self as Cast>::error(value), + } + } + + fn describe() -> CastInfo { + T::describe() + CastInfo::Type("auto") + } +} + +impl<T: Resolve> Resolve for Smart<T> { + type Output = Smart<T::Output>; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|v| v.resolve(styles)) + } +} + +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: Into<Value>> From<Smart<T>> for Value { + fn from(v: Smart<T>) -> Self { + match v { + Smart::Custom(v) => v.into(), + Smart::Auto => Value::Auto, + } + } +} diff --git a/src/geom/stroke.rs b/src/geom/stroke.rs index 86191d33..500a4c10 100644 --- a/src/geom/stroke.rs +++ b/src/geom/stroke.rs @@ -58,3 +58,37 @@ impl<T: Debug> Debug for PartialStroke<T> { } } } + +cast_from_value! { + PartialStroke: "stroke", + thickness: Length => Self { + paint: Smart::Auto, + thickness: Smart::Custom(thickness), + }, + color: Color => Self { + paint: Smart::Custom(color.into()), + thickness: Smart::Auto, + }, +} + +impl Resolve for PartialStroke { + type Output = PartialStroke<Abs>; + + fn resolve(self, styles: StyleChain) -> Self::Output { + PartialStroke { + paint: self.paint, + thickness: self.thickness.resolve(styles), + } + } +} + +impl Fold for PartialStroke<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), + } + } +} diff --git a/src/ide/complete.rs b/src/ide/complete.rs index 06ab53a1..f5eece93 100644 --- a/src/ide/complete.rs +++ b/src/ide/complete.rs @@ -338,6 +338,11 @@ fn field_access_completions(ctx: &mut CompletionContext, value: &Value) { } } } + Value::Content(content) => { + for (name, value) in content.fields() { + ctx.value_completion(Some(name.clone()), value, false, None); + } + } Value::Dict(dict) => { for (name, value) in dict.iter() { ctx.value_completion(Some(name.clone().into()), value, false, None); @@ -39,14 +39,13 @@ extern crate self as typst; #[macro_use] pub mod util; #[macro_use] -pub mod geom; -#[macro_use] pub mod diag; #[macro_use] pub mod eval; pub mod doc; pub mod export; pub mod font; +pub mod geom; pub mod ide; pub mod image; pub mod model; diff --git a/src/model/content.rs b/src/model/content.rs index b10a3409..2af4ae72 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -1,27 +1,24 @@ -use std::any::{Any, TypeId}; -use std::fmt::{self, Debug, Formatter}; +use std::any::TypeId; +use std::fmt::{self, Debug, Formatter, Write}; use std::hash::{Hash, Hasher}; use std::iter::{self, Sum}; use std::ops::{Add, AddAssign}; -use std::sync::Arc; use comemo::Tracked; use ecow::{EcoString, EcoVec}; -use siphasher::sip128::{Hasher128, SipHasher}; -use typst_macros::node; -use super::{capability, capable, Guard, Key, Property, Recipe, Style, StyleMap}; +use super::{node, Guard, Key, Property, Recipe, Style, StyleMap}; use crate::diag::{SourceResult, StrResult}; -use crate::eval::{Args, ParamInfo, Value, Vm}; +use crate::eval::{cast_from_value, Args, Cast, ParamInfo, Value, Vm}; use crate::syntax::Span; -use crate::util::ReadableTypeId; use crate::World; /// Composable representation of styled content. #[derive(Clone, Hash)] pub struct Content { - obj: Arc<dyn Bounds>, + id: NodeId, span: Option<Span>, + fields: EcoVec<(EcoString, Value)>, modifiers: EcoVec<Modifier>, } @@ -30,55 +27,43 @@ pub struct Content { enum Modifier { Prepared, Guard(Guard), - Label(Label), - Field(EcoString, Value), } impl Content { + pub fn new<T: Node>() -> Self { + Self { + id: T::id(), + span: None, + fields: EcoVec::new(), + modifiers: EcoVec::new(), + } + } + /// Create empty content. pub fn empty() -> Self { - SequenceNode(vec![]).pack() + SequenceNode::new(vec![]).pack() } /// Create a new sequence node from multiples nodes. pub fn sequence(seq: Vec<Self>) -> Self { match seq.as_slice() { [_] => seq.into_iter().next().unwrap(), - _ => SequenceNode(seq).pack(), + _ => SequenceNode::new(seq).pack(), } } /// Attach a span to the content. pub fn spanned(mut self, span: Span) -> Self { - if let Some(styled) = self.to_mut::<StyledNode>() { - styled.sub.span = Some(span); - } else if let Some(styled) = self.to::<StyledNode>() { - self = StyledNode { - sub: styled.sub.clone().spanned(span), - map: styled.map.clone(), - } - .pack(); + if let Some(styled) = self.to::<StyledNode>() { + self = StyledNode::new(styled.sub().spanned(span), styled.map()).pack(); } self.span = Some(span); self } /// Attach a label to the content. - pub fn labelled(mut self, label: Label) -> Self { - for (i, modifier) in self.modifiers.iter().enumerate() { - if matches!(modifier, Modifier::Label(_)) { - self.modifiers.make_mut()[i] = Modifier::Label(label); - return self; - } - } - - self.modifiers.push(Modifier::Label(label)); - self - } - - /// Attach a field to the content. - pub fn push_field(&mut self, name: impl Into<EcoString>, value: Value) { - self.modifiers.push(Modifier::Field(name.into(), value)); + pub fn labelled(self, label: Label) -> Self { + self.with_field("label", label) } /// Style this content with a single style property. @@ -87,31 +72,21 @@ impl Content { } /// Style this content with a style entry. - pub fn styled_with_entry(mut self, style: Style) -> Self { - if let Some(styled) = self.to_mut::<StyledNode>() { - styled.map.apply_one(style); - self - } else if let Some(styled) = self.to::<StyledNode>() { - let mut map = styled.map.clone(); - map.apply_one(style); - StyledNode { sub: styled.sub.clone(), map }.pack() - } else { - StyledNode { sub: self, map: style.into() }.pack() - } + pub fn styled_with_entry(self, style: Style) -> Self { + self.styled_with_map(style.into()) } /// Style this content with a full style map. - pub fn styled_with_map(mut self, styles: StyleMap) -> Self { + pub fn styled_with_map(self, styles: StyleMap) -> Self { if styles.is_empty() { - return self; - } - - if let Some(styled) = self.to_mut::<StyledNode>() { - styled.map.apply(styles); - return self; + self + } else if let Some(styled) = self.to::<StyledNode>() { + let mut map = styled.map(); + map.apply(styles); + StyledNode::new(styled.sub(), map).pack() + } else { + StyledNode::new(self, styles).pack() } - - StyledNode { sub: self, map: styles }.pack() } /// Style this content with a recipe, eagerly applying it if possible. @@ -139,12 +114,12 @@ impl Content { impl Content { /// The id of the contained node. pub fn id(&self) -> NodeId { - (*self.obj).id() + self.id } /// The node's human-readable name. pub fn name(&self) -> &'static str { - (*self.obj).name() + self.id.name() } /// The node's span. @@ -154,72 +129,86 @@ impl Content { /// The content's label. pub fn label(&self) -> Option<&Label> { - self.modifiers.iter().find_map(|modifier| match modifier { - Modifier::Label(label) => Some(label), + match self.field("label")? { + Value::Label(label) => Some(label), _ => None, - }) + } } - /// Access a field on this content. - pub fn field(&self, name: &str) -> Option<Value> { - if name == "label" { - return Some(match self.label() { - Some(label) => Value::Label(label.clone()), - None => Value::None, - }); - } + pub fn with_field( + mut self, + name: impl Into<EcoString>, + value: impl Into<Value>, + ) -> Self { + self.push_field(name, value); + self + } - for modifier in &self.modifiers { - if let Modifier::Field(other, value) = modifier { - if name == other { - return Some(value.clone()); - } - } + /// Attach a field to the content. + pub fn push_field(&mut self, name: impl Into<EcoString>, value: impl Into<Value>) { + let name = name.into(); + if let Some(i) = self.fields.iter().position(|(field, _)| *field == name) { + self.fields.make_mut()[i] = (name, value.into()); + } else { + self.fields.push((name, value.into())); } + } - self.obj.field(name) + pub fn field(&self, name: &str) -> Option<&Value> { + static NONE: Value = Value::None; + self.fields + .iter() + .find(|(field, _)| field == name) + .map(|(_, value)| value) + .or_else(|| (name == "label").then(|| &NONE)) + } + + pub fn fields(&self) -> &[(EcoString, Value)] { + &self.fields + } + + #[track_caller] + pub fn cast_field<T: Cast>(&self, name: &str) -> T { + match self.field(name) { + Some(value) => value.clone().cast().unwrap(), + None => field_is_missing(name), + } } /// Whether the contained node is of type `T`. pub fn is<T>(&self) -> bool where - T: Capable + 'static, + T: Node + 'static, { - (*self.obj).as_any().is::<T>() + self.id == NodeId::of::<T>() } /// Cast to `T` if the contained node is of type `T`. pub fn to<T>(&self) -> Option<&T> where - T: Capable + 'static, + T: Node + 'static, { - (*self.obj).as_any().downcast_ref::<T>() + self.is::<T>().then(|| unsafe { std::mem::transmute(self) }) } /// Whether this content has the given capability. pub fn has<C>(&self) -> bool where - C: Capability + ?Sized, + C: ?Sized + 'static, { - self.obj.vtable(TypeId::of::<C>()).is_some() + (self.id.0.vtable)(TypeId::of::<C>()).is_some() } /// Cast to a trait object if this content has the given capability. pub fn with<C>(&self) -> Option<&C> where - C: Capability + ?Sized, + C: ?Sized + 'static, { - let node: &dyn Bounds = &*self.obj; - let vtable = node.vtable(TypeId::of::<C>())?; - let data = node as *const dyn Bounds as *const (); + let vtable = (self.id.0.vtable)(TypeId::of::<C>())?; + let data = self as *const Self as *const (); Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) }) } - /// Try to cast to a mutable instance of `T`. - fn to_mut<T: 'static>(&mut self) -> Option<&mut T> { - Arc::get_mut(&mut self.obj)?.as_any_mut().downcast_mut::<T>() - } - /// Disable a show rule recipe. #[doc(hidden)] pub fn guarded(mut self, id: Guard) -> Self { @@ -262,12 +251,40 @@ impl Content { pub(super) fn copy_modifiers(&mut self, from: &Content) { self.span = from.span; self.modifiers = from.modifiers.clone(); + if let Some(label) = from.label() { + self.push_field("label", label.clone()) + } } } impl Debug for Content { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.obj.fmt(f) + struct Pad<'a>(&'a str); + impl Debug for Pad<'_> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(self.0) + } + } + + if let Some(styled) = self.to::<StyledNode>() { + styled.map().fmt(f)?; + styled.sub().fmt(f) + } else if let Some(seq) = self.to::<SequenceNode>() { + f.debug_list().entries(&seq.children()).finish() + } else if self.id.name() == "space" { + ' '.fmt(f) + } else if self.id.name() == "text" { + self.field("text").unwrap().fmt(f) + } else { + f.write_str(self.name())?; + if self.fields.is_empty() { + return Ok(()); + } + f.write_char(' ')?; + f.debug_map() + .entries(self.fields.iter().map(|(name, value)| (Pad(name), value))) + .finish() + } } } @@ -280,27 +297,19 @@ impl Default for Content { impl Add for Content { type Output = Self; - fn add(self, mut rhs: Self) -> Self::Output { - let mut lhs = self; - if let Some(lhs_mut) = lhs.to_mut::<SequenceNode>() { - if let Some(rhs_mut) = rhs.to_mut::<SequenceNode>() { - lhs_mut.0.append(&mut rhs_mut.0); - } else if let Some(rhs) = rhs.to::<SequenceNode>() { - lhs_mut.0.extend(rhs.0.iter().cloned()); - } else { - lhs_mut.0.push(rhs); - } - return lhs; - } - + fn add(self, rhs: Self) -> Self::Output { + let lhs = self; let seq = match (lhs.to::<SequenceNode>(), rhs.to::<SequenceNode>()) { - (Some(lhs), Some(rhs)) => lhs.0.iter().chain(&rhs.0).cloned().collect(), - (Some(lhs), None) => lhs.0.iter().cloned().chain(iter::once(rhs)).collect(), - (None, Some(rhs)) => iter::once(lhs).chain(rhs.0.iter().cloned()).collect(), + (Some(lhs), Some(rhs)) => { + lhs.children().into_iter().chain(rhs.children()).collect() + } + (Some(lhs), None) => { + lhs.children().into_iter().chain(iter::once(rhs)).collect() + } + (None, Some(rhs)) => iter::once(lhs).chain(rhs.children()).collect(), (None, None) => vec![lhs, rhs], }; - - SequenceNode(seq).pack() + SequenceNode::new(seq).pack() } } @@ -316,73 +325,33 @@ impl Sum for Content { } } -trait Bounds: Node + Debug + Sync + Send + 'static { - fn as_any(&self) -> &dyn Any; - fn as_any_mut(&mut self) -> &mut dyn Any; - fn hash128(&self) -> u128; -} - -impl<T> Bounds for T -where - T: Node + Debug + Hash + Sync + Send + 'static, -{ - fn as_any(&self) -> &dyn Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - - fn hash128(&self) -> u128 { - let mut state = SipHasher::new(); - self.type_id().hash(&mut state); - self.hash(&mut state); - state.finish128().as_u128() - } -} - -impl Hash for dyn Bounds { - fn hash<H: Hasher>(&self, state: &mut H) { - state.write_u128(self.hash128()); - } -} - /// A node with applied styles. -#[capable] -#[derive(Clone, Hash)] +#[node] pub struct StyledNode { /// The styled content. + #[positional] + #[required] pub sub: Content, + /// The styles. + #[positional] + #[required] pub map: StyleMap, } -#[node] -impl StyledNode {} - -impl Debug for StyledNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.map.fmt(f)?; - self.sub.fmt(f) - } +cast_from_value! { + StyleMap: "style map", } /// A sequence of nodes. /// /// Combines other arbitrary content. So, when you write `[Hi] + [you]` in /// Typst, the two text nodes are combined into a single sequence node. -#[capable] -#[derive(Clone, Hash)] -pub struct SequenceNode(pub Vec<Content>); - #[node] -impl SequenceNode {} - -impl Debug for SequenceNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_list().entries(self.0.iter()).finish() - } +pub struct SequenceNode { + #[variadic] + #[required] + pub children: Vec<Content>, } /// A label for a node. @@ -396,80 +365,83 @@ impl Debug for Label { } /// A constructable, stylable content node. -pub trait Node: 'static + Capable { +pub trait Node: Construct + Set + Sized + 'static { + /// The node's ID. + fn id() -> NodeId; + /// Pack a node into type-erased content. - fn pack(self) -> Content - where - Self: Node + Debug + Hash + Sync + Send + Sized + 'static, - { - Content { - obj: Arc::new(self), - span: None, - modifiers: EcoVec::new(), - } - } + fn pack(self) -> Content; +} - /// A unique identifier of the node type. - fn id(&self) -> NodeId; +/// A unique identifier for a node. +#[derive(Copy, Clone)] +pub struct NodeId(&'static NodeMeta); - /// The node's name. - fn name(&self) -> &'static str; +impl NodeId { + pub fn of<T: Node>() -> Self { + T::id() + } - /// Construct a node from the arguments. - /// - /// This is passed only the arguments that remain after execution of the - /// node's set rule. - fn construct(vm: &Vm, args: &mut Args) -> SourceResult<Content> - where - Self: Sized; + pub fn from_meta(meta: &'static NodeMeta) -> Self { + Self(meta) + } - /// Parse relevant arguments into style properties for this node. - /// - /// When `constructor` is true, [`construct`](Self::construct) will run - /// after this invocation of `set` with the remaining arguments. - fn set(args: &mut Args, constructor: bool) -> SourceResult<StyleMap> - where - Self: Sized; + /// The name of the identified node. + pub fn name(self) -> &'static str { + self.0.name + } +} - /// List the settable properties. - fn properties() -> Vec<ParamInfo> - where - Self: Sized; +impl Debug for NodeId { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(self.name()) + } +} - /// Access a field on this node. - fn field(&self, name: &str) -> Option<Value>; +impl Hash for NodeId { + fn hash<H: Hasher>(&self, state: &mut H) { + state.write_usize(self.0 as *const _ as usize); + } } -/// A unique identifier for a node type. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct NodeId(ReadableTypeId); +impl Eq for NodeId {} -impl NodeId { - /// The id of the given node type. - pub fn of<T: 'static>() -> Self { - Self(ReadableTypeId::of::<T>()) +impl PartialEq for NodeId { + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(self.0, other.0) } } -impl Debug for NodeId { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.0.fmt(f) - } +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct NodeMeta { + pub name: &'static str, + pub vtable: fn(of: TypeId) -> Option<*const ()>, } -/// A capability a node can have. -/// -/// Should be implemented by trait objects that are accessible through -/// [`Capable`]. -pub trait Capability: 'static {} - -/// Dynamically access a trait implementation at runtime. -pub unsafe trait Capable { - /// Return the vtable pointer of the trait object with given type `id` - /// if `self` implements the trait. - fn vtable(&self, of: TypeId) -> Option<*const ()>; +pub trait Construct { + /// Construct a node from the arguments. + /// + /// This is passed only the arguments that remain after execution of the + /// node's set rule. + fn construct(vm: &Vm, args: &mut Args) -> SourceResult<Content>; +} + +pub trait Set { + /// Parse relevant arguments into style properties for this node. + /// + /// When `constructor` is true, [`construct`](Construct::construct) will run + /// after this invocation of `set` with the remaining arguments. + fn set(args: &mut Args, constructor: bool) -> SourceResult<StyleMap>; + + /// List the settable properties. + fn properties() -> Vec<ParamInfo>; } /// Indicates that a node cannot be labelled. -#[capability] pub trait Unlabellable {} + +#[cold] +#[track_caller] +fn field_is_missing(name: &str) -> ! { + panic!("required field `{name}` is missing") +} diff --git a/src/model/mod.rs b/src/model/mod.rs index 692d18d5..07329e3f 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -13,4 +13,4 @@ pub use self::typeset::*; #[doc(hidden)] pub use once_cell; -pub use typst_macros::{capability, capable, node}; +pub use typst_macros::node; diff --git a/src/model/realize.rs b/src/model/realize.rs index b33cc0bb..2f38df51 100644 --- a/src/model/realize.rs +++ b/src/model/realize.rs @@ -1,4 +1,4 @@ -use super::{capability, Content, NodeId, Recipe, Selector, StyleChain, Vt}; +use super::{Content, NodeId, Recipe, Selector, StyleChain, Vt}; use crate::diag::SourceResult; /// Whether the target is affected by show rules in the given style chain. @@ -105,7 +105,7 @@ fn try_apply( let mut result = vec![]; let mut cursor = 0; - for m in regex.find_iter(text) { + for m in regex.find_iter(&text) { let start = m.start(); if cursor < start { result.push(make(text[cursor..start].into())); @@ -133,7 +133,6 @@ fn try_apply( } /// Preparations before execution of any show rule. -#[capability] pub trait Prepare { /// Prepare the node for show rule application. fn prepare( @@ -145,7 +144,6 @@ pub trait Prepare { } /// The base recipe for a node. -#[capability] pub trait Show { /// Execute the base recipe for this node. fn show( @@ -157,7 +155,6 @@ pub trait Show { } /// Post-process a node after it was realized. -#[capability] pub trait Finalize { /// Finalize the fully realized form of the node. Use this for effects that /// should work even in the face of a user-defined show rule, for example diff --git a/src/model/styles.rs b/src/model/styles.rs index 18507491..cbf4cfb2 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -1,21 +1,16 @@ use std::any::Any; use std::fmt::{self, Debug, Formatter}; -use std::hash::Hash; +use std::hash::{Hash, Hasher}; use std::iter; use std::marker::PhantomData; -use std::sync::Arc; -use comemo::{Prehashed, Tracked}; +use comemo::Tracked; +use ecow::EcoString; -use super::{Content, Label, NodeId}; +use super::{Content, Label, Node, NodeId}; use crate::diag::{SourceResult, Trace, Tracepoint}; -use crate::eval::{Args, Dict, Func, Regex, Value}; -use crate::geom::{ - Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides, - Smart, -}; +use crate::eval::{cast_from_value, Args, Cast, Dict, Func, Regex, Value}; use crate::syntax::Span; -use crate::util::ReadableTypeId; use crate::World; /// A map of style properties. @@ -76,7 +71,7 @@ impl StyleMap { /// Mark all contained properties as _scoped_. This means that they only /// apply to the first descendant node (of their type) in the hierarchy and /// not its children, too. This is used by - /// [constructors](super::Node::construct). + /// [constructors](super::Construct::construct). pub fn scoped(mut self) -> Self { for entry in &mut self.0 { if let Style::Property(property) = entry { @@ -98,7 +93,7 @@ impl StyleMap { /// Returns `Some(_)` with an optional span if this map contains styles for /// the given `node`. - pub fn interruption<T: 'static>(&self) -> Option<Option<Span>> { + pub fn interruption<T: Node>(&self) -> Option<Option<Span>> { let node = NodeId::of::<T>(); self.0.iter().find_map(|entry| match entry { Style::Property(property) => property.is_of(node).then(|| property.origin), @@ -114,6 +109,12 @@ impl From<Style> for StyleMap { } } +impl PartialEq for StyleMap { + fn eq(&self, other: &Self) -> bool { + crate::util::hash128(self) == crate::util::hash128(other) + } +} + impl Debug for StyleMap { fn fmt(&self, f: &mut Formatter) -> fmt::Result { for entry in self.0.iter() { @@ -154,13 +155,11 @@ impl Style { impl Debug for Style { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("#[")?; match self { - Self::Property(property) => property.fmt(f)?, - Self::Recipe(recipe) => recipe.fmt(f)?, - Self::Barrier(id) => write!(f, "Barrier for {id:?}")?, + Self::Property(property) => property.fmt(f), + Self::Recipe(recipe) => recipe.fmt(f), + Self::Barrier(id) => write!(f, "#[Barrier for {id:?}]"), } - f.write_str("]") } } @@ -175,12 +174,9 @@ pub struct Property { /// hierarchy. Used by constructors. scoped: bool, /// The property's value. - value: Arc<Prehashed<dyn Bounds>>, + value: Value, /// The span of the set rule the property stems from. origin: Option<Span>, - /// The name of the property. - #[cfg(debug_assertions)] - name: &'static str, } impl Property { @@ -189,11 +185,9 @@ impl Property { Self { key: KeyId::of::<K>(), node: K::node(), - value: Arc::new(Prehashed::new(value)), + value: value.into(), scoped: false, origin: None, - #[cfg(debug_assertions)] - name: K::NAME, } } @@ -208,9 +202,12 @@ impl Property { } /// Access the property's value if it is of the given key. - pub fn downcast<K: Key>(&self) -> Option<&K::Value> { + #[track_caller] + pub fn cast<K: Key>(&self) -> Option<K::Value> { if self.key == KeyId::of::<K>() { - (**self.value).as_any().downcast_ref() + Some(self.value.clone().cast().unwrap_or_else(|err| { + panic!("{} (for {} with value {:?})", err, self.key.name(), self.value) + })) } else { None } @@ -234,9 +231,7 @@ impl Property { impl Debug for Property { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - #[cfg(debug_assertions)] - write!(f, "{} = ", self.name)?; - write!(f, "{:?}", self.value)?; + write!(f, "#set {}({}: {:?})", self.node.name(), self.key.name(), self.value)?; if self.scoped { write!(f, " [scoped]")?; } @@ -267,47 +262,69 @@ where /// A style property key. /// -/// This trait is not intended to be implemented manually, but rather through -/// the `#[node]` proc-macro. +/// This trait is not intended to be implemented manually. pub trait Key: Copy + 'static { /// The unfolded type which this property is stored as in a style map. - type Value: Debug + Clone + Hash + Sync + Send + 'static; + type Value: Cast + Into<Value>; /// The folded type of value that is returned when reading this property /// from a style chain. - type Output<'a>; + type Output; - /// The name of the property, used for debug printing. - const NAME: &'static str; + /// The id of the property. + fn id() -> KeyId; /// The id of the node the key belongs to. fn node() -> NodeId; /// Compute an output value from a sequence of values belonging to this key, /// folding if necessary. - fn get<'a>( - chain: StyleChain<'a>, - values: impl Iterator<Item = &'a Self::Value>, - ) -> Self::Output<'a>; + fn get(chain: StyleChain, values: impl Iterator<Item = Self::Value>) -> Self::Output; } -/// A unique identifier for a property key. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -struct KeyId(ReadableTypeId); +/// A unique identifier for a style key. +#[derive(Copy, Clone)] +pub struct KeyId(&'static KeyMeta); impl KeyId { - /// The id of the given key. pub fn of<T: Key>() -> Self { - Self(ReadableTypeId::of::<T>()) + T::id() + } + + pub fn from_meta(meta: &'static KeyMeta) -> Self { + Self(meta) + } + + pub fn name(self) -> &'static str { + self.0.name } } impl Debug for KeyId { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.0.fmt(f) + f.pad(self.name()) + } +} + +impl Hash for KeyId { + fn hash<H: Hasher>(&self, state: &mut H) { + state.write_usize(self.0 as *const _ as usize); + } +} + +impl Eq for KeyId {} + +impl PartialEq for KeyId { + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(self.0, other.0) } } +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct KeyMeta { + pub name: &'static str, +} + /// A show rule recipe. #[derive(Clone, Hash)] pub struct Recipe { @@ -362,7 +379,7 @@ impl Recipe { impl Debug for Recipe { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Recipe matching {:?}", self.selector) + write!(f, "#show {:?}: {:?}", self.selector, self.transform) } } @@ -382,7 +399,7 @@ pub enum Selector { impl Selector { /// Define a simple node selector. - pub fn node<T: 'static>() -> Self { + pub fn node<T: Node>() -> Self { Self::Node(NodeId::of::<T>(), None) } @@ -399,17 +416,25 @@ impl Selector { && dict .iter() .flat_map(|dict| dict.iter()) - .all(|(name, value)| target.field(name).as_ref() == Some(value)) + .all(|(name, value)| target.field(name) == Some(value)) } Self::Label(label) => target.label() == Some(label), Self::Regex(regex) => { target.id() == item!(text_id) - && item!(text_str)(target).map_or(false, |text| regex.is_match(text)) + && item!(text_str)(target).map_or(false, |text| regex.is_match(&text)) } } } } +cast_from_value! { + Selector: "selector", + text: EcoString => Self::text(&text), + label: Label => Self::Label(label), + func: Func => func.select(None)?, + regex: Regex => Self::Regex(regex), +} + /// A show rule transformation that can be applied to a match. #[derive(Debug, Clone, Hash)] pub enum Transform { @@ -421,6 +446,17 @@ pub enum Transform { Style(StyleMap), } +cast_from_value! { + Transform, + content: Content => Self::Content(content), + func: Func => { + if func.argc().map_or(false, |count| count != 1) { + Err("function must have exactly one parameter")? + } + Self::Func(func) + }, +} + /// A chain of style maps, similar to a linked list. /// /// A style chain allows to combine properties from multiple style maps in a @@ -478,7 +514,7 @@ impl<'a> StyleChain<'a> { /// Returns the property's default value if no map in the chain contains an /// entry for it. Also takes care of resolving and folding and returns /// references where applicable. - pub fn get<K: Key>(self, key: K) -> K::Output<'a> { + pub fn get<K: Key>(self, key: K) -> K::Output { K::get(self, self.values(key)) } @@ -534,10 +570,7 @@ impl Debug for StyleChain<'_> { impl PartialEq for StyleChain<'_> { fn eq(&self, other: &Self) -> bool { - let as_ptr = |s| s as *const _; - self.head.as_ptr() == other.head.as_ptr() - && self.head.len() == other.head.len() - && self.tail.map(as_ptr) == other.tail.map(as_ptr) + crate::util::hash128(self) == crate::util::hash128(other) } } @@ -585,13 +618,14 @@ struct Values<'a, K> { } impl<'a, K: Key> Iterator for Values<'a, K> { - type Item = &'a K::Value; + type Item = K::Value; + #[track_caller] fn next(&mut self) -> Option<Self::Item> { for entry in &mut self.entries { match entry { Style::Property(property) => { - if let Some(value) = property.downcast::<K>() { + if let Some(value) = property.cast::<K>() { if !property.scoped() || self.barriers <= 1 { return Some(value); } @@ -672,6 +706,20 @@ impl<T> StyleVec<T> { } } +impl StyleVec<Content> { + pub fn to_vec(self) -> Vec<Content> { + self.items + .into_iter() + .zip( + self.maps + .iter() + .flat_map(|(map, count)| iter::repeat(map).take(*count)), + ) + .map(|(content, map)| content.styled_with_map(map.clone())) + .collect() + } +} + impl<T> Default for StyleVec<T> { fn default() -> Self { Self { items: vec![], maps: vec![] } @@ -791,26 +839,6 @@ pub trait Resolve { fn resolve(self, styles: StyleChain) -> Self::Output; } -impl Resolve for Em { - type Output = Abs; - - fn resolve(self, styles: StyleChain) -> Self::Output { - if self.is_zero() { - Abs::zero() - } else { - self.at(item!(em)(styles)) - } - } -} - -impl Resolve for Length { - type Output = Abs; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.abs + self.em.resolve(styles) - } -} - impl<T: Resolve> Resolve for Option<T> { type Output = Option<T::Output>; @@ -819,74 +847,6 @@ impl<T: Resolve> Resolve for Option<T> { } } -impl<T: Resolve> Resolve for Smart<T> { - type Output = Smart<T::Output>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl<T: Resolve> Resolve for Axes<T> { - type Output = Axes<T::Output>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl<T: Resolve> Resolve for Sides<T> { - type Output = Sides<T::Output>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl<T: Resolve> Resolve for Corners<T> { - type Output = Corners<T::Output>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl<T> Resolve for Rel<T> -where - T: Resolve + Numeric, - <T as Resolve>::Output: Numeric, -{ - type Output = Rel<<T as Resolve>::Output>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|abs| abs.resolve(styles)) - } -} - -impl Resolve for GenAlign { - type Output = Align; - - fn resolve(self, styles: StyleChain) -> Self::Output { - let dir = item!(dir)(styles); - match self { - Self::Start => dir.start().into(), - Self::End => dir.end().into(), - Self::Specific(align) => align, - } - } -} - -impl Resolve for PartialStroke { - type Output = PartialStroke<Abs>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - PartialStroke { - paint: self.paint, - thickness: self.thickness.resolve(styles), - } - } -} - /// A property that is folded to determine its final value. pub trait Fold { /// The type of the folded output. @@ -907,92 +867,3 @@ where self.map(|inner| inner.fold(outer.unwrap_or_default())) } } - -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 for Axes<Option<T>> -where - T: Fold, -{ - type Output = Axes<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, - }) - } -} - -impl<T> Fold for Sides<Option<T>> -where - T: Fold, -{ - 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, - }) - } -} - -impl<T> Fold for Corners<Option<T>> -where - T: Fold, -{ - 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, - }) - } -} - -impl Fold for PartialStroke<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), - } - } -} - -impl Fold for Rel<Length> { - type Output = Self; - - fn fold(self, _: Self::Output) -> Self::Output { - self - } -} - -impl Fold for Rel<Abs> { - type Output = Self; - - fn fold(self, _: Self::Output) -> Self::Output { - self - } -} - -impl Fold for GenAlign { - type Output = Self; - - fn fold(self, _: Self::Output) -> Self::Output { - self - } -} diff --git a/src/model/typeset.rs b/src/model/typeset.rs index f8b5e012..6361e6ce 100644 --- a/src/model/typeset.rs +++ b/src/model/typeset.rs @@ -8,7 +8,6 @@ use comemo::{Track, Tracked, TrackedMut}; use super::{Content, Selector, StyleChain}; use crate::diag::SourceResult; use crate::doc::{Document, Element, Frame, Location, Meta}; -use crate::eval::Value; use crate::geom::Transform; use crate::util::hash128; use crate::World; @@ -162,7 +161,7 @@ impl Introspector { let pos = pos.transform(ts); let mut node = content.clone(); let loc = Location { page, pos }; - node.push_field("loc", Value::Dict(loc.encode())); + node.push_field("loc", loc); self.nodes.push((id, node)); } } diff --git a/src/util/mod.rs b/src/util/mod.rs index ce0ed1aa..54b9fe27 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -60,10 +60,7 @@ pub trait ArcExt<T> { fn take(self) -> T; } -impl<T> ArcExt<T> for Arc<T> -where - T: Clone, -{ +impl<T: Clone> ArcExt<T> for Arc<T> { fn take(self) -> T { match Arc::try_unwrap(self) { Ok(v) => v, |
