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 | |
| parent | ace57c34206a13b4bc3885b944cc51e274f30b0f (diff) | |
Expand functionality of function! macro 🛰
Diffstat (limited to 'src/library')
| -rw-r--r-- | src/library/align.rs | 193 | ||||
| -rw-r--r-- | src/library/boxed.rs | 59 | ||||
| -rw-r--r-- | src/library/mod.rs | 270 | ||||
| -rw-r--r-- | src/library/page.rs | 79 | ||||
| -rw-r--r-- | src/library/spacing.rs | 71 | ||||
| -rw-r--r-- | src/library/style.rs | 39 |
6 files changed, 341 insertions, 370 deletions
diff --git a/src/library/align.rs b/src/library/align.rs index 530120e7..14e329e3 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -1,155 +1,68 @@ use crate::func::prelude::*; -/// 📐 `align`: Aligns content in different ways. -#[derive(Debug, PartialEq)] -pub struct Align { - body: Option<SyntaxTree>, - positional_1: Option<AlignSpecifier>, - positional_2: Option<AlignSpecifier>, - primary: Option<AlignSpecifier>, - secondary: Option<AlignSpecifier>, - horizontal: Option<AlignSpecifier>, - vertical: Option<AlignSpecifier>, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -enum AlignSpecifier { - Origin, - Center, - End, - Left, - Right, - Top, - Bottom, -} - function! { - data: Align, + /// `align`: Aligns content along the layouting axes. + #[derive(Debug, PartialEq)] + pub struct Align { + body: Option<SyntaxTree>, + map: ArgMap<Key, AlignmentKey>, + } parse(args, body, ctx) { - let body = parse!(optional: body, ctx); - - let mut align = Align { - body, - positional_1: None, - positional_2: None, - primary: None, - secondary: None, - horizontal: None, - vertical: None, - }; - - if let Some(arg) = args.get_pos_opt::<ArgIdent>()? { - align.positional_1 = Some(parse_align_specifier(arg)?); + let mut map = ArgMap::new(); + map.put(Key::First, args.get_pos_opt::<ArgIdent>()?)?; + map.put(Key::Second, args.get_pos_opt::<ArgIdent>()?)?; + + for arg in args.keys() { + let key = match arg.val.0.val { + "horizontal" => Key::Axis(AxisKey::Horizontal), + "vertical" => Key::Axis(AxisKey::Vertical), + "primary" => Key::Axis(AxisKey::Primary), + "secondary" => Key::Axis(AxisKey::Secondary), + _ => pr!("unexpected argument"), + }; + + let value = AlignmentKey::parse(arg.val.1.val)?; + map.add(key, value); } - if let Some(arg) = args.get_pos_opt::<ArgIdent>()? { - align.positional_2 = Some(parse_align_specifier(arg)?); + Align { + body: parse!(optional: body, ctx), + map, } - - let mut parse_arg = |axis, target: &mut Option<AlignSpecifier>| { - Ok(if let Some(arg) = args.get_key_opt::<ArgIdent>(axis)? { - if target.is_none() { - *target = Some(parse_align_specifier(arg)?); - } else { - perr!("duplicate alignment specification for {} axis", axis); - } - }) - }; - - parse_arg("primary", &mut align.primary)?; - parse_arg("secondary", &mut align.secondary)?; - parse_arg("horizontal", &mut align.horizontal)?; - parse_arg("vertical", &mut align.vertical)?; - - args.done()?; - - Ok(align) } - layout(this, ctx) { - let mut axes = ctx.axes; - let primary_horizontal = axes.primary.is_horizontal(); - - let mut primary = false; - let mut secondary = false; - - let mut set_axis = |is_primary: bool, spec: Option<AlignSpecifier>| -> LayoutResult<()> { - if let Some(spec) = spec { - let (axis, was_set, name) = match is_primary { - true => (&mut axes.primary, &mut primary, "primary"), - false => (&mut axes.secondary, &mut secondary, "secondary"), - }; - - if *was_set { - panic!("duplicate alignment for {} axis", name); - } - - *was_set = true; - - let horizontal = axis.is_horizontal(); - let alignment = generic_alignment(spec, horizontal)?; - - if axis.alignment == Alignment::End && alignment == Alignment::Origin { - axis.expand = true; - } - - axis.alignment = alignment; - } - - Ok(()) - }; - - if let Some(spec) = this.positional_1 { - let positional = generic_alignment(spec, primary_horizontal).is_ok(); - set_axis(positional, this.positional_1)?; + layout(self, mut ctx) { + let axes = ctx.axes; + let basic = axes.primary.is_horizontal(); + + let map = self.map.dedup(|key, val| { + let axis = match key { + Key::First => val.axis(axes, GenericAxisKind::Primary), + Key::Second => val.axis(axes, GenericAxisKind::Secondary), + Key::Axis(AxisKey::Primary) => GenericAxisKind::Primary, + Key::Axis(AxisKey::Secondary) => GenericAxisKind::Secondary, + Key::Axis(AxisKey::Horizontal) => axes.horizontal(), + Key::Axis(AxisKey::Vertical) => axes.vertical(), + }; + + let alignment = val.generic(axes, axis)?; + Ok((key, alignment)) + })?; + + map.with(GenericAxisKind::Primary, |val| ctx.alignment.primary = val); + map.with(GenericAxisKind::Secondary, |val| ctx.alignment.secondary = val); + + match &self.body { + Some(body) => vec![AddMultiple(layout_tree(&body, ctx)?)], + None => vec![Command::SetAlignment(ctx.alignment)], } - - if let Some(spec) = this.positional_2 { - let positional = generic_alignment(spec, primary_horizontal).is_ok(); - set_axis(positional, this.positional_2)?; - } - - set_axis(true, this.primary)?; - set_axis(false, this.secondary)?; - set_axis(primary_horizontal, this.horizontal)?; - set_axis(!primary_horizontal, this.vertical)?; - - Ok(match &this.body { - Some(body) => vec![AddMultiple( - layout_tree(body, LayoutContext { - axes, - .. ctx.clone() - })? - )], - None => vec![Command::SetAxes(axes)] - }) } } -fn parse_align_specifier(arg: Spanned<&str>) -> ParseResult<AlignSpecifier> { - Ok(match arg.val { - "origin" => AlignSpecifier::Origin, - "center" => AlignSpecifier::Center, - "end" => AlignSpecifier::End, - "left" => AlignSpecifier::Left, - "right" => AlignSpecifier::Right, - "top" => AlignSpecifier::Top, - "bottom" => AlignSpecifier::Bottom, - s => perr!("invalid alignment specifier: {}", s), - }) -} - -fn generic_alignment(spec: AlignSpecifier, horizontal: bool) -> LayoutResult<Alignment> { - use AlignSpecifier::*; - Ok(match (spec, horizontal) { - (Origin, _) | (Left, true) | (Top, false) => Alignment::Origin, - (Center, _) => Alignment::Center, - (End, _) | (Right, true) | (Bottom, false) => Alignment::End, - _ => lerr!( - "invalid alignment specifier `{}` for {} axis", - format!("{:?}", spec).to_lowercase(), - if horizontal { "horizontal" } else { "vertical" }, - ), - }) +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +enum Key { + First, + Second, + Axis(AxisKey), } diff --git a/src/library/boxed.rs b/src/library/boxed.rs index 8984417c..d4e39450 100644 --- a/src/library/boxed.rs +++ b/src/library/boxed.rs @@ -1,37 +1,44 @@ use crate::func::prelude::*; -/// `box`: Layouts content into a box. -#[derive(Debug, PartialEq)] -pub struct Boxed { - body: SyntaxTree, - width: Option<Size>, - height: Option<Size>, -} - function! { - data: Boxed, + /// `box`: Layouts content into a box. + #[derive(Debug, PartialEq)] + pub struct Boxed { + body: SyntaxTree, + map: ArgMap<AxisKey, Size>, + } parse(args, body, ctx) { - let width = args.get_key_opt::<ArgSize>("width")?.map(|a| a.val); - let height = args.get_key_opt::<ArgSize>("height")?.map(|a| a.val); - args.done()?; - - let body = parse!(required: body, ctx); - Ok(Boxed { - body, - width, - height, - }) - } + let mut map = ArgMap::new(); + + for arg in args.keys() { + let key = match arg.val.0.val { + "width" | "w" => AxisKey::Horizontal, + "height" | "h" => AxisKey::Vertical, + "primary-size" => AxisKey::Primary, + "secondary-size" => AxisKey::Secondary, + _ => pr!("unexpected argument"), + }; - layout(this, mut ctx) { - if let Some(width) = this.width { - ctx.spaces[0].dimensions.x = width; + let size = ArgParser::convert::<ArgSize>(arg.val.1.val)?; + map.add(key, size); } - if let Some(height) = this.height { - ctx.spaces[0].dimensions.y = height; + + Boxed { + body: parse!(expected: body, ctx), + map, } + } + + layout(self, mut ctx) { + let map = self.map.dedup(|key, val| { + Ok((key.specific(ctx.axes), val)) + }); + + let mut dimensions = &mut ctx.spaces[0].dimensions; + map.with(AxisKey::Horizontal, |val| dimensions.x = val); + map.with(AxisKey::Vertical, |val| dimensions.y = val); - Ok(vec![AddMultiple(layout_tree(&this.body, ctx)?)]) + vec![AddMultiple(layout_tree(&self.body, ctx)?)] } } 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)] + } + } +} diff --git a/src/library/page.rs b/src/library/page.rs deleted file mode 100644 index e8a80870..00000000 --- a/src/library/page.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::func::prelude::*; - -/// `page.break`: Ends the current page. -#[derive(Debug, PartialEq)] -pub struct PageBreak; - -function! { - data: PageBreak, - parse: plain, - layout(_, _) { Ok(vec![FinishSpace]) } -} - -/// `page.size`: Set the size of pages. -#[derive(Debug, PartialEq)] -pub struct PageSize { - width: Option<Size>, - height: Option<Size>, -} - -function! { - data: PageSize, - - parse(args, body, _ctx) { - parse!(forbidden: body); - Ok(PageSize { - width: args.get_key_opt::<ArgSize>("width")?.map(|a| a.val), - height: args.get_key_opt::<ArgSize>("height")?.map(|a| a.val), - }) - } - - layout(this, ctx) { - let mut style = ctx.style.page; - - if let Some(width) = this.width { style.dimensions.x = width; } - if let Some(height) = this.height { style.dimensions.y = height; } - - Ok(vec![SetPageStyle(style)]) - } -} - -/// `page.margins`: Set the margins of pages. -#[derive(Debug, PartialEq)] -pub struct PageMargins { - left: Option<Size>, - top: Option<Size>, - right: Option<Size>, - bottom: Option<Size>, -} - -function! { - data: PageMargins, - - parse(args, body, _ctx) { - parse!(forbidden: body); - let default = args.get_pos_opt::<ArgSize>()?; - let mut get = |which| { - args.get_key_opt::<ArgSize>(which) - .map(|size| size.or(default).map(|a| a.val)) - }; - - Ok(PageMargins { - left: get("left")?, - top: get("top")?, - right: get("right")?, - bottom: get("bottom")?, - }) - } - - layout(this, ctx) { - let mut style = ctx.style.page; - - if let Some(left) = this.left { style.margins.left = left; } - if let Some(top) = this.top { style.margins.top = top; } - if let Some(right) = this.right { style.margins.right = right; } - if let Some(bottom) = this.bottom { style.margins.bottom = bottom; } - - Ok(vec![SetPageStyle(style)]) - } -} diff --git a/src/library/spacing.rs b/src/library/spacing.rs deleted file mode 100644 index 47fe9fff..00000000 --- a/src/library/spacing.rs +++ /dev/null @@ -1,71 +0,0 @@ -use crate::func::prelude::*; - -/// `line.break`, `n`: Ends the current line. -#[derive(Debug, PartialEq)] -pub struct LineBreak; - -function! { - data: LineBreak, - parse: plain, - layout(_, _) { Ok(vec![FinishLine]) } -} - -/// `paragraph.break`: Ends the current paragraph. -/// -/// This has the same effect as two subsequent newlines. -#[derive(Debug, PartialEq)] -pub struct ParagraphBreak; - -function! { - data: ParagraphBreak, - parse: plain, - layout(_, _) { Ok(vec![BreakParagraph]) } -} - -macro_rules! space_func { - ($ident:ident, $doc:expr, $var:ident => $command:expr) => ( - #[doc = $doc] - #[derive(Debug, PartialEq)] - pub struct $ident(Spacing); - - function! { - data: $ident, - - parse(args, body, _ctx) { - parse!(forbidden: body); - - let arg = args.get_pos::<ArgExpr>()?; - let spacing = match arg.val { - Expression::Size(s) => Spacing::Absolute(*s), - Expression::Num(f) => Spacing::Relative(*f as f32), - _ => perr!("invalid spacing, expected size or number"), - }; - - Ok($ident(spacing)) - } - - layout(this, ctx) { - let $var = match this.0 { - Spacing::Absolute(s) => s, - Spacing::Relative(f) => f * ctx.style.text.font_size, - }; - - Ok(vec![$command]) - } - } - ); -} - -/// Absolute or font-relative spacing. -#[derive(Debug, PartialEq)] -enum Spacing { - Absolute(Size), - Relative(f32), -} - -// FIXME: h != primary and v != secondary. -space_func!(HorizontalSpace, "📖 `h`: Adds horizontal whitespace.", - space => AddSpacing(space, SpacingKind::Hard, AxisKind::Primary)); - -space_func!(VerticalSpace, "📑 `v`: Adds vertical whitespace.", - space => AddSpacing(space, SpacingKind::Hard, AxisKind::Secondary)); diff --git a/src/library/style.rs b/src/library/style.rs deleted file mode 100644 index b2de19bb..00000000 --- a/src/library/style.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::func::prelude::*; -use toddle::query::FontClass; - -macro_rules! stylefunc { - ($ident:ident, $doc:expr) => ( - #[doc = $doc] - #[derive(Debug, PartialEq)] - pub struct $ident { - body: Option<SyntaxTree> - } - - function! { - data: $ident, - - parse(args, body, ctx) { - args.done()?; - Ok($ident { body: parse!(optional: body, ctx) }) - } - - layout(this, ctx) { - let mut style = ctx.style.text.clone(); - style.toggle_class(FontClass::$ident); - - Ok(match &this.body { - Some(body) => vec![ - SetTextStyle(style), - LayoutTree(body), - SetTextStyle(ctx.style.text.clone()), - ], - None => vec![SetTextStyle(style)] - }) - } - } - ); -} - -stylefunc!(Italic, "`italic`: Sets text in _italics_."); -stylefunc!(Bold, "`bold`: Sets text in **bold**."); -stylefunc!(Monospace, "`mono`: Sets text in `monospace`."); |
