summaryrefslogtreecommitdiff
path: root/src/library
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-01-02 19:37:10 +0100
committerLaurenz <laurmaedje@gmail.com>2021-01-02 19:37:10 +0100
commit1c40dc42e7bc7b799b77f06d25414aca59a044ba (patch)
treeea8bdedaebf59f5bc601346b0108236c7264a29d /src/library
parent8cad78481cd52680317032c3bb84cacda5666489 (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.rs14
-rw-r--r--src/library/layout.rs201
-rw-r--r--src/library/mod.rs81
-rw-r--r--src/library/style.rs201
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
})