diff options
Diffstat (limited to 'src/library')
| -rw-r--r-- | src/library/align.rs | 37 | ||||
| -rw-r--r-- | src/library/boxed.rs | 21 | ||||
| -rw-r--r-- | src/library/keys.rs | 172 | ||||
| -rw-r--r-- | src/library/mod.rs | 104 |
4 files changed, 229 insertions, 105 deletions
diff --git a/src/library/align.rs b/src/library/align.rs index 417d8f07..eea25dfa 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -1,29 +1,25 @@ use crate::func::prelude::*; +use super::keys::*; function! { /// `align`: Aligns content along the layouting axes. #[derive(Debug, PartialEq)] pub struct Align { body: Option<SyntaxTree>, - map: ArgMap<Key, AlignmentKey>, + map: ConsistentMap<Key, AlignmentKey>, } parse(args, body, ctx) { - let mut map = ArgMap::new(); - map.put(Key::First, args.get_pos_opt::<ArgIdent>()?)?; - map.put(Key::Second, args.get_pos_opt::<ArgIdent>()?)?; + let mut map = ConsistentMap::new(); + + map.add_opt_span(Key::First, args.get_pos_opt::<AlignmentKey>()?)?; + map.add_opt_span(Key::Second, args.get_pos_opt::<AlignmentKey>()?)?; 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), - _ => error!(unexpected_argument), - }; + let axis = AxisKey::from_ident(&arg.v.key)?; + let value = AlignmentKey::from_expr(arg.v.value)?; - let value = AlignmentKey::parse(arg.val.1.val)?; - map.add(key, value); + map.add(Key::Axis(axis), value)?; } Align { @@ -34,24 +30,23 @@ function! { layout(self, mut ctx) { let axes = ctx.axes; - let basic = axes.primary.is_horizontal(); - let map = self.map.dedup(|key, val| { + let map = self.map.dedup(|key, alignment| { let axis = match key { - Key::First => val.axis(axes, GenericAxisKind::Primary), - Key::Second => val.axis(axes, GenericAxisKind::Secondary), + Key::First => alignment.axis(axes, GenericAxisKind::Primary), + Key::Second => alignment.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)) + let alignment = alignment.generic(axes, axis)?; + Ok((axis, alignment)) })?; - map.with(GenericAxisKind::Primary, |val| ctx.alignment.primary = val); - map.with(GenericAxisKind::Secondary, |val| ctx.alignment.secondary = val); + 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)?)], diff --git a/src/library/boxed.rs b/src/library/boxed.rs index a2df45e3..ef5ae24e 100644 --- a/src/library/boxed.rs +++ b/src/library/boxed.rs @@ -1,18 +1,19 @@ use crate::func::prelude::*; +use super::keys::*; function! { /// `box`: Layouts content into a box. #[derive(Debug, PartialEq)] pub struct Boxed { body: SyntaxTree, - map: ArgMap<AxisKey, Size>, + map: ConsistentMap<AxisKey, Size>, } parse(args, body, ctx) { - let mut map = ArgMap::new(); + let mut map = ConsistentMap::new(); for arg in args.keys() { - let key = match arg.val.0.val { + let key = match arg.v.key.v.0.as_str() { "width" | "w" => AxisKey::Horizontal, "height" | "h" => AxisKey::Vertical, "primary-size" => AxisKey::Primary, @@ -20,8 +21,8 @@ function! { _ => error!(unexpected_argument), }; - let size = ArgParser::convert::<ArgSize>(arg.val.1.val)?; - map.add(key, size); + let size = Size::from_expr(arg.v.value)?; + map.add(key, size)?; } Boxed { @@ -31,13 +32,11 @@ function! { } layout(self, mut ctx) { - let map = self.map.dedup(|key, val| { - Ok((key.specific(ctx.axes), val)) - }); + 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); + let dimensions = &mut ctx.spaces[0].dimensions; + map.with(SpecificAxisKind::Horizontal, |&val| dimensions.x = val); + map.with(SpecificAxisKind::Vertical, |&val| dimensions.y = val); vec![AddMultiple(layout_tree(&self.body, ctx)?)] } diff --git a/src/library/keys.rs b/src/library/keys.rs new file mode 100644 index 00000000..df658027 --- /dev/null +++ b/src/library/keys.rs @@ -0,0 +1,172 @@ +use crate::func::prelude::*; + +macro_rules! kind { + ($type:ty, $name:expr, $($patterns:tt)*) => { + impl $type { + /// Parse this key from an identifier. + pub fn from_ident(ident: &Spanned<Ident>) -> ParseResult<Self> { + Ok(match ident.v.0.as_str() { + $($patterns)* + _ => error!("expected {}", <Self as ExpressionKind>::NAME), + }) + } + } + + impl ExpressionKind for $type { + const NAME: &'static str = $name; + + fn from_expr(expr: Spanned<Expression>) -> ParseResult<Self> { + if let Expression::Ident(ident) = expr.v { + Self::from_ident(&Spanned::new(ident, expr.span)) + } else { + error!("expected {}", Self::NAME); + } + } + } + }; +} + +/// An argument key which identifies a layouting axis. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum AxisKey { + Primary, + Secondary, + Vertical, + Horizontal, +} + +impl AxisKey { + /// The generic version of this axis key in the given system of axes. + pub fn generic(&self, axes: LayoutAxes) -> GenericAxisKind { + match self { + AxisKey::Primary => GenericAxisKind::Primary, + AxisKey::Secondary => GenericAxisKind::Secondary, + AxisKey::Vertical => axes.vertical(), + AxisKey::Horizontal => axes.horizontal(), + } + } + + /// The specific version of this axis key in the given system of axes. + pub fn specific(&self, axes: LayoutAxes) -> SpecificAxisKind { + match self { + AxisKey::Primary => axes.primary(), + AxisKey::Secondary => axes.secondary(), + AxisKey::Vertical => SpecificAxisKind::Vertical, + AxisKey::Horizontal => SpecificAxisKind::Horizontal, + } + } +} + +kind!(AxisKey, "axis", + "horizontal" => AxisKey::Horizontal, + "vertical" => AxisKey::Vertical, + "primary" => AxisKey::Primary, + "secondary" => AxisKey::Secondary, +); + +/// An argument key which identifies a target alignment. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum AlignmentKey { + Left, + Top, + Right, + Bottom, + Origin, + Center, + End, +} + +impl AlignmentKey { + /// The generic axis this alignment key corresopnds to in the given system + /// of layouting axes. Falls back to `default` if the alignment is generic. + pub fn axis(&self, axes: LayoutAxes, default: GenericAxisKind) -> GenericAxisKind { + use AlignmentKey::*; + match self { + Origin | Center | End => default, + Left | Right => axes.horizontal(), + Top | Bottom => axes.vertical(), + } + } + + /// The generic version of this alignment in the given system of layouting + /// axes. Returns an error if the alignment is invalid for the given axis. + pub fn generic(&self, axes: LayoutAxes, axis: GenericAxisKind) -> LayoutResult<Alignment> { + use AlignmentKey::*; + + let horizontal = axis == axes.horizontal(); + Ok(match self { + Origin => Alignment::Origin, + Center => Alignment::Center, + End => Alignment::End, + Left if horizontal => axes.left(), + Right if horizontal => axes.right(), + Top if !horizontal => axes.top(), + Bottom if !horizontal => axes.bottom(), + _ => error!( + "invalid alignment `{}` for {} axis", + format!("{:?}", self).to_lowercase(), + format!("{:?}", axis).to_lowercase() + ) + }) + } + + /// The specific version of this alignment in the given system of layouting + /// axes. + pub fn specific(&self, axes: LayoutAxes, axis: SpecificAxisKind) -> AlignmentKey { + use AlignmentKey::*; + use SpecificAxisKind::*; + + let positive = axes.get_specific(axis).is_positive(); + match (self, axis, positive) { + (Origin, Horizontal, true) | (End, Horizontal, false) => Left, + (End, Horizontal, true) | (Origin, Horizontal, false) => Right, + (Origin, Vertical, true) | (End, Vertical, false) => Top, + (End, Vertical, true) | (Origin, Vertical, false) => Bottom, + _ => *self, + } + } +} + +kind!(AlignmentKey, "alignment", + "left" => AlignmentKey::Left, + "top" => AlignmentKey::Top, + "right" => AlignmentKey::Right, + "bottom" => AlignmentKey::Bottom, + "origin" => AlignmentKey::Origin, + "center" => AlignmentKey::Center, + "end" => AlignmentKey::End, +); + +/// An argument key which identifies a margin or padding target. +/// +/// A is the axis type used. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum PaddingKey<A> { + /// All four sides should have the specified padding. + All, + /// Both sides of the given axis should have the specified padding. + Axis(A), + /// Only the given side of the given axis should have the specified padding. + AxisAligned(A, AlignmentKey), +} + +kind!(PaddingKey<AxisKey>, "axis or anchor", + "horizontal" => PaddingKey::Axis(AxisKey::Horizontal), + "vertical" => PaddingKey::Axis(AxisKey::Vertical), + "primary" => PaddingKey::Axis(AxisKey::Primary), + "secondary" => PaddingKey::Axis(AxisKey::Secondary), + + "left" => PaddingKey::AxisAligned(AxisKey::Horizontal, AlignmentKey::Left), + "right" => PaddingKey::AxisAligned(AxisKey::Horizontal, AlignmentKey::Right), + "top" => PaddingKey::AxisAligned(AxisKey::Vertical, AlignmentKey::Top), + "bottom" => PaddingKey::AxisAligned(AxisKey::Vertical, AlignmentKey::Bottom), + + "primary-origin" => PaddingKey::AxisAligned(AxisKey::Primary, AlignmentKey::Origin), + "primary-end" => PaddingKey::AxisAligned(AxisKey::Primary, AlignmentKey::End), + "secondary-origin" => PaddingKey::AxisAligned(AxisKey::Secondary, AlignmentKey::Origin), + "secondary-end" => PaddingKey::AxisAligned(AxisKey::Secondary, AlignmentKey::End), + "horizontal-origin" => PaddingKey::AxisAligned(AxisKey::Horizontal, AlignmentKey::Origin), + "horizontal-end" => PaddingKey::AxisAligned(AxisKey::Horizontal, AlignmentKey::End), + "vertical-origin" => PaddingKey::AxisAligned(AxisKey::Vertical, AlignmentKey::Origin), + "vertical-end" => PaddingKey::AxisAligned(AxisKey::Vertical, AlignmentKey::End), +); diff --git a/src/library/mod.rs b/src/library/mod.rs index 02af0d83..f25c6397 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -6,6 +6,9 @@ use toddle::query::FontClass; pub_use_mod!(align); pub_use_mod!(boxed); +mod keys; +use keys::*; + /// Create a scope with all standard functions. pub fn std() -> Scope { let mut std = Scope::new(); @@ -33,7 +36,7 @@ pub fn std() -> Scope { ("italic", FontClass::Italic), ("mono", FontClass::Monospace), ] { - std.add_with_metadata::<StyleChange, FontClass>(name, *class); + std.add_with_metadata::<StyleChange, FontClass>(name, class.clone()); } std @@ -80,8 +83,8 @@ function! { 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), + width: args.get_key_opt::<Size>("width")?.map(|s| s.v), + height: args.get_key_opt::<Size>("height")?.map(|s| s.v), } } @@ -97,42 +100,18 @@ function! { /// `page.margins`: Set the margins of pages. #[derive(Debug, PartialEq)] pub struct PageMargins { - map: ArgMap<PaddingKey, Size>, + map: ConsistentMap<PaddingKey<AxisKey>, Size>, } parse(args, body) { - use PaddingKey::*; - use AlignmentKey::*; - - let mut map = ArgMap::new(); - map.add_opt(All, args.get_pos_opt::<ArgSize>()?); + let mut map = ConsistentMap::new(); + map.add_opt_span(PaddingKey::All, args.get_pos_opt::<Size>()?)?; 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), - - _ => error!(unexpected_argument), - }; - - let size = ArgParser::convert::<ArgSize>(arg.val.1.val)?; - map.add(key, size); + let key = PaddingKey::from_ident(&arg.v.key)?; + let size = Size::from_expr(arg.v.value)?; + + map.add(key, size)?; } parse!(forbidden: body); @@ -144,25 +123,25 @@ function! { let axes = ctx.axes; let map = self.map.dedup(|key, val| { - match key { + Ok((match key { All => All, Axis(axis) => Axis(axis.specific(axes)), AxisAligned(axis, alignment) => { let axis = axis.specific(axes); AxisAligned(axis, alignment.specific(axes, axis)) } - } - }); + }, val)) + })?; - let style = ctx.style.page; + let mut 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)); + map.with(All, |&val| padding.set_all(val)); + map.with(Axis(SpecificAxisKind::Horizontal), |&val| padding.set_horizontal(val)); + map.with(Axis(SpecificAxisKind::Vertical), |&val| padding.set_vertical(val)); - for (key, val) in map.iter() { - if let AxisAligned(axis, alignment) = key { + for (key, &val) in map.iter() { + if let AxisAligned(_, alignment) = key { match alignment { AlignmentKey::Left => padding.left = val, AlignmentKey::Right => padding.right = val, @@ -182,7 +161,7 @@ function! { #[derive(Debug, PartialEq)] pub struct Spacing { axis: AxisKey, - spacing: SpacingValue, + spacing: FSize, } type Meta = Option<AxisKey>; @@ -191,19 +170,14 @@ function! { let spacing = if let Some(axis) = meta { Spacing { axis, - spacing: SpacingValue::from_expr(args.get_pos::<ArgExpr>()?)?, + spacing: FSize::from_expr(args.get_pos::<Expression>()?)?, } } 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, - _ => error!(unexpected_argument), - }; - - let spacing = SpacingValue::from_expr(arg.val.1.val)?; + let axis = AxisKey::from_ident(&arg.v.key) + .map_err(|_| error!(@unexpected_argument))?; + + let spacing = FSize::from_expr(arg.v.value)?; Spacing { axis, spacing } } else { error!("expected axis and expression") @@ -217,30 +191,14 @@ function! { 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, + FSize::Absolute(size) => size, + FSize::Scaled(scale) => scale * 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), - _ => error!("invalid spacing: expected size or number"), - }) - } -} - function! { /// Sets text with a different style. #[derive(Debug, PartialEq)] @@ -260,7 +218,7 @@ function! { layout(self, ctx) { let mut style = ctx.style.text.clone(); - style.toggle_class(self.class); + style.toggle_class(self.class.clone()); match &self.body { Some(body) => vec