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 | |
| 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')
| -rw-r--r-- | src/library/insert.rs | 14 | ||||
| -rw-r--r-- | src/library/layout.rs | 201 | ||||
| -rw-r--r-- | src/library/mod.rs | 81 | ||||
| -rw-r--r-- | src/library/style.rs | 201 |
4 files changed, 268 insertions, 229 deletions
diff --git a/src/library/insert.rs b/src/library/insert.rs index 6f267b7a..587f96dc 100644 --- a/src/library/insert.rs +++ b/src/library/insert.rs @@ -6,14 +6,14 @@ use crate::prelude::*; /// `image`: Insert an image. /// -/// # Positional arguments -/// - Path (`string`): The path to the image file. -/// /// Supports PNG and JPEG files. -pub fn image(mut args: Args, ctx: &mut EvalContext) -> Value { - let path = args.need::<_, Spanned<String>>(ctx, 0, "path"); - let width = args.get::<_, Linear>(ctx, "width"); - let height = args.get::<_, Linear>(ctx, "height"); +/// +/// # Positional arguments +/// - Path to image file: of type `string`. +pub fn image(ctx: &mut EvalContext, args: &mut Args) -> Value { + let path = args.require::<Spanned<String>>(ctx, "path to image file"); + let width = args.get(ctx, "width"); + let height = args.get(ctx, "height"); if let Some(path) = path { let mut env = ctx.env.borrow_mut(); diff --git a/src/library/layout.rs b/src/library/layout.rs index c888ea18..ac152d07 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -1,51 +1,44 @@ -use std::fmt::{self, Display, Formatter}; - use crate::eval::Softness; -use crate::geom::{Length, Linear}; use crate::layout::{Expansion, Fixed, Spacing, Stack}; use crate::paper::{Paper, PaperClass}; use crate::prelude::*; /// `align`: Align content along the layouting axes. /// +/// Which axis an alignment should apply to (main or cross) is inferred from +/// either the argument itself (for anything other than `center`) or from the +/// second argument if present, defaulting to the cross axis for a single +/// `center` alignment. +/// /// # Positional arguments -/// - first (optional, `Alignment`): An alignment for any of the two axes. -/// - second (optional, `Alignment`): An alignment for the other axis. +/// - Alignments: variadic, of type `alignment`. /// /// # Named arguments -/// - `horizontal` (`Alignment`): An alignment for the horizontal axis. -/// - `vertical` (`Alignment`): An alignment for the vertical axis. +/// - Horizontal alignment: `horizontal`, of type `alignment`. +/// - Vertical alignment: `vertical`, of type `alignment`. /// -/// # Enumerations -/// - `Alignment` +/// # Relevant types and constants +/// - Type `alignment` /// - `left` /// - `right` /// - `top` /// - `bottom` /// - `center` -/// -/// # Notes -/// Which axis an alignment should apply to (main or cross) is inferred from -/// either the argument itself (for anything other than `center`) or from the -/// second argument if present, defaulting to the cross axis for a single -/// `center` alignment. -pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value { +pub fn align(ctx: &mut EvalContext, args: &mut Args) -> Value { let snapshot = ctx.state.clone(); - let body = args.find::<SynTree>(); - let first = args.get::<_, Spanned<SpecAlign>>(ctx, 0); - let second = args.get::<_, Spanned<SpecAlign>>(ctx, 1); - let hor = args.get::<_, Spanned<SpecAlign>>(ctx, "horizontal"); - let ver = args.get::<_, Spanned<SpecAlign>>(ctx, "vertical"); - args.done(ctx); - - let prev_main = ctx.state.align.main; + + let first = args.find(ctx); + let second = args.find(ctx); + let hor = args.get(ctx, "horizontal"); + let ver = args.get(ctx, "vertical"); + let mut had = Gen::uniform(false); let mut had_center = false; for (axis, Spanned { v: arg, span }) in first .into_iter() .chain(second.into_iter()) - .map(|arg| (arg.v.axis(), arg)) + .map(|arg: Spanned<Alignment>| (arg.v.axis(), arg)) .chain(hor.into_iter().map(|arg| (Some(SpecAxis::Horizontal), arg))) .chain(ver.into_iter().map(|arg| (Some(SpecAxis::Vertical), arg))) { @@ -56,10 +49,7 @@ pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value { let gen_align = arg.switch(ctx.state.flow); if arg.axis().map_or(false, |a| a != axis) { - ctx.diag(error!( - span, - "invalid alignment `{}` for {} axis", arg, axis, - )); + ctx.diag(error!(span, "invalid alignment for {} axis", axis)); } else if had.get(gen_axis) { ctx.diag(error!(span, "duplicate alignment for {} axis", axis)); } else { @@ -69,7 +59,7 @@ pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value { } else { // We don't know the axis: This has to be a `center` alignment for a // positional argument. - debug_assert_eq!(arg, SpecAlign::Center); + debug_assert_eq!(arg, Alignment::Center); if had.main && had.cross { ctx.diag(error!(span, "duplicate alignment")); @@ -104,12 +94,12 @@ pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value { ctx.state.align.cross = Align::Center; } - if ctx.state.align.main != prev_main { + if ctx.state.align.main != snapshot.align.main { ctx.end_par_group(); ctx.start_par_group(); } - if let Some(body) = body { + if let Some(body) = args.find::<ValueContent>(ctx) { body.eval(ctx); ctx.state = snapshot; } @@ -117,30 +107,18 @@ pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value { Value::None } -/// An argument to `[align]`. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -enum SpecAlign { +pub(crate) enum Alignment { Left, + Center, Right, Top, Bottom, - Center, } -try_from_id!(SpecAlign["alignment"]: |v| match v { - "left" => Some(Self::Left), - "right" => Some(Self::Right), - "top" => Some(Self::Top), - "bottom" => Some(Self::Bottom), - "center" => Some(Self::Center), - _ => None, -}); - -impl SpecAlign { +impl Alignment { /// The specific axis this alignment refers to. - /// - /// Returns `None` if this is `Center` since the axis is unknown. - pub fn axis(self) -> Option<SpecAxis> { + fn axis(self) -> Option<SpecAxis> { match self { Self::Left => Some(SpecAxis::Horizontal), Self::Right => Some(SpecAxis::Horizontal), @@ -151,7 +129,7 @@ impl SpecAlign { } } -impl Switch for SpecAlign { +impl Switch for Alignment { type Other = Align; fn switch(self, flow: Flow) -> Self::Other { @@ -174,38 +152,34 @@ impl Switch for SpecAlign { } } -impl Display for SpecAlign { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::Left => "left", - Self::Right => "right", - Self::Top => "top", - Self::Bottom => "bottom", - Self::Center => "center", - }) - } +impl_type! { + Alignment: "alignment" } /// `box`: Layout content into a box. /// /// # Named arguments -/// - `width` (`linear` relative to parent width): The width of the box. -/// - `height` (`linear` relative to parent height): The height of the box. -pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value { +/// - Width of the box: `width`, of type `linear` relative to parent width. +/// - Height of the box: `height`, of type `linear` relative to parent height. +pub fn boxed(ctx: &mut EvalContext, args: &mut Args) -> Value { let snapshot = ctx.state.clone(); - let body = args.find::<SynTree>().unwrap_or_default(); - let width = args.get::<_, Linear>(ctx, "width"); - let height = args.get::<_, Linear>(ctx, "height"); - let main = args.get::<_, Spanned<Dir>>(ctx, "main-dir"); - let cross = args.get::<_, Spanned<Dir>>(ctx, "cross-dir"); + + let width = args.get(ctx, "width"); + let height = args.get(ctx, "height"); + let main = args.get(ctx, "main-dir"); + let cross = args.get(ctx, "cross-dir"); + ctx.set_flow(Gen::new(main, cross)); - args.done(ctx); let flow = ctx.state.flow; let align = ctx.state.align; ctx.start_content_group(); - body.eval(ctx); + + if let Some(body) = args.find::<ValueContent>(ctx) { + body.eval(ctx); + } + let children = ctx.end_content_group(); ctx.push(Fixed { @@ -227,31 +201,34 @@ pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value { Value::None } +impl_type! { + Dir: "direction" +} + /// `h`: Add horizontal spacing. /// /// # Positional arguments -/// - Spacing (`linear` relative to font size): The amount of spacing. -pub fn h(args: Args, ctx: &mut EvalContext) -> Value { - spacing(args, ctx, SpecAxis::Horizontal) +/// - Amount of spacing: of type `linear` relative to current font size. +pub fn h(ctx: &mut EvalContext, args: &mut Args) -> Value { + spacing(ctx, args, SpecAxis::Horizontal) } /// `v`: Add vertical spacing. /// /// # Positional arguments -/// - Spacing (`linear` relative to font size): The amount of spacing. -pub fn v(args: Args, ctx: &mut EvalContext) -> Value { - spacing(args, ctx, SpecAxis::Vertical) +/// - Amount of spacing: of type `linear` relative to current font size. +pub fn v(ctx: &mut EvalContext, args: &mut Args) -> Value { + spacing(ctx, args, SpecAxis::Vertical) } /// Apply spacing along a specific axis. -fn spacing(mut args: Args, ctx: &mut EvalContext, axis: SpecAxis) -> Value { - let spacing = args.need::<_, Linear>(ctx, 0, "spacing"); - args.done(ctx); +fn spacing(ctx: &mut EvalContext, args: &mut Args, axis: SpecAxis) -> Value { + let spacing: Option<Linear> = args.require(ctx, "spacing"); if let Some(linear) = spacing { let amount = linear.resolve(ctx.state.font.font_size()); let spacing = Spacing { amount, softness: Softness::Hard }; - if ctx.state.flow.main.axis() == axis { + if axis == ctx.state.flow.main.axis() { ctx.end_par_group(); ctx.push(spacing); ctx.start_par_group(); @@ -266,79 +243,78 @@ fn spacing(mut args: Args, ctx: &mut EvalContext, axis: SpecAxis) -> Value { /// `page`: Configure pages. /// /// # Positional arguments -/// - Paper name (optional, `Paper`). +/// - Paper name: optional, of type `string`, see [here](crate::paper) for a +/// full list of all paper names. /// /// # Named arguments -/// - `width` (`length`): The width of pages. -/// - `height` (`length`): The height of pages. -/// - `margins` (`linear` relative to sides): The margins for all sides. -/// - `left` (`linear` relative to width): The left margin. -/// - `right` (`linear` relative to width): The right margin. -/// - `top` (`linear` relative to height): The top margin. -/// - `bottom` (`linear` relative to height): The bottom margin. -/// - `flip` (`bool`): Flips custom or paper-defined width and height. -/// -/// # Enumerations -/// - `Paper`: See [here](crate::paper) for a full list. -pub fn page(mut args: Args, ctx: &mut EvalContext) -> Value { +/// - Width of the page: `width`, of type `length`. +/// - Height of the page: `height`, of type `length`. +/// - Margins for all sides: `margins`, of type `linear` relative to sides. +/// - Left margin: `left`, of type `linear` relative to width. +/// - Right margin: `right`, of type `linear` relative to width. +/// - Top margin: `top`, of type `linear` relative to height. +/// - Bottom margin: `bottom`, of type `linear` relative to height. +/// - Flip width and height: `flip`, of type `bool`. +pub fn page(ctx: &mut EvalContext, args: &mut Args) -> Value { let snapshot = ctx.state.clone(); - let body = args.find::<SynTree>(); - if let Some(paper) = args.get::<_, Paper>(ctx, 0) { - ctx.state.page.class = paper.class; - ctx.state.page.size = paper.size(); + if let Some(name) = args.find::<Spanned<String>>(ctx) { + if let Some(paper) = Paper::from_name(&name.v) { + ctx.state.page.class = paper.class; + ctx.state.page.size = paper.size(); + } else { + ctx.diag(error!(name.span, "invalid paper name")); + } } - if let Some(width) = args.get::<_, Length>(ctx, "width") { + if let Some(width) = args.get(ctx, "width") { ctx.state.page.class = PaperClass::Custom; ctx.state.page.size.width = width; } - if let Some(height) = args.get::<_, Length>(ctx, "height") { + if let Some(height) = args.get(ctx, "height") { ctx.state.page.class = PaperClass::Custom; ctx.state.page.size.height = height; } - if let Some(margins) = args.get::<_, Linear>(ctx, "margins") { + if let Some(margins) = args.get(ctx, "margins") { ctx.state.page.margins = Sides::uniform(Some(margins)); } - if let Some(left) = args.get::<_, Linear>(ctx, "left") { + if let Some(left) = args.get(ctx, "left") { ctx.state.page.margins.left = Some(left); } - if let Some(top) = args.get::<_, Linear>(ctx, "top") { + if let Some(top) = args.get(ctx, "top") { ctx.state.page.margins.top = Some(top); } - if let Some(right) = args.get::<_, Linear>(ctx, "right") { + if let Some(right) = args.get(ctx, "right") { ctx.state.page.margins.right = Some(right); } - if let Some(bottom) = args.get::<_, Linear>(ctx, "bottom") { + if let Some(bottom) = args.get(ctx, "bottom") { ctx.state.page.margins.bottom = Some(bottom); } - if args.get::<_, bool>(ctx, "flip").unwrap_or(false) { + if args.get(ctx, "flip").unwrap_or(false) { let size = &mut ctx.state.page.size; std::mem::swap(&mut size.width, &mut size.height); } - let main = args.get::<_, Spanned<Dir>>(ctx, "main-dir"); - let cross = args.get::<_, Spanned<Dir>>(ctx, "cross-dir"); - ctx.set_flow(Gen::new(main, cross)); + let main = args.get(ctx, "main-dir"); + let cross = args.get(ctx, "cross-dir"); - args.done(ctx); + ctx.set_flow(Gen::new(main, cross)); let mut softness = ctx.end_page_group(|_| false); - - if let Some(body) = body { + if let Some(body) = args.find::<ValueContent>(ctx) { // TODO: Restrict body to a single page? ctx.start_page_group(Softness::Hard); body.eval(ctx); ctx.end_page_group(|s| s == Softness::Hard); - ctx.state = snapshot; softness = Softness::Soft; + ctx.state = snapshot; } ctx.start_page_group(softness); @@ -347,8 +323,7 @@ pub fn page(mut args: Args, ctx: &mut EvalContext) -> Value { } /// `pagebreak`: Start a new page. -pub fn pagebreak(args: Args, ctx: &mut EvalContext) -> Value { - args.done(ctx); +pub fn pagebreak(ctx: &mut EvalContext, _: &mut Args) -> Value { ctx.end_page_group(|_| true); ctx.start_page_group(Softness::Hard); Value::None diff --git a/src/library/mod.rs b/src/library/mod.rs index 806b0275..1cc7b9e9 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -8,31 +8,60 @@ pub use insert::*; pub use layout::*; pub use style::*; -use crate::eval::{Scope, ValueFunc}; - -macro_rules! std { - ($($func:expr $(=> $name:expr)?),* $(,)?) => { - /// The scope containing all standard library functions. - pub fn _std() -> Scope { - let mut std = Scope::new(); - $( - let _name = stringify!($func); - $(let _name = $name;)? - std.set(_name, ValueFunc::new($func)); - )* - std - } - }; -} +use fontdock::{FontStretch, FontStyle, FontWeight}; + +use crate::eval::Scope; +use crate::geom::Dir; + +/// The scope containing the standard library. +pub fn _std() -> Scope { + let mut std = Scope::new(); + + // Functions. + std.set("align", align); + std.set("box", boxed); + std.set("font", font); + std.set("h", h); + std.set("image", image); + std.set("page", page); + std.set("pagebreak", pagebreak); + std.set("rgb", rgb); + std.set("v", v); + + // Constants. + std.set("left", Alignment::Left); + std.set("center", Alignment::Center); + std.set("right", Alignment::Right); + std.set("top", Alignment::Top); + std.set("bottom", Alignment::Bottom); + std.set("ltr", Dir::LTR); + std.set("rtl", Dir::RTL); + std.set("ttb", Dir::TTB); + std.set("btt", Dir::BTT); + std.set("serif", FontFamily::Serif); + std.set("sans-serif", FontFamily::SansSerif); + std.set("monospace", FontFamily::Monospace); + std.set("normal", FontStyle::Normal); + std.set("italic", FontStyle::Italic); + std.set("oblique", FontStyle::Oblique); + std.set("thin", FontWeight::THIN); + std.set("extralight", FontWeight::EXTRALIGHT); + std.set("light", FontWeight::LIGHT); + std.set("regular", FontWeight::REGULAR); + std.set("medium", FontWeight::MEDIUM); + std.set("semibold", FontWeight::SEMIBOLD); + std.set("bold", FontWeight::BOLD); + std.set("extrabold", FontWeight::EXTRABOLD); + std.set("black", FontWeight::BLACK); + std.set("ultra-condensed", FontStretch::UltraCondensed); + std.set("extra-condensed", FontStretch::ExtraCondensed); + std.set("condensed", FontStretch::Condensed); + std.set("semi-condensed", FontStretch::SemiCondensed); + std.set("normal", FontStretch::Normal); + std.set("semi-expanded", FontStretch::SemiExpanded); + std.set("expanded", FontStretch::Expanded); + std.set("extra-expanded", FontStretch::ExtraExpanded); + std.set("ultra-expanded", FontStretch::UltraExpanded); -std! { - align, - boxed => "box", - font, - h, - image, - page, - pagebreak, - rgb, - v, + std } 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 }) |
