diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/eval/value.rs | 144 | ||||
| -rw-r--r-- | src/library/deco.rs | 2 | ||||
| -rw-r--r-- | src/library/mod.rs | 8 | ||||
| -rw-r--r-- | src/library/page.rs | 24 | ||||
| -rw-r--r-- | src/library/shape.rs | 6 | ||||
| -rw-r--r-- | src/library/text.rs | 27 | ||||
| -rw-r--r-- | src/style/mod.rs | 35 |
7 files changed, 161 insertions, 85 deletions
diff --git a/src/eval/value.rs b/src/eval/value.rs index e224438a..dec5c6c0 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -252,43 +252,6 @@ pub trait Cast<V>: Sized { fn cast(value: V) -> StrResult<Self>; } -impl Cast<Value> for Value { - fn is(_: &Value) -> bool { - true - } - - fn cast(value: Value) -> StrResult<Self> { - Ok(value) - } -} - -impl<T> Cast<Spanned<Value>> for T -where - T: Cast<Value>, -{ - fn is(value: &Spanned<Value>) -> bool { - T::is(&value.v) - } - - fn cast(value: Spanned<Value>) -> StrResult<Self> { - T::cast(value.v) - } -} - -impl<T> Cast<Spanned<Value>> for Spanned<T> -where - T: Cast<Value>, -{ - fn is(value: &Spanned<Value>) -> bool { - T::is(&value.v) - } - - fn cast(value: Spanned<Value>) -> StrResult<Self> { - let span = value.span; - T::cast(value.v).map(|t| Spanned::new(t, span)) - } -} - /// Implement traits for primitives. macro_rules! primitive { ( @@ -400,6 +363,113 @@ primitive! { Dict: "dictionary", Dict } primitive! { Template: "template", Template } primitive! { Function: "function", Func } +impl Cast<Value> for Value { + fn is(_: &Value) -> bool { + true + } + + fn cast(value: Value) -> StrResult<Self> { + Ok(value) + } +} + +impl<T> Cast<Spanned<Value>> for T +where + T: Cast<Value>, +{ + fn is(value: &Spanned<Value>) -> bool { + T::is(&value.v) + } + + fn cast(value: Spanned<Value>) -> StrResult<Self> { + T::cast(value.v) + } +} + +impl<T> Cast<Spanned<Value>> for Spanned<T> +where + T: Cast<Value>, +{ + fn is(value: &Spanned<Value>) -> bool { + T::is(&value.v) + } + + fn cast(value: Spanned<Value>) -> StrResult<Self> { + let span = value.span; + T::cast(value.v).map(|t| Spanned::new(t, span)) + } +} + +/// A value that can be automatically determined. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum Smart<T> { + /// The value should be determined smartly based on the + /// circumstances. + Auto, + /// A forced, specific value. + Custom(T), +} + +impl<T> Smart<T> { + /// Returns the contained custom value or a provided default value. + pub fn unwrap_or(self, default: T) -> T { + match self { + Self::Auto => default, + Self::Custom(x) => x, + } + } +} + +impl<T> Default for Smart<T> { + fn default() -> Self { + Self::Auto + } +} + +impl<T> Cast<Value> for Option<T> +where + T: Cast<Value>, +{ + fn is(value: &Value) -> bool { + matches!(value, Value::None) || T::is(value) + } + + fn cast(value: Value) -> StrResult<Self> { + match value { + Value::None => Ok(None), + v => T::cast(v).map(Some).map_err(|msg| with_alternative(msg, "none")), + } + } +} + +impl<T> Cast<Value> for Smart<T> +where + T: Cast<Value>, +{ + 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 => T::cast(v) + .map(Self::Custom) + .map_err(|msg| with_alternative(msg, "auto")), + } + } +} + +/// Transform `expected X, found Y` into `expected X or A, found Y`. +fn with_alternative(msg: String, alt: &str) -> String { + let mut parts = msg.split(", found "); + if let (Some(a), Some(b)) = (parts.next(), parts.next()) { + format!("{} or {}, found {}", a, alt, b) + } else { + msg + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/library/deco.rs b/src/library/deco.rs index 1f8c051f..cb065689 100644 --- a/src/library/deco.rs +++ b/src/library/deco.rs @@ -17,7 +17,7 @@ pub fn overline(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { } fn line_impl(args: &mut Args, kind: LineKind) -> TypResult<Value> { - let stroke = args.named("stroke")?.or_else(|| args.find()).map(Paint::Solid); + let stroke = args.named("stroke")?.or_else(|| args.find()); let thickness = args.named::<Linear>("thickness")?.or_else(|| args.find()); let offset = args.named("offset")?; let extent = args.named("extent")?.unwrap_or_default(); diff --git a/src/library/mod.rs b/src/library/mod.rs index 7b8acf9e..6260e6fc 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -26,7 +26,7 @@ mod prelude { pub use std::rc::Rc; pub use crate::diag::{At, TypResult}; - pub use crate::eval::{Args, EvalContext, Template, Value}; + pub use crate::eval::{Args, EvalContext, Smart, Template, Value}; pub use crate::frame::*; pub use crate::geom::*; pub use crate::layout::*; @@ -144,3 +144,9 @@ dynamic! { FontFamily: "font family", Value::Str(string) => Self::Named(string.to_lowercase()), } + +castable! { + Paint, + Expected: "color", + Value::Color(color) => Paint::Solid(color), +} diff --git a/src/library/page.rs b/src/library/page.rs index 20871bd9..b256a521 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -12,13 +12,13 @@ pub fn page(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { let paper = args.named::<Paper>("paper")?.or_else(|| args.find()); let width = args.named("width")?; let height = args.named("height")?; + let flip = args.named("flip")?; let margins = args.named("margins")?; let left = args.named("left")?; let top = args.named("top")?; let right = args.named("right")?; let bottom = args.named("bottom")?; - let flip = args.named("flip")?; - let fill = args.named("fill")?.map(Paint::Solid); + let fill = args.named("fill")?; ctx.template.modify(move |style| { let page = style.page_mut(); @@ -33,37 +33,37 @@ pub fn page(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { page.size.w = width; } + if flip.unwrap_or(false) { + std::mem::swap(&mut page.size.w, &mut page.size.h); + } + if let Some(height) = height { page.class = PaperClass::Custom; page.size.h = height; } if let Some(margins) = margins { - page.margins = Sides::splat(Some(margins)); + page.margins = Sides::splat(margins); } if let Some(left) = left { - page.margins.left = Some(left); + page.margins.left = left; } if let Some(top) = top { - page.margins.top = Some(top); + page.margins.top = top; } if let Some(right) = right { - page.margins.right = Some(right); + page.margins.right = right; } if let Some(bottom) = bottom { - page.margins.bottom = Some(bottom); - } - - if flip.unwrap_or(false) { - std::mem::swap(&mut page.size.w, &mut page.size.h); + page.margins.bottom = bottom; } if let Some(fill) = fill { - page.fill = Some(fill); + page.fill = fill; } }); diff --git a/src/library/shape.rs b/src/library/shape.rs index abf927e4..f47da82f 100644 --- a/src/library/shape.rs +++ b/src/library/shape.rs @@ -58,11 +58,11 @@ fn shape_impl( }; // Parse fill & stroke. - let fill = args.named("fill")?.map(Paint::Solid); + let fill = args.named("fill")?.unwrap_or(None); let stroke = match (args.named("stroke")?, args.named("thickness")?) { (None, None) => fill.is_none().then(|| default), - (color, thickness) => Some(Stroke { - paint: color.map(Paint::Solid).unwrap_or(default.paint), + (color, thickness) => color.unwrap_or(Some(default.paint)).map(|paint| Stroke { + paint, thickness: thickness.unwrap_or(default.thickness), }), }; diff --git a/src/library/text.rs b/src/library/text.rs index d0b5c8e6..c0ee80e1 100644 --- a/src/library/text.rs +++ b/src/library/text.rs @@ -93,18 +93,16 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { castable! { StylisticSet, - Expected: "none or integer", - Value::None => Self(None), + Expected: "integer", Value::Int(v) => match v { - 1 ..= 20 => Self(Some(v as u8)), + 1 ..= 20 => Self::new(v as u8), _ => Err("must be between 1 and 20")?, }, } castable! { NumberType, - Expected: "auto or string", - Value::Auto => Self::Auto, + Expected: "string", Value::Str(string) => match string.as_str() { "lining" => Self::Lining, "old-style" => Self::OldStyle, @@ -114,8 +112,7 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { castable! { NumberWidth, - Expected: "auto or string", - Value::Auto => Self::Auto, + Expected: "string", Value::Str(string) => match string.as_str() { "proportional" => Self::Proportional, "tabular" => Self::Tabular, @@ -629,8 +626,8 @@ fn tags(features: &FontFeatures) -> Vec<Feature> { } let storage; - if let StylisticSet(Some(set @ 1 ..= 20)) = features.stylistic_set { - storage = [b's', b's', b'0' + set / 10, b'0' + set % 10]; + if let Some(set) = features.stylistic_set { + storage = [b's', b's', b'0' + set.get() / 10, b'0' + set.get() % 10]; feat(&storage, 1); } @@ -648,15 +645,15 @@ fn tags(features: &FontFeatures) -> Vec<Feature> { } match features.numbers.type_ { - NumberType::Auto => {} - NumberType::Lining => feat(b"lnum", 1), - NumberType::OldStyle => feat(b"onum", 1), + Smart::Auto => {} + Smart::Custom(NumberType::Lining) => feat(b"lnum", 1), + Smart::Custom(NumberType::OldStyle) => feat(b"onum", 1), } match features.numbers.width { - NumberWidth::Auto => {} - NumberWidth::Proportional => feat(b"pnum", 1), - NumberWidth::Tabular => feat(b"tnum", 1), + Smart::Auto => {} + Smart::Custom(NumberWidth::Proportional) => feat(b"pnum", 1), + Smart::Custom(NumberWidth::Tabular) => feat(b"tnum", 1), } match features.numbers.position { diff --git a/src/style/mod.rs b/src/style/mod.rs index 4a8830f8..45dbeb54 100644 --- a/src/style/mod.rs +++ b/src/style/mod.rs @@ -9,6 +9,7 @@ use std::rc::Rc; use ttf_parser::Tag; +use crate::eval::Smart; use crate::font::*; use crate::geom::*; use crate::util::EcoString; @@ -70,7 +71,7 @@ pub struct PageStyle { pub size: Size, /// The amount of white space on each side of the page. If a side is set to /// `None`, the default for the paper class is used. - pub margins: Sides<Option<Linear>>, + pub margins: Sides<Smart<Linear>>, /// The background fill of the page. pub fill: Option<Paint>, } @@ -94,7 +95,7 @@ impl Default for PageStyle { Self { class: paper.class(), size: paper.size(), - margins: Sides::splat(None), + margins: Sides::splat(Smart::Auto), fill: None, } } @@ -301,7 +302,7 @@ pub struct FontFeatures { /// Whether to apply stylistic alternates. ("salt") pub alternates: bool, /// Which stylistic set to apply. ("ss01" - "ss20") - pub stylistic_set: StylisticSet, + pub stylistic_set: Option<StylisticSet>, /// Configuration of ligature features. pub ligatures: LigatureFeatures, /// Configuration of numbers features. @@ -316,7 +317,7 @@ impl Default for FontFeatures { kerning: true, smallcaps: false, alternates: false, - stylistic_set: StylisticSet::default(), + stylistic_set: None, ligatures: LigatureFeatures::default(), numbers: NumberFeatures::default(), raw: vec![], @@ -326,11 +327,17 @@ impl Default for FontFeatures { /// A stylistic set in a font face. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct StylisticSet(pub Option<u8>); +pub struct StylisticSet(u8); -impl Default for StylisticSet { - fn default() -> Self { - Self(None) +impl StylisticSet { + /// Creates a new set, clamping to 1-20. + pub fn new(index: u8) -> Self { + Self(index.clamp(1, 20)) + } + + /// Get the value, guaranteed to be 1-20. + pub fn get(self) -> u8 { + self.0 } } @@ -359,9 +366,9 @@ impl Default for LigatureFeatures { #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct NumberFeatures { /// Whether to use lining or old-style numbers. - pub type_: NumberType, + pub type_: Smart<NumberType>, /// Whether to use proportional or tabular numbers. - pub width: NumberWidth, + pub width: Smart<NumberWidth>, /// How to position numbers vertically. pub position: NumberPosition, /// Whether to have a slash through the zero glyph. ("zero") @@ -373,8 +380,8 @@ pub struct NumberFeatures { impl Default for NumberFeatures { fn default() -> Self { Self { - type_: NumberType::Auto, - width: NumberWidth::Auto, + type_: Smart::Auto, + width: Smart::Auto, position: NumberPosition::Normal, slashed_zero: false, fractions: false, @@ -385,8 +392,6 @@ impl Default for NumberFeatures { /// Which kind of numbers / figures to select. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum NumberType { - /// Select the font's preference. - Auto, /// Numbers that fit well with capital text. ("lnum") Lining, /// Numbers that fit well into flow of upper- and lowercase text. ("onum") @@ -396,8 +401,6 @@ pub enum NumberType { /// The width of numbers / figures. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum NumberWidth { - /// Select the font's preference. - Auto, /// Number widths are glyph specific. ("pnum") Proportional, /// All numbers are of equal width / monospaced. ("tnum") |
