diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-01-02 19:37:10 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-01-02 19:37:10 +0100 |
| commit | 1c40dc42e7bc7b799b77f06d25414aca59a044ba (patch) | |
| tree | ea8bdedaebf59f5bc601346b0108236c7264a29d /src/library/style.rs | |
| parent | 8cad78481cd52680317032c3bb84cacda5666489 (diff) | |
Dynamic values, Types, Arrays, and Dictionaries 🚀
- Identifiers are now evaluated as variables instead of being plain values
- Constants like `left` or `bold` are stored as dynamic values containing the respective rust types
- We now distinguish between arrays and dictionaries to make things more intuitive (at the cost of a bit more complex parsing)
- Spans were removed from collections (arrays, dictionaries), function arguments still have spans for the top-level values to enable good diagnostics
Diffstat (limited to 'src/library/style.rs')
| -rw-r--r-- | src/library/style.rs | 201 |
1 files changed, 118 insertions, 83 deletions
diff --git a/src/library/style.rs b/src/library/style.rs index 76838046..3bdcdfd4 100644 --- a/src/library/style.rs +++ b/src/library/style.rs @@ -1,58 +1,41 @@ +use std::fmt::{self, Display, Formatter}; use std::rc::Rc; use fontdock::{FontStretch, FontStyle, FontWeight}; use crate::color::{Color, RgbaColor}; -use crate::eval::StringLike; -use crate::geom::Linear; use crate::prelude::*; /// `font`: Configure the font. /// /// # Positional arguments -/// - Font size (optional, `linear` relative to current font size). -/// - Font families ... (optional, variadic, `Family`) +/// - Font size: optional, of type `linear` relative to current font size. +/// - Font families: variadic, of type `font-family`. /// /// # Named arguments -/// - `style` (`Style`): The font style. -/// - `weight` (`Weight`): The font weight. -/// - `stretch` (`Stretch`): The font stretch. -/// - `serif` (`Family` or `dict` of type `Family`): The serif family. -/// - `sans-serif` (`Family` or `dict` of type `Family`): The new sansserif family. -/// - `monospace` (`Family` or `dict` of type `Family`): The monospace family. -/// - `emoji` (`Family` or `dict` of type `Family`): The emoji family. -/// - `math` (`Family` or `dict` of type `Family`): The math family. +/// - Font Style: `style`, of type `font-style`. +/// - Font Weight: `weight`, of type `font-weight`. +/// - Font Stretch: `stretch`, of type `font-stretch`. +/// - Serif family definition: `serif`, of type `font-families`. +/// - Sans-serif family definition: `sans-serif`, of type `font-families`. +/// - Monospace family definition: `monospace`, of type `font-families`. /// -/// # Examples -/// Set font size and font families. -/// ```typst -/// [font 12pt, "Arial", "Noto Sans", sans-serif] -/// ``` -/// -/// Redefine the default sans-serif family to a single font family. -/// ```typst -/// [font sans-serif: "Source Sans Pro"] -/// ``` -/// -/// Redefine the default emoji family with a fallback. -/// ```typst -/// [font emoji: ("Segoe UI Emoji", "Noto Emoji")] -/// ``` -/// -/// # Enumerations -/// - `Family` +/// # Relevant types and constants +/// - Type `font-families` +/// - coerces from `string` +/// - coerces from `array` +/// - coerces from `font-family` +/// - Type `font-family` /// - `serif` /// - `sans-serif` /// - `monospace` -/// - `emoji` -/// - `math` -/// - any string -/// - `Style` +/// - coerces from `string` +/// - Type `font-style` /// - `normal` /// - `italic` /// - `oblique` -/// - `Weight` -/// - `thin` or `hairline` (100) +/// - Type `font-weight` +/// - `thin` (100) /// - `extralight` (200) /// - `light` (300) /// - `regular` (400) @@ -61,8 +44,8 @@ use crate::prelude::*; /// - `bold` (700) /// - `extrabold` (800) /// - `black` (900) -/// - any integer between 100 and 900 -/// - `Stretch` +/// - coerces from `integer` +/// - Type `font-stretch` /// - `ultra-condensed` /// - `extra-condensed` /// - `condensed` @@ -72,11 +55,10 @@ use crate::prelude::*; /// - `expanded` /// - `extra-expanded` /// - `ultra-expanded` -pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value { +pub fn font(ctx: &mut EvalContext, args: &mut Args) -> Value { let snapshot = ctx.state.clone(); - let body = args.find::<SynTree>(); - if let Some(linear) = args.find::<Linear>() { + if let Some(linear) = args.find::<Linear>(ctx) { if linear.is_absolute() { ctx.state.font.size = linear.abs; ctx.state.font.scale = Relative::ONE.into(); @@ -85,52 +67,35 @@ pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value { } } - let mut needs_flattening = false; - let list: Vec<_> = args.find_all::<StringLike>().map(|s| s.to_lowercase()).collect(); - + let list: Vec<_> = args.filter::<FontFamily>(ctx).map(|f| f.to_string()).collect(); if !list.is_empty() { - Rc::make_mut(&mut ctx.state.font.families).list = list; - needs_flattening = true; + let families = Rc::make_mut(&mut ctx.state.font.families); + families.list = list; + families.flatten(); } - if let Some(style) = args.get::<_, FontStyle>(ctx, "style") { + if let Some(style) = args.get(ctx, "style") { ctx.state.font.variant.style = style; } - if let Some(weight) = args.get::<_, FontWeight>(ctx, "weight") { + if let Some(weight) = args.get(ctx, "weight") { ctx.state.font.variant.weight = weight; } - if let Some(stretch) = args.get::<_, FontStretch>(ctx, "stretch") { + if let Some(stretch) = args.get(ctx, "stretch") { ctx.state.font.variant.stretch = stretch; } - struct FamilyList(Vec<String>); - - try_from_match!(FamilyList["family or list of families"] @ span: - Value::Str(v) => Self(vec![v.to_lowercase()]), - Value::Dict(v) => Self(Args(v.with_span(span)) - .find_all::<StringLike>() - .map(|s| s.to_lowercase()) - .collect() - ), - ); - - for &class in &["serif", "sans-serif", "monospace", "emoji", "math"] { - if let Some(list) = args.get::<_, FamilyList>(ctx, class) { - Rc::make_mut(&mut ctx.state.font.families) - .update_class_list(class.to_string(), list.0); - needs_flattening = true; + for variant in FontFamily::VARIANTS { + if let Some(FontFamilies(list)) = args.get(ctx, variant.as_str()) { + let strings = list.into_iter().map(|f| f.to_string()).collect(); + let families = Rc::make_mut(&mut ctx.state.font.families); + families.update_class_list(variant.to_string(), strings); + families.flatten(); } } - if needs_flattening { - Rc::make_mut(&mut ctx.state.font.families).flatten(); - } - - args.done(ctx); - - if let Some(body) = body { + if let Some(body) = args.find::<ValueContent>(ctx) { body.eval(ctx); ctx.state = snapshot; } @@ -138,24 +103,94 @@ pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value { Value::None } +/// A list of font families. +#[derive(Debug, Clone, PartialEq)] +struct FontFamilies(Vec<FontFamily>); + +impl_type! { + FontFamilies: "font family or array of font families", + Value::Str(string) => Self(vec![FontFamily::Named(string.to_lowercase())]), + Value::Array(values) => Self(values + .into_iter() + .filter_map(|v| v.cast().ok()) + .collect() + ), + #(family: FontFamily) => Self(vec![family]), +} + +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub(crate) enum FontFamily { + Serif, + SansSerif, + Monospace, + Named(String), +} + +impl FontFamily { + pub const VARIANTS: &'static [Self] = + &[Self::Serif, Self::SansSerif, Self::Monospace]; + + pub fn as_str(&self) -> &str { + match self { + Self::Serif => "serif", + Self::SansSerif => "sans-serif", + Self::Monospace => "monospace", + Self::Named(s) => s, + } + } +} + +impl Display for FontFamily { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(self.as_str()) + } +} + +impl_type! { + FontFamily: "font family", + Value::Str(string) => Self::Named(string.to_lowercase()) +} + +impl_type! { + FontStyle: "font style" +} + +impl_type! { + FontWeight: "font weight", + Value::Int(number) => { + let [min, max] = [Self::THIN, Self::BLACK]; + let message = || format!("must be between {:#?} and {:#?}", min, max); + return if number < i64::from(min.to_number()) { + CastResult::Warn(min, message()) + } else if number > i64::from(max.to_number()) { + CastResult::Warn(max, message()) + } else { + CastResult::Ok(Self::from_number(number as u16)) + }; + }, +} + +impl_type! { + FontStretch: "font stretch" +} + /// `rgb`: Create an RGB(A) color. /// /// # Positional arguments -/// - Red component (`float` between 0.0 and 1.0). -/// - Green component (`float` between 0.0 and 1.0). -/// - Blue component (`float` between 0.0 and 1.0). -/// - Alpha component (optional, `float` between 0.0 and 1.0). -pub fn rgb(mut args: Args, ctx: &mut EvalContext) -> Value { - let r = args.need::<_, Spanned<f64>>(ctx, 0, "red component"); - let g = args.need::<_, Spanned<f64>>(ctx, 1, "green component"); - let b = args.need::<_, Spanned<f64>>(ctx, 2, "blue component"); - let a = args.get::<_, Spanned<f64>>(ctx, 3); - args.done(ctx); +/// - Red component: of type `float`, between 0.0 and 1.0. +/// - Green component: of type `float`, between 0.0 and 1.0. +/// - Blue component: of type `float`, between 0.0 and 1.0. +/// - Alpha component: optional, of type `float`, between 0.0 and 1.0. +pub fn rgb(ctx: &mut EvalContext, args: &mut Args) -> Value { + let r = args.require(ctx, "red component"); + let g = args.require(ctx, "green component"); + let b = args.require(ctx, "blue component"); + let a = args.find(ctx); let mut clamp = |component: Option<Spanned<f64>>, default| { component.map_or(default, |c| { if c.v < 0.0 || c.v > 1.0 { - ctx.diag(error!(c.span, "should be between 0.0 and 1.0")); + ctx.diag(warning!(c.span, "must be between 0.0 and 1.0")); } (c.v.max(0.0).min(1.0) * 255.0).round() as u8 }) |
