diff options
| author | Laurenz <laurmaedje@gmail.com> | 2019-12-04 19:34:29 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2019-12-04 19:35:28 +0100 |
| commit | 9fb31defd037a90bf8f9e38fa33acae23a70b269 (patch) | |
| tree | e0fd887792a59cbb3262a5d3157d0c786df56d60 /src/library/mod.rs | |
| parent | ace57c34206a13b4bc3885b944cc51e274f30b0f (diff) | |
Expand functionality of function! macro 🛰
Diffstat (limited to 'src/library/mod.rs')
| -rw-r--r-- | src/library/mod.rs | 270 |
1 files changed, 255 insertions, 15 deletions
diff --git a/src/library/mod.rs b/src/library/mod.rs index adcb974b..b09303bb 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -1,34 +1,274 @@ //! The standard library for the _Typst_ language. -use crate::func::Scope; +use crate::func::prelude::*; +use toddle::query::FontClass; -pub_use_mod!(boxed); pub_use_mod!(align); -pub_use_mod!(spacing); -pub_use_mod!(style); -pub_use_mod!(page); +pub_use_mod!(boxed); /// Create a scope with all standard functions. pub fn std() -> Scope { let mut std = Scope::new(); - std.add::<Boxed>("box"); - std.add::<Align>("align"); + std.add::<Boxed>("box"); + std.add::<PageSize>("page.size"); + std.add::<PageMargins>("page.margins"); std.add::<LineBreak>("n"); std.add::<LineBreak>("line.break"); - std.add::<ParagraphBreak>("paragraph.break"); + std.add::<ParBreak>("par.break"); std.add::<PageBreak>("page.break"); - std.add::<HorizontalSpace>("h"); - std.add::<VerticalSpace>("v"); - std.add::<Bold>("bold"); - std.add::<Italic>("italic"); - std.add::<Monospace>("mono"); + std.add_with_metadata::<Spacing, Option<AxisKey>>("spacing", None); + for (name, key) in &[ + ("h", AxisKey::Horizontal), + ("v", AxisKey::Vertical), + ] { + std.add_with_metadata::<Spacing, Option<AxisKey>>(name, Some(*key)); + } - std.add::<PageSize>("page.size"); - std.add::<PageMargins>("page.margins"); + for (name, class) in &[ + ("bold", FontClass::Bold), + ("italic", FontClass::Italic), + ("mono", FontClass::Monospace), + ] { + std.add_with_metadata::<StyleChange, FontClass>(name, *class); + } std } + +function! { + /// `line.break`, `n`: Ends the current line. + #[derive(Debug, Default, PartialEq)] + pub struct LineBreak; + + parse(default) + layout() { vec![FinishLine] } +} + +function! { + /// `par.break`: Ends the current paragraph. + /// + /// self has the same effect as two subsequent newlines. + #[derive(Debug, Default, PartialEq)] + pub struct ParBreak; + + parse(default) + layout() { vec![BreakParagraph] } +} + + +function! { + /// `page.break`: Ends the current page. + #[derive(Debug, Default, PartialEq)] + pub struct PageBreak; + + parse(default) + layout() { vec![FinishSpace] } +} + +function! { + /// `page.size`: Set the size of pages. + #[derive(Debug, PartialEq)] + pub struct PageSize { + width: Option<Size>, + height: Option<Size>, + } + + parse(args, body) { + parse!(forbidden: body); + PageSize { + width: args.get_key_opt::<ArgSize>("width")?.map(|a| a.val), + height: args.get_key_opt::<ArgSize>("height")?.map(|a| a.val), + } + } + + layout(self, ctx) { + let mut style = ctx.style.page; + if let Some(width) = self.width { style.dimensions.x = width; } + if let Some(height) = self.height { style.dimensions.y = height; } + vec![SetPageStyle(style)] + } +} + +function! { + /// `page.margins`: Set the margins of pages. + #[derive(Debug, PartialEq)] + pub struct PageMargins { + map: ArgMap<PaddingKey, Size>, + } + + parse(args, body) { + use PaddingKey::*; + use AlignmentKey::*; + + let mut map = ArgMap::new(); + map.add_opt(All, args.get_pos_opt::<ArgSize>()?); + + for arg in args.keys() { + let key = match arg.val.0.val { + "horizontal" => Axis(AxisKey::Horizontal), + "vertical" => Axis(AxisKey::Vertical), + "primary" => Axis(AxisKey::Primary), + "secondary" => Axis(AxisKey::Secondary), + + "left" => AxisAligned(AxisKey::Horizontal, Left), + "right" => AxisAligned(AxisKey::Horizontal, Right), + "top" => AxisAligned(AxisKey::Vertical, Top), + "bottom" => AxisAligned(AxisKey::Vertical, Bottom), + + "primary-origin" => AxisAligned(AxisKey::Primary, Origin), + "primary-end" => AxisAligned(AxisKey::Primary, End), + "secondary-origin" => AxisAligned(AxisKey::Secondary, Origin), + "secondary-end" => AxisAligned(AxisKey::Secondary, End), + "horizontal-origin" => AxisAligned(AxisKey::Horizontal, Origin), + "horizontal-end" => AxisAligned(AxisKey::Horizontal, End), + "vertical-origin" => AxisAligned(AxisKey::Vertical, Origin), + "vertical-end" => AxisAligned(AxisKey::Vertical, End), + + _ => pr!("unexpected argument"), + }; + + let size = ArgParser::convert::<ArgSize>(arg.val.1.val)?; + map.add(key, size); + } + + parse!(forbidden: body); + PageMargins { map } + } + + layout(self, ctx) { + use PaddingKey::*; + + let axes = ctx.axes; + let map = self.map.dedup(|key, val| { + match key { + All => All, + Axis(axis) => Axis(axis.specific(axes)), + AxisAligned(axis, alignment) => { + let axis = axis.specific(axes); + AxisAligned(axis, alignment.specific(axes, axis)) + } + } + }); + + let style = ctx.style.page; + let padding = &mut style.margins; + + map.with(All, |val| padding.set_all(val)); + map.with(Axis(AxisKey::Horizontal), |val| padding.set_horizontal(val)); + map.with(Axis(AxisKey::Vertical), |val| padding.set_vertical(val)); + + for (key, val) in map.iter() { + if let AxisAligned(axis, alignment) = key { + match alignment { + AlignmentKey::Left => padding.left = val, + AlignmentKey::Right => padding.right = val, + AlignmentKey::Top => padding.top = val, + AlignmentKey::Bottom => padding.bottom = val, + _ => {}, + } + } + } + + vec![SetPageStyle(style)] + } +} + +function! { + /// `spacing`, `h`, `v`: Add spacing along an axis. + #[derive(Debug, PartialEq)] + pub struct Spacing { + axis: AxisKey, + spacing: SpacingValue, + } + + type Meta = Option<AxisKey>; + + parse(args, body, _, meta) { + let spacing = if let Some(axis) = meta { + Spacing { + axis, + spacing: SpacingValue::from_expr(args.get_pos::<ArgExpr>()?)?, + } + } else { + if let Some(arg) = args.get_key_next() { + let axis = match arg.val.0.val { + "horizontal" => AxisKey::Horizontal, + "vertical" => AxisKey::Vertical, + "primary" => AxisKey::Primary, + "secondary" => AxisKey::Secondary, + _ => pr!("unexpected argument"), + }; + + let spacing = SpacingValue::from_expr(arg.val.1.val)?; + Spacing { axis, spacing } + } else { + pr!("expected axis and expression") + } + }; + + parse!(forbidden: body); + spacing + } + + layout(self, ctx) { + let axis = self.axis.generic(ctx.axes); + let spacing = match self.spacing { + SpacingValue::Absolute(s) => s, + SpacingValue::Relative(f) => f * ctx.style.text.font_size, + }; + + vec![AddSpacing(spacing, SpacingKind::Hard, axis)] + } +} + +#[derive(Debug, PartialEq)] +enum SpacingValue { + Absolute(Size), + Relative(f32), +} + +impl SpacingValue { + fn from_expr(expr: Spanned<&Expression>) -> ParseResult<SpacingValue> { + Ok(match expr.val { + Expression::Size(s) => SpacingValue::Absolute(*s), + Expression::Num(f) => SpacingValue::Relative(*f as f32), + _ => pr!("invalid spacing: expected size or number"), + }) + } +} + +function! { + /// Sets text with a different style. + #[derive(Debug, PartialEq)] + pub struct StyleChange { + body: Option<SyntaxTree>, + class: FontClass, + } + + type Meta = FontClass; + + parse(args, body, ctx, meta) { + StyleChange { + body: parse!(optional: body, ctx), + class: meta, + } + } + + layout(self, ctx) { + let mut style = ctx.style.text.clone(); + style.toggle_class(self.class); + + match &self.body { + Some(body) => vec![ + SetTextStyle(style), + LayoutTree(body), + SetTextStyle(ctx.style.text.clone()), + ], + None => vec![SetTextStyle(style)] + } + } +} |
