From 665b4d2aca81af48b8e0eaca4e709ef2e7825844 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Fri, 13 Dec 2019 23:59:01 +0100 Subject: [PATCH] =?UTF-8?q?More=20consistent=20library=20code=20and=20func?= =?UTF-8?q?tions=20=F0=9F=8E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.rs | 1 + src/func/macros.rs | 15 +-- src/func/mod.rs | 1 + src/layout/flex.rs | 6 +- src/layout/mod.rs | 39 +++++-- src/layout/stack.rs | 27 +++-- src/layout/text.rs | 6 +- src/layout/tree.rs | 21 ++-- src/lib.rs | 6 +- src/library/align.rs | 52 ++-------- src/library/boxed.rs | 36 +++++-- src/library/direction.rs | 38 ++----- src/library/keys.rs | 186 --------------------------------- src/library/maps.rs | 188 ---------------------------------- src/library/maps/alignment.rs | 76 ++++++++++++++ src/library/maps/axis.rs | 122 ++++++++++++++++++++++ src/library/maps/mod.rs | 103 +++++++++++++++++++ src/library/maps/padding.rs | 92 +++++++++++++++++ src/library/mod.rs | 99 ++++++++++-------- src/size.rs | 31 ++++-- src/style.rs | 73 ++++++++++++- src/syntax/parsing.rs | 11 +- src/syntax/tokens.rs | 2 + tests/layouts/stack.typ | 52 ++++------ 24 files changed, 700 insertions(+), 583 deletions(-) delete mode 100644 src/library/keys.rs delete mode 100644 src/library/maps.rs create mode 100644 src/library/maps/alignment.rs create mode 100644 src/library/maps/axis.rs create mode 100644 src/library/maps/mod.rs create mode 100644 src/library/maps/padding.rs diff --git a/build.rs b/build.rs index 1254e33d1..0c3f1da51 100644 --- a/build.rs +++ b/build.rs @@ -7,6 +7,7 @@ fn main() -> Result<(), Box> { // Make sure the script reruns if this file changes or files are // added/deleted in the parsing folder. println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=tests/cache/parse"); println!("cargo:rerun-if-changed=tests/parsing"); // Compile all parser tests into a single giant vector. diff --git a/src/func/macros.rs b/src/func/macros.rs index 17a554cf0..df7952135 100644 --- a/src/func/macros.rs +++ b/src/func/macros.rs @@ -5,22 +5,25 @@ macro_rules! function { // Parse a unit struct. ($(#[$outer:meta])* pub struct $type:ident; $($rest:tt)*) => { - $(#[$outer])* - pub struct $type; + $(#[$outer])* pub struct $type; function!(@meta $type | $($rest)*); }; // Parse a tuple struct. ($(#[$outer:meta])* pub struct $type:ident($($fields:tt)*); $($rest:tt)*) => { - $(#[$outer])* - pub struct $type($($fields)*); + $(#[$outer])* pub struct $type($($fields)*); function!(@meta $type | $($rest)*); }; // Parse a struct with fields. ($(#[$outer:meta])* pub struct $type:ident { $($fields:tt)* } $($rest:tt)*) => { - $(#[$outer])* - pub struct $type { $($fields)* } + $(#[$outer])* pub struct $type { $($fields)* } + function!(@meta $type | $($rest)*); + }; + + // Parse an enum. + ($(#[$outer:meta])* pub enum $type:ident { $($fields:tt)* } $($rest:tt)*) => { + $(#[$outer])* pub enum $type { $($fields)* } function!(@meta $type | $($rest)*); }; diff --git a/src/func/mod.rs b/src/func/mod.rs index a5328314c..babde1ae3 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -107,6 +107,7 @@ pub enum Command<'a> { FinishRun, FinishSpace, BreakParagraph, + BreakPage, SetTextStyle(TextStyle), SetPageStyle(PageStyle), diff --git a/src/layout/flex.rs b/src/layout/flex.rs index 488538630..72878a297 100644 --- a/src/layout/flex.rs +++ b/src/layout/flex.rs @@ -66,6 +66,7 @@ pub struct FlexContext { pub axes: LayoutAxes, pub alignment: LayoutAlignment, pub flex_spacing: Size, + pub repeat: bool, pub debug: bool, } @@ -76,6 +77,7 @@ impl FlexLayouter { spaces: ctx.spaces, axes: ctx.axes, alignment: ctx.alignment, + repeat: ctx.repeat, debug: ctx.debug, }); @@ -144,7 +146,7 @@ impl FlexLayouter { pub fn finish(mut self) -> LayoutResult { self.finish_space(false)?; - Ok(self.stack.finish()) + self.stack.finish() } pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> { @@ -152,7 +154,7 @@ impl FlexLayouter { self.finish_run()?; } - self.stack.finish_space(hard); + self.stack.finish_space(hard)?; Ok(self.start_line()) } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 58c56dd4a..b2b102645 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -16,7 +16,7 @@ mod text; /// Common types for layouting. pub mod prelude { pub use super::{ - layout_tree, LayoutResult, + layout, LayoutResult, MultiLayout, Layout, LayoutContext, LayoutSpaces, LayoutSpace, LayoutExpansion, LayoutAxes, GenericAxis, SpecificAxis, Direction, LayoutAlignment, Alignment, SpacingKind, @@ -29,7 +29,7 @@ pub mod prelude { /// Different kinds of layouters (fully re-exported). pub mod layouters { - pub use super::tree::layout_tree; + pub use super::tree::layout; pub use super::flex::{FlexLayouter, FlexContext}; pub use super::stack::{StackLayouter, StackContext}; pub use super::text::{layout_text, TextContext}; @@ -107,10 +107,11 @@ pub struct LayoutContext<'a, 'p> { pub loader: &'a SharedFontLoader<'p>, /// The style for pages and text. pub style: &'a LayoutStyle, + /// The base unpadded dimensions of this container (for relative sizing). + pub base: Size2D, /// The spaces to layout in. pub spaces: LayoutSpaces, - /// Whether to repeat the last space or quit with an error if more space - /// would be needed. + /// Whether to have repeated spaces or to use only the first and only once. pub repeat: bool, /// The initial axes along which content is laid out. pub axes: LayoutAxes, @@ -172,6 +173,14 @@ impl LayoutExpansion { pub fn new(horizontal: bool, vertical: bool) -> LayoutExpansion { LayoutExpansion { horizontal, vertical } } + + /// Borrow the spcified component mutably. + pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut bool { + match axis { + Horizontal => &mut self.horizontal, + Vertical => &mut self.vertical, + } + } } /// The axes along which the content is laid out. @@ -192,16 +201,24 @@ impl LayoutAxes { } /// Return the direction of the specified generic axis. - pub fn get_generic(self, axis: GenericAxis) -> Direction { + pub fn get(self, axis: GenericAxis) -> Direction { match axis { Primary => self.primary, Secondary => self.secondary, } } + /// Borrow the direction of the specified generic axis mutably. + pub fn get_mut(&mut self, axis: GenericAxis) -> &mut Direction { + match axis { + Primary => &mut self.primary, + Secondary => &mut self.secondary, + } + } + /// Return the direction of the specified specific axis. pub fn get_specific(self, axis: SpecificAxis) -> Direction { - self.get_generic(axis.to_generic(self)) + self.get(axis.to_generic(self)) } } @@ -215,7 +232,7 @@ pub enum GenericAxis { impl GenericAxis { /// The specific version of this axis in the given system of axes. pub fn to_specific(self, axes: LayoutAxes) -> SpecificAxis { - axes.get_generic(self).axis() + axes.get(self).axis() } /// The other axis. @@ -306,11 +323,11 @@ impl LayoutAlignment { LayoutAlignment { primary, secondary } } - /// Return the alignment of the specified generic axis. - pub fn get(self, axis: GenericAxis) -> Alignment { + /// Borrow the alignment of the specified generic axis mutably. + pub fn get_mut(&mut self, axis: GenericAxis) -> &mut Alignment { match axis { - Primary => self.primary, - Secondary => self.secondary, + Primary => &mut self.primary, + Secondary => &mut self.secondary, } } } diff --git a/src/layout/stack.rs b/src/layout/stack.rs index a2ffdc9be..3c659d8a3 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -27,6 +27,8 @@ pub struct StackContext { /// Which alignment to set on the resulting layout. This affects how it will /// be positioned in a parent box. pub alignment: LayoutAlignment, + /// Whether to have repeated spaces or to use only the first and only once. + pub repeat: bool, /// Whether to output a command which renders a debugging box showing the /// extent of the layout. pub debug: bool, @@ -77,8 +79,9 @@ impl StackLayouter { /// Add a layout to the stack. pub fn add(&mut self, layout: Layout) -> LayoutResult<()> { + // If the alignment cannot be fit in this space, finish it. if !self.update_rulers(layout.alignment) { - self.finish_space(true); + self.finish_space(true)?; } // Now, we add a possibly cached soft space. If the secondary alignment @@ -91,11 +94,11 @@ impl StackLayouter { // Find the first space that fits the layout. while !self.space.usable.fits(layout.dimensions) { if self.space_is_last() && self.space_is_empty() { - error!("box of size {} does not fit into remaining usable size {}", + error!("cannot fit box of size {} into usable size of {}", layout.dimensions, self.space.usable); } - self.finish_space(true); + self.finish_space(true)?; } // Change the usable space and size of the space. @@ -257,15 +260,19 @@ impl StackLayouter { } /// Compute the finished multi-layout. - pub fn finish(mut self) -> MultiLayout { + pub fn finish(mut self) -> LayoutResult { if self.space.hard || !self.space_is_empty() { - self.finish_space(false); + self.finish_space(false)?; } - self.layouts + Ok(self.layouts) } /// Finish the current space and start a new one. - pub fn finish_space(&mut self, hard: bool) { + pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> { + if !self.ctx.repeat && hard { + error!("cannot create new space in a non-repeating context"); + } + let space = self.ctx.spaces[self.space.index]; // ------------------------------------------------------------------ // @@ -304,7 +311,7 @@ impl StackLayouter { // layout uses up space from the origin to the end. Thus, it reduces // the usable space for following layouts at it's origin by its // extent along the secondary axis. - *bound.get_mut(*axes, Secondary, Origin) + *bound.get_mut(axes.secondary, Origin) += axes.secondary.factor() * layout.dimensions.get_secondary(*axes); } @@ -333,7 +340,7 @@ impl StackLayouter { // We reduce the bounding box of this layout at it's end by the // accumulated secondary extent of all layouts we have seen so far, // which are the layouts after this one since we iterate reversed. - *bound.get_mut(*axes, Secondary, End) + *bound.get_mut(axes.secondary, End) -= axes.secondary.factor() * extent.y; // Then, we add this layout's secondary extent to the accumulator. @@ -378,7 +385,7 @@ impl StackLayouter { // ------------------------------------------------------------------ // // Step 5: Start the next space. - self.start_space(self.next_space(), hard); + Ok(self.start_space(self.next_space(), hard)) } /// Start a new space with the given index. diff --git a/src/layout/text.rs b/src/layout/text.rs index af74df136..996c51397 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -61,7 +61,7 @@ impl<'a, 'p> TextLayouter<'a, 'p> { self.buffer = String::new(); } - self.actions.add(LayoutAction::SetFont(index, self.ctx.style.font_size)); + self.actions.add(LayoutAction::SetFont(index, self.ctx.style.font_size())); self.active_font = index; } @@ -73,7 +73,7 @@ impl<'a, 'p> TextLayouter<'a, 'p> { } Ok(Layout { - dimensions: Size2D::new(self.width, self.ctx.style.font_size), + dimensions: Size2D::new(self.width, self.ctx.style.font_size()), alignment: self.ctx.alignment, actions: self.actions.to_vec(), }) @@ -108,7 +108,7 @@ impl<'a, 'p> TextLayouter<'a, 'p> { .advance_width as f32; let char_width = font_unit_to_size(glyph_width) - * self.ctx.style.font_size.to_pt(); + * self.ctx.style.font_size().to_pt(); return Ok((index, char_width)); } diff --git a/src/layout/tree.rs b/src/layout/tree.rs index e9d8850df..3c6a4274d 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -7,7 +7,7 @@ use crate::style::TextStyle; use super::*; /// Layout a syntax tree into a multibox. -pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult { +pub fn layout(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult { let mut layouter = TreeLayouter::new(ctx); layouter.layout(tree)?; layouter.finish() @@ -28,6 +28,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { spaces: ctx.spaces.clone(), axes: ctx.axes, alignment: ctx.alignment, + repeat: ctx.repeat, debug: ctx.debug, }), style: ctx.style.clone(), @@ -106,8 +107,15 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { FinishLine => {}, FinishRun => {}, - FinishSpace => self.stack.finish_space(true), + FinishSpace => self.stack.finish_space(true)?, BreakParagraph => self.layout_paragraph()?, + BreakPage => { + if self.ctx.nested { + error!("page break cannot be issued from nested context"); + } + + self.stack.finish_space(true)? + } SetTextStyle(style) => self.style.text = style, SetPageStyle(style) => { @@ -116,6 +124,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { } self.style.page = style; + self.ctx.base = style.dimensions.unpadded(style.margins); self.stack.set_spaces(smallvec![ LayoutSpace { dimensions: style.dimensions, @@ -135,18 +144,18 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { } fn finish(self) -> LayoutResult { - Ok(self.stack.finish()) + self.stack.finish() } } fn word_spacing(style: &TextStyle) -> Size { - style.word_spacing * style.font_size + style.word_spacing * style.font_size() } fn flex_spacing(style: &TextStyle) -> Size { - (style.line_spacing - 1.0) * style.font_size + (style.line_spacing - 1.0) * style.font_size() } fn paragraph_spacing(style: &TextStyle) -> Size { - (style.paragraph_spacing - 1.0) * style.font_size + (style.paragraph_spacing - 1.0) * style.font_size() } diff --git a/src/lib.rs b/src/lib.rs index 922ff76e7..c4e8fbe1e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,8 +27,7 @@ use toddle::query::{FontLoader, FontProvider, SharedFontLoader}; use toddle::Error as FontError; use crate::func::Scope; -use crate::layout::{layout_tree, MultiLayout, LayoutContext, LayoutResult}; -use crate::layout::{LayoutSpace, LayoutExpansion, LayoutAxes, LayoutAlignment}; +use crate::layout::{MultiLayout, LayoutResult}; use crate::syntax::{parse, SyntaxTree, ParseContext, Span, ParseResult}; use crate::style::{LayoutStyle, PageStyle, TextStyle}; @@ -92,11 +91,12 @@ impl<'p> Typesetter<'p> { /// Layout a syntax tree and return the produced layout. pub fn layout(&self, tree: &SyntaxTree) -> LayoutResult { use crate::layout::prelude::*; - Ok(layout_tree( + Ok(layout( &tree, LayoutContext { loader: &self.loader, style: &self.style, + base: self.style.page.dimensions.unpadded(self.style.page.margins), spaces: smallvec![LayoutSpace { dimensions: self.style.page.dimensions, padding: self.style.page.margins, diff --git a/src/library/align.rs b/src/library/align.rs index e6952dc21..524ada61d 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -1,64 +1,34 @@ use crate::func::prelude::*; -use super::maps::ConsistentMap; -use super::keys::{AxisKey, AlignmentKey}; +use super::maps::{PosAxisMap, AlignmentKey}; function! { /// `align`: Aligns content along the layouting axes. #[derive(Debug, PartialEq)] pub struct Align { body: Option, - map: ConsistentMap, + map: PosAxisMap, } parse(args, body, ctx) { - let mut map = ConsistentMap::new(); - - map.add_opt(Key::First, args.get_pos_opt::()?)?; - map.add_opt(Key::Second, args.get_pos_opt::()?)?; - - for arg in args.keys() { - let axis = AxisKey::from_ident(&arg.v.key)?; - let value = AlignmentKey::from_expr(arg.v.value)?; - - map.add(Key::Axis(axis), value)?; - } - Align { body: parse!(optional: body, ctx), - map, + map: PosAxisMap::new(&mut args)?, } } layout(self, mut ctx) { - let axes = ctx.axes; + ctx.base = ctx.spaces[0].dimensions; - let map = self.map.dedup(|key, alignment| { - let axis = match key { - Key::First => alignment.axis(axes, Primary), - Key::Second => alignment.axis(axes, Secondary), - Key::Axis(AxisKey::Primary) => Primary, - Key::Axis(AxisKey::Secondary) => Secondary, - Key::Axis(AxisKey::Horizontal) => Horizontal.to_generic(axes), - Key::Axis(AxisKey::Vertical) => Vertical.to_generic(axes), - }; - - let alignment = alignment.to_generic(axes, axis)?; - Ok((axis, alignment)) - })?; - - map.with(Primary, |&val| ctx.alignment.primary = val); - map.with(Secondary, |&val| ctx.alignment.secondary = val); + let map = self.map.dedup(ctx.axes, |alignment| alignment.axis(ctx.axes))?; + for &axis in &[Primary, Secondary] { + if let Some(alignment) = map.get(axis) { + *ctx.alignment.get_mut(axis) = alignment.to_generic(ctx.axes, axis)?; + } + } match &self.body { - Some(body) => vec![AddMultiple(layout_tree(&body, ctx)?)], + Some(body) => vec![AddMultiple(layout(&body, ctx)?)], None => vec![Command::SetAlignment(ctx.alignment)], } } } - -#[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 2d0d6e6b6..3e9a4c7a8 100644 --- a/src/library/boxed.rs +++ b/src/library/boxed.rs @@ -1,3 +1,5 @@ +use smallvec::smallvec; + use crate::func::prelude::*; use super::maps::ExtentMap; @@ -19,18 +21,34 @@ function! { } layout(self, mut ctx) { + ctx.repeat = false; ctx.debug = self.debug; - let space = &mut ctx.spaces[0]; - self.map.apply_with(ctx.axes, |axis, p| { - let entity = match axis { - Horizontal => { space.expansion.horizontal = true; &mut space.dimensions.x }, - Vertical => { space.expansion.vertical = true; &mut space.dimensions.y }, - }; + let map = self.map.dedup(ctx.axes)?; - *entity = p.concretize(*entity) - })?; + // Try to layout this box in all spaces. + let mut error = None; + for &space in &ctx.spaces { + let mut ctx = ctx.clone(); + let mut space = space; - vec![AddMultiple(layout_tree(&self.body, ctx)?)] + for &axis in &[Horizontal, Vertical] { + if let Some(psize) = map.get(axis) { + let size = psize.concretize(ctx.base.get(axis)); + *ctx.base.get_mut(axis) = size; + *space.dimensions.get_mut(axis) = size; + *space.expansion.get_mut(axis) = true; + } + } + + ctx.spaces = smallvec![space]; + + match layout(&self.body, ctx) { + Ok(layouts) => return Ok(vec![AddMultiple(layouts)]), + Err(err) => error = Some(err), + } + } + + return Err(error.expect("expected at least one space")); } } diff --git a/src/library/direction.rs b/src/library/direction.rs index 59c0d1fd2..7bb11c997 100644 --- a/src/library/direction.rs +++ b/src/library/direction.rs @@ -1,59 +1,41 @@ use crate::func::prelude::*; -use super::maps::ConsistentMap; -use super::keys::AxisKey; +use super::maps::PosAxisMap; function! { /// `direction`: Sets the directions of the layouting axes. #[derive(Debug, PartialEq)] pub struct DirectionChange { body: Option, - map: ConsistentMap, + map: PosAxisMap, } parse(args, body, ctx) { - let mut map = ConsistentMap::new(); - - map.add_opt(AxisKey::Primary, args.get_pos_opt::()?)?; - map.add_opt(AxisKey::Secondary, args.get_pos_opt::()?)?; - - for arg in args.keys() { - let axis = AxisKey::from_ident(&arg.v.key)?; - let value = Direction::from_expr(arg.v.value)?; - - map.add(axis, value)?; - } - DirectionChange { body: parse!(optional: body, ctx), - map, + map: PosAxisMap::new(&mut args)?, } } layout(self, mut ctx) { - let axes = ctx.axes; + ctx.base = ctx.spaces[0].dimensions; - let map = self.map.dedup(|key, &direction| { - Ok((match key { - AxisKey::Primary => Primary, - AxisKey::Secondary => Secondary, - AxisKey::Horizontal => Horizontal.to_generic(axes), - AxisKey::Vertical => Vertical.to_generic(axes), - }, direction)) + let map = self.map.dedup(ctx.axes, |direction| { + Some(direction.axis().to_generic(ctx.axes)) })?; - map.with(Primary, |&val| ctx.axes.primary = val); - map.with(Secondary, |&val| ctx.axes.secondary = val); + map.with(Primary, |&dir| ctx.axes.primary = dir); + map.with(Secondary, |&dir| ctx.axes.secondary = dir); if ctx.axes.primary.axis() == ctx.axes.secondary.axis() { error!( - "aligned primary and secondary axes: `{}`, `{}`", + "invalid aligned primary and secondary axes: `{}`, `{}`", format!("{:?}", ctx.axes.primary).to_lowercase(), format!("{:?}", ctx.axes.secondary).to_lowercase(), ); } match &self.body { - Some(body) => vec![AddMultiple(layout_tree(&body, ctx)?)], + Some(body) => vec![AddMultiple(layout(&body, ctx)?)], None => vec![Command::SetAxes(ctx.axes)], } } diff --git a/src/library/keys.rs b/src/library/keys.rs deleted file mode 100644 index bee45638d..000000000 --- a/src/library/keys.rs +++ /dev/null @@ -1,186 +0,0 @@ -//! Keys for the consistent maps. - -use super::*; - -macro_rules! kind { - ($type:ty, $name:expr, $($patterns:tt)*) => { - impl $type { - /// Parse this key from an identifier. - pub fn from_ident(ident: &Spanned) -> ParseResult { - Ok(match ident.v.0.as_str() { - $($patterns)* - _ => error!("expected {}", ::NAME), - }) - } - } - - impl ExpressionKind for $type { - const NAME: &'static str = $name; - - fn from_expr(expr: Spanned) -> ParseResult { - 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 to_generic(self, axes: LayoutAxes) -> GenericAxis { - match self { - AxisKey::Primary => Primary, - AxisKey::Secondary => Secondary, - AxisKey::Vertical => Vertical.to_generic(axes), - AxisKey::Horizontal => Horizontal.to_generic(axes), - } - } - - /// The specific version of this axis key in the given system of axes. - pub fn to_specific(self, axes: LayoutAxes) -> SpecificAxis { - match self { - AxisKey::Primary => Primary.to_specific(axes), - AxisKey::Secondary => Secondary.to_specific(axes), - AxisKey::Vertical => Vertical, - AxisKey::Horizontal => Horizontal, - } - } -} - -/// An argument key which describes 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: GenericAxis) -> GenericAxis { - use AlignmentKey::*; - match self { - Origin | Center | End => default, - Left | Right => Horizontal.to_generic(axes), - Top | Bottom => Vertical.to_generic(axes), - } - } - - /// 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 to_generic(self, axes: LayoutAxes, axis: GenericAxis) -> LayoutResult { - let specific = axis.to_specific(axes); - - Ok(match (self, specific) { - (AlignmentKey::Origin, _) => Origin, - (AlignmentKey::Center, _) => Center, - (AlignmentKey::End, _) => End, - - (AlignmentKey::Left, Horizontal) | (AlignmentKey::Top, Vertical) => { - if axes.get_specific(specific).is_positive() { Origin } else { End } - } - - (AlignmentKey::Right, Horizontal) | (AlignmentKey::Bottom, Vertical) => { - if axes.get_specific(specific).is_positive() { End } else { Origin } - } - - _ => 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 to_specific(self, axes: LayoutAxes, axis: SpecificAxis) -> AlignmentKey { - use AlignmentKey::*; - - 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, - } - } -} - -/// An argument key which identifies a margin or padding target. -/// -/// A is the used axis type. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum PaddingKey { - /// 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!(AxisKey, "axis", - "horizontal" | "h" => AxisKey::Horizontal, - "vertical" | "v" => AxisKey::Vertical, - "primary" | "p" => AxisKey::Primary, - "secondary" | "s" => AxisKey::Secondary, -); - -kind!(AlignmentKey, "alignment", - "left" => AlignmentKey::Left, - "top" => AlignmentKey::Top, - "right" => AlignmentKey::Right, - "bottom" => AlignmentKey::Bottom, - "origin" => AlignmentKey::Origin, - "center" => AlignmentKey::Center, - "end" => AlignmentKey::End, -); - -kind!(PaddingKey, "axis or side", - "horizontal" | "h" => PaddingKey::Axis(AxisKey::Horizontal), - "vertical" | "v" => PaddingKey::Axis(AxisKey::Vertical), - "primary" | "p" => PaddingKey::Axis(AxisKey::Primary), - "secondary" | "s" => 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), -); - -kind!(Direction, "direction", - "left-to-right" | "ltr" => LeftToRight, - "right-to-left" | "rtl" => RightToLeft, - "top-to-bottom" | "ttb" => TopToBottom, - "bottom-to-top" | "btt" => BottomToTop, -); diff --git a/src/library/maps.rs b/src/library/maps.rs deleted file mode 100644 index 13b4dc5d6..000000000 --- a/src/library/maps.rs +++ /dev/null @@ -1,188 +0,0 @@ -//! Deduplicating maps for argument parsing. - -use std::collections::HashMap; -use std::hash::Hash; - -use super::*; - -/// A deduplicating map type useful for storing possibly redundant arguments. -#[derive(Debug, Clone, PartialEq)] -pub struct ConsistentMap where K: Hash + Eq { - map: HashMap, -} - -impl ConsistentMap where K: Hash + Eq { - pub fn new() -> ConsistentMap { - ConsistentMap { map: HashMap::new() } - } - - /// Add a key-value pair. - pub fn add(&mut self, key: K, value: V) -> ParseResult<()> { - match self.map.insert(key, value) { - Some(_) => error!("duplicate arguments"), - None => Ok(()) - } - } - - /// Add a key-value pair if the value is not `None`. - pub fn add_opt(&mut self, key: K, value: Option) -> ParseResult<()> { - Ok(if let Some(value) = value { - self.add(key, value)?; - }) - } - - /// Call a function with the value if the key is present. - pub fn with(&self, key: K, callback: F) where F: FnOnce(&V) { - if let Some(value) = self.map.get(&key) { - callback(value); - } - } - - /// Create a new consistent map where keys and values are mapped to new keys - /// and values. - /// - /// Returns an error if a new key is duplicate. - pub fn dedup(&self, f: F) -> LayoutResult> - where - F: Fn(&K, &V) -> ParseResult<(K2, V2)>, - K2: Hash + Eq - { - let mut map = ConsistentMap::new(); - - for (key, value) in self.map.iter() { - let (key, value) = f(key, value)?; - map.add(key, value)?; - } - - Ok(map) - } - - /// Iterate over the (key, value) pairs. - pub fn iter(&self) -> std::collections::hash_map::Iter<'_, K, V> { - self.map.iter() - } -} - -/// A map for storing extents along axes. -#[derive(Debug, Clone, PartialEq)] -pub struct ExtentMap(ConsistentMap); - -impl ExtentMap { - /// Parse an extent map from the function args. - /// - /// If `enforce` is true other arguments will create an error, otherwise - /// they are left intact. - pub fn new(args: &mut FuncArgs, enforce: bool) -> ParseResult> { - let mut map = ConsistentMap::new(); - - for arg in args.keys() { - let key = match arg.v.key.v.0.as_str() { - "width" | "w" => AxisKey::Horizontal, - "height" | "h" => AxisKey::Vertical, - "primary-size" | "ps" => AxisKey::Primary, - "secondary-size" | "ss" => AxisKey::Secondary, - - _ => if enforce { - error!("expected dimension") - } else { - args.add_key(arg); - continue; - } - }; - - let e = E::from_expr(arg.v.value)?; - map.add(key, e)?; - } - - Ok(ExtentMap(map)) - } - - /// Apply the extents on the dimensions. - pub fn apply( - &self, - axes: LayoutAxes, - dimensions: &mut Size2D, - size: F - ) -> LayoutResult<()> where F: Fn(&E) -> Size { - let map = self.dedup(axes)?; - map.with(Horizontal, |val| dimensions.x = size(val)); - map.with(Vertical, |val| dimensions.y = size(val)); - Ok(()) - } - - /// Map from any axis key to the specific axis kind. - pub fn apply_with(&self, axes: LayoutAxes, mut f: F) -> LayoutResult<()> - where F: FnMut(SpecificAxis, &E) { - for (&key, value) in self.dedup(axes)?.iter() { - f(key, value); - } - Ok(()) - } - - fn dedup(&self, axes: LayoutAxes) -> LayoutResult> { - self.0.dedup(|key, &val| Ok((key.to_specific(axes), val))) - } -} - -/// A map for storing padding at sides. -#[derive(Debug, Clone, PartialEq)] -pub struct PaddingMap(ConsistentMap, Size>); - -impl PaddingMap { - /// Parse an extent map from the function args. - /// - /// If `enforce` is true other arguments will create an error, otherwise - /// they are left intact. - pub fn new(args: &mut FuncArgs, enforce: bool) -> ParseResult { - let mut map = ConsistentMap::new(); - - map.add_opt(PaddingKey::All, args.get_pos_opt::()?)?; - - for arg in args.keys() { - let key = match PaddingKey::from_ident(&arg.v.key) { - Ok(key) => key, - e => if enforce { e? } else { args.add_key(arg); continue; } - }; - - let size = Size::from_expr(arg.v.value)?; - - map.add(key, size)?; - } - - Ok(PaddingMap(map)) - } - - /// Map from any axis key to the specific axis kind. - pub fn apply(&self, axes: LayoutAxes, padding: &mut SizeBox) -> LayoutResult<()> { - use PaddingKey::*; - - let map = self.0.dedup(|key, &val| { - Ok((match key { - All => All, - Axis(axis) => Axis(axis.to_specific(axes)), - AxisAligned(axis, alignment) => { - let axis = axis.to_specific(axes); - AxisAligned(axis, alignment.to_specific(axes, axis)) - } - }, val)) - })?; - - map.with(All, |&val| padding.set_all(val)); - map.with(Axis(Horizontal), |&val| padding.set_horizontal(val)); - map.with(Axis(Vertical), |&val| padding.set_vertical(val)); - - for (key, &val) in map.iter() { - if let AxisAligned(_, alignment) = key { - match alignment { - AlignmentKey::Left => padding.left = val, - AlignmentKey::Right => padding.right = val, - AlignmentKey::Top => padding.top = val, - AlignmentKey::Bottom => padding.bottom = val, - _ => {}, - } - } - } - - Ok(()) - } -} diff --git a/src/library/maps/alignment.rs b/src/library/maps/alignment.rs new file mode 100644 index 000000000..486c8b2e8 --- /dev/null +++ b/src/library/maps/alignment.rs @@ -0,0 +1,76 @@ +use super::*; +use AlignmentKey::*; + +/// An argument key which describes a target alignment. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum AlignmentKey { + Align(Alignment), + Left, + Top, + Right, + Bottom, +} + +impl AlignmentKey { + /// The generic axis this alignment key corresponds to in the given system + /// of layouting axes. `None` if the alignment is generic. + pub fn axis(self, axes: LayoutAxes) -> Option { + match self { + Left | Right => Some(Horizontal.to_generic(axes)), + Top | Bottom => Some(Vertical.to_generic(axes)), + Align(_) => None, + } + } + + /// 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 to_generic(self, axes: LayoutAxes, axis: GenericAxis) -> LayoutResult { + let specific = axis.to_specific(axes); + let start = match axes.get(axis).is_positive() { + true => Origin, + false => End, + }; + + Ok(match (self, specific) { + (Align(alignment), _) => alignment, + (Left, Horizontal) | (Top, Vertical) => start, + (Right, Horizontal) | (Bottom, Vertical) => start.inv(), + + _ => 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 to_specific(self, axes: LayoutAxes, axis: SpecificAxis) -> AlignmentKey { + let direction = axes.get_specific(axis); + if let Align(alignment) = self { + match (direction, alignment) { + (LeftToRight, Origin) | (RightToLeft, End) => Left, + (LeftToRight, End) | (RightToLeft, Origin) => Right, + (TopToBottom, Origin) | (BottomToTop, End) => Top, + (TopToBottom, End) | (BottomToTop, Origin) => Bottom, + (_, Center) => self, + } + } else { + self + } + } +} + +key!(AlignmentKey, "alignment", + "origin" => Align(Origin), + "center" => Align(Center), + "end" => Align(End), + + "left" => Left, + "top" => Top, + "right" => Right, + "bottom" => Bottom, +); diff --git a/src/library/maps/axis.rs b/src/library/maps/axis.rs new file mode 100644 index 000000000..a177a324f --- /dev/null +++ b/src/library/maps/axis.rs @@ -0,0 +1,122 @@ +use super::*; +use AxisKey::*; + +/// An argument key which identifies a layouting axis. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum AxisKey { + Generic(GenericAxis), + Specific(SpecificAxis), +} + +impl AxisKey { + /// The generic version of this axis key in the given system of axes. + pub fn to_generic(self, axes: LayoutAxes) -> GenericAxis { + match self { + Generic(axis) => axis, + Specific(axis) => axis.to_generic(axes), + } + } + + /// The specific version of this axis key in the given system of axes. + pub fn to_specific(self, axes: LayoutAxes) -> SpecificAxis { + match self { + Generic(axis) => axis.to_specific(axes), + Specific(axis) => axis, + } + } +} + +key!(AxisKey, "axis", + "horizontal" | "h" => Specific(Horizontal), + "vertical" | "v" => Specific(Vertical), + "primary" | "p" => Generic(Primary), + "secondary" | "s" => Generic(Secondary), +); + +/// A map for storing extents along axes. +#[derive(Debug, Clone, PartialEq)] +pub struct ExtentMap(ConsistentMap); + +impl ExtentMap { + /// Parse an extent map from the function args. + /// + /// If `enforce` is true other arguments will create an error, otherwise + /// they are left intact. + pub fn new(args: &mut FuncArgs, enforce: bool) -> ParseResult> { + let mut map = ConsistentMap::new(); + + for arg in args.keys() { + let key = match arg.v.key.v.0.as_str() { + "width" | "w" => AxisKey::Specific(Horizontal), + "height" | "h" => AxisKey::Specific(Vertical), + "primary-size" | "ps" => AxisKey::Generic(Primary), + "secondary-size" | "ss" => AxisKey::Generic(Secondary), + + _ => if enforce { + error!("expected dimension") + } else { + args.add_key(arg); + continue; + } + }; + + let e = E::from_expr(arg.v.value)?; + map.add(key, e)?; + } + + Ok(ExtentMap(map)) + } + + /// Deduplicate from generic to specific axes. + pub fn dedup(&self, axes: LayoutAxes) -> LayoutResult> { + self.0.dedup(|key, &val| Ok((key.to_specific(axes), val))) + } +} + +/// An argument key which identifies an axis, but allows for positional +/// arguments with unspecified axes. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum PosAxisKey { + /// The first positional argument. + First, + /// The second positional argument. + Second, + /// An axis keyword argument. + Keyword(AxisKey), +} + +/// A map for storing some data for via keyword or positionally given axes. +#[derive(Debug, Clone, PartialEq)] +pub struct PosAxisMap(ConsistentMap); + +impl PosAxisMap { + /// Parse a positional axis map from the function args. + pub fn new(args: &mut FuncArgs) -> ParseResult> { + let mut map = ConsistentMap::new(); + + map.add_opt(PosAxisKey::First, args.get_pos_opt::()?)?; + map.add_opt(PosAxisKey::Second, args.get_pos_opt::()?)?; + + for arg in args.keys() { + let axis = AxisKey::from_ident(&arg.v.key)?; + let value = E::from_expr(arg.v.value)?; + + map.add(PosAxisKey::Keyword(axis), value)?; + } + + Ok(PosAxisMap(map)) + } + + /// Deduplicate from positional or specific to generic axes. + pub fn dedup(&self, axes: LayoutAxes, f: F) -> LayoutResult> + where F: Fn(E) -> Option { + self.0.dedup(|key, &e| { + Ok((match key { + PosAxisKey::First => f(e).unwrap_or(Primary), + PosAxisKey::Second => f(e).unwrap_or(Secondary), + PosAxisKey::Keyword(AxisKey::Specific(axis)) => axis.to_generic(axes), + PosAxisKey::Keyword(AxisKey::Generic(axis)) => *axis, + }, e)) + }) + } +} diff --git a/src/library/maps/mod.rs b/src/library/maps/mod.rs new file mode 100644 index 000000000..284c71810 --- /dev/null +++ b/src/library/maps/mod.rs @@ -0,0 +1,103 @@ +//! Deduplicating maps and keys for argument parsing. + +use std::collections::HashMap; +use std::hash::Hash; + +use crate::func::prelude::*; + +macro_rules! key { + ($type:ty, $name:expr, $($patterns:tt)*) => { + impl $type { + /// Parse this key from an identifier. + pub fn from_ident(ident: &Spanned) -> ParseResult { + Ok(match ident.v.0.as_str() { + $($patterns)* + _ => error!("expected {}", ::NAME), + }) + } + } + + impl ExpressionKind for $type { + const NAME: &'static str = $name; + + fn from_expr(expr: Spanned) -> ParseResult { + if let Expression::Ident(ident) = expr.v { + Self::from_ident(&Spanned::new(ident, expr.span)) + } else { + error!("expected {}", Self::NAME); + } + } + } + }; +} + +pub_use_mod!(axis); +pub_use_mod!(alignment); +pub_use_mod!(padding); + +/// A deduplicating map type useful for storing possibly redundant arguments. +#[derive(Debug, Clone, PartialEq)] +pub struct ConsistentMap where K: Hash + Eq { + map: HashMap, +} + +impl ConsistentMap where K: Hash + Eq { + pub fn new() -> ConsistentMap { + ConsistentMap { map: HashMap::new() } + } + + /// Add a key-value pair. + pub fn add(&mut self, key: K, value: V) -> ParseResult<()> { + match self.map.insert(key, value) { + Some(_) => error!("duplicate argument"), + None => Ok(()) + } + } + + /// Add a key-value pair if the value is not `None`. + pub fn add_opt(&mut self, key: K, value: Option) -> ParseResult<()> { + Ok(if let Some(value) = value { + self.add(key, value)?; + }) + } + + /// Get the value at a key if it is present. + pub fn get(&self, key: K) -> Option<&V> { + self.map.get(&key) + } + + /// Call a function with the value if the key is present. + pub fn with(&self, key: K, callback: F) where F: FnOnce(&V) { + if let Some(value) = self.map.get(&key) { + callback(value); + } + } + + /// Create a new consistent map where keys and values are mapped to new keys + /// and values. + /// + /// Returns an error if a new key is duplicate. + pub fn dedup(&self, f: F) -> LayoutResult> + where F: Fn(&K, &V) -> ParseResult<(K2, V2)>, K2: Hash + Eq { + let mut map = ConsistentMap::new(); + + for (key, value) in self.map.iter() { + let (key, value) = f(key, value)?; + map.add(key, value)?; + } + + Ok(map) + } + + /// Iterate over the (key, value) pairs. + pub fn iter(&self) -> std::collections::hash_map::Iter<'_, K, V> { + self.map.iter() + } +} + +key!(Direction, "direction", + "left-to-right" | "ltr" => LeftToRight, + "right-to-left" | "rtl" => RightToLeft, + "top-to-bottom" | "ttb" => TopToBottom, + "bottom-to-top" | "btt" => BottomToTop, +); diff --git a/src/library/maps/padding.rs b/src/library/maps/padding.rs new file mode 100644 index 000000000..37f2ba4ad --- /dev/null +++ b/src/library/maps/padding.rs @@ -0,0 +1,92 @@ +use super::*; +use AxisKey::*; +use AlignmentKey::*; +use PaddingKey::*; + +/// An argument key which identifies a margin or padding target. +/// +/// A is the used axis type. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum PaddingKey { + /// All four sides should have the specified padding. + All, + /// Both sides of the given axis should have the specified padding. + Both(A), + /// Only the given side of the given axis should have the specified padding. + Side(A, AlignmentKey), +} + +key!(PaddingKey, "axis or side", + "horizontal" | "h" => Both(Specific(Horizontal)), + "vertical" | "v" => Both(Specific(Vertical)), + "primary" | "p" => Both(Generic(Primary)), + "secondary" | "s" => Both(Generic(Secondary)), + + "left" => Side(Specific(Horizontal), Left), + "right" => Side(Specific(Horizontal), Right), + "top" => Side(Specific(Vertical), Top), + "bottom" => Side(Specific(Vertical), Bottom), + + "primary-origin" => Side(Generic(Primary), Align(Origin)), + "primary-end" => Side(Generic(Primary), Align(End)), + "secondary-origin" => Side(Generic(Secondary), Align(Origin)), + "secondary-end" => Side(Generic(Secondary), Align(End)), + "horizontal-origin" => Side(Specific(Horizontal), Align(Origin)), + "horizontal-end" => Side(Specific(Horizontal), Align(End)), + "vertical-origin" => Side(Specific(Vertical), Align(Origin)), + "vertical-end" => Side(Specific(Vertical), Align(End)), +); + +/// A map for storing padding at sides. +#[derive(Debug, Clone, PartialEq)] +pub struct PaddingMap(ConsistentMap, Size>); + +impl PaddingMap { + /// Parse a padding map from the function args. + pub fn new(args: &mut FuncArgs) -> ParseResult { + let mut map = ConsistentMap::new(); + map.add_opt(PaddingKey::All, args.get_pos_opt::()?)?; + + for arg in args.keys() { + let key = PaddingKey::from_ident(&arg.v.key)?; + let size = Size::from_expr(arg.v.value)?; + map.add(key, size)?; + } + + Ok(PaddingMap(map)) + } + + /// Apply the specified padding on the size box. + pub fn apply(&self, axes: LayoutAxes, padding: &mut SizeBox) -> LayoutResult<()> { + use PaddingKey::*; + + let map = self.0.dedup(|key, &val| { + Ok((match key { + All => All, + Both(axis) => Both(axis.to_specific(axes)), + Side(axis, alignment) => { + let axis = axis.to_specific(axes); + Side(axis, alignment.to_specific(axes, axis)) + } + }, val)) + })?; + + map.with(All, |&val| padding.set_all(val)); + map.with(Both(Horizontal), |&val| padding.set_horizontal(val)); + map.with(Both(Vertical), |&val| padding.set_vertical(val)); + + for (key, &val) in map.iter() { + if let Side(_, alignment) = key { + match alignment { + AlignmentKey::Left => padding.left = val, + AlignmentKey::Right => padding.right = val, + AlignmentKey::Top => padding.top = val, + AlignmentKey::Bottom => padding.bottom = val, + _ => {}, + } + } + } + + Ok(()) + } +} diff --git a/src/library/mod.rs b/src/library/mod.rs index 9a49896b2..1e5e406c8 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -3,11 +3,10 @@ use toddle::query::FontClass; use crate::func::prelude::*; -use self::keys::*; -use self::maps::*; +use crate::style::parse_paper_name; +use self::maps::{ExtentMap, PaddingMap, AxisKey}; pub mod maps; -pub mod keys; pub_use_mod!(align); pub_use_mod!(boxed); @@ -20,29 +19,23 @@ pub fn std() -> Scope { std.add::("align"); std.add::("box"); std.add::("direction"); - std.add::("page.size"); - std.add::("page.margins"); std.add::("n"); std.add::("line.break"); std.add::("par.break"); std.add::("page.break"); - - std.add::("font.size"); + std.add::("page.size"); + std.add::("page.margins"); std.add_with_metadata::("spacing", None); + std.add_with_metadata::("h", Some(Horizontal)); + std.add_with_metadata::("v", Some(Vertical)); - for (name, key) in &[("h", AxisKey::Horizontal), ("v", AxisKey::Vertical)] { - std.add_with_metadata::(name, Some(*key)); - } + std.add_with_metadata::("bold", FontClass::Bold); + std.add_with_metadata::("italic", FontClass::Italic); + std.add_with_metadata::("mono", FontClass::Monospace); - for (name, class) in &[ - ("bold", FontClass::Bold), - ("italic", FontClass::Italic), - ("mono", FontClass::Monospace), - ] { - std.add_with_metadata::(name, class.clone()); - } + std.add::("font.size"); std } @@ -67,33 +60,46 @@ function! { layout() { vec![BreakParagraph] } } - function! { /// `page.break`: Ends the current page. #[derive(Debug, Default, PartialEq)] pub struct PageBreak; parse(default) - layout() { vec![FinishSpace] } + layout() { vec![BreakPage] } } function! { /// `page.size`: Set the size of pages. #[derive(Debug, PartialEq)] - pub struct PageSize { - map: ExtentMap, + pub enum PageSize { + Map(ExtentMap), + Size(Size2D), } parse(args, body) { parse!(forbidden: body); - PageSize { - map: ExtentMap::new(&mut args, true)?, + + if let Some(name) = args.get_pos_opt::()? { + PageSize::Size(parse_paper_name(name.0.as_str())?) + } else { + PageSize::Map(ExtentMap::new(&mut args, true)?) } } layout(self, ctx) { let mut style = ctx.style.page; - self.map.apply(ctx.axes, &mut style.dimensions, |&s| s)?; + let dims = &mut style.dimensions; + + match self { + PageSize::Map(map) => { + let map = map.dedup(ctx.axes)?; + map.with(Horizontal, |&psize| dims.x = psize.concretize(dims.x)); + map.with(Vertical, |&psize| dims.y = psize.concretize(dims.y)); + } + PageSize::Size(size) => *dims = *size, + } + vec![SetPageStyle(style)] } } @@ -108,7 +114,7 @@ function! { parse(args, body) { parse!(forbidden: body); PageMargins { - map: PaddingMap::new(&mut args, true)?, + map: PaddingMap::new(&mut args)?, } } @@ -127,33 +133,30 @@ function! { spacing: FSize, } - type Meta = Option; + type Meta = Option; parse(args, body, _, meta) { - let spacing = if let Some(axis) = meta { + parse!(forbidden: body); + + if let Some(axis) = meta { Spacing { - axis, + axis: AxisKey::Specific(axis), spacing: FSize::from_expr(args.get_pos::>()?)?, } + } else if let Some(arg) = args.get_key_next() { + 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 { - if let Some(arg) = args.get_key_next() { - 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") - } - }; - - parse!(forbidden: body); - spacing + error!("expected axis and spacing") + } } layout(self, ctx) { let axis = self.axis.to_generic(ctx.axes); - let spacing = self.spacing.concretize(ctx.style.text.font_size); + let spacing = self.spacing.concretize(ctx.style.text.font_size()); vec![AddSpacing(spacing, SpacingKind::Hard, axis)] } } @@ -187,19 +190,25 @@ function! { #[derive(Debug, PartialEq)] pub struct FontSize { body: Option, - size: Size, + size: ScaleSize, } parse(args, body, ctx) { FontSize { body: parse!(optional: body, ctx), - size: args.get_pos::()?, + size: args.get_pos::()?, } } layout(self, ctx) { let mut style = ctx.style.text.clone(); - style.font_size = self.size; + match self.size { + ScaleSize::Absolute(size) => { + style.base_font_size = size; + style.font_scale = 1.0; + } + ScaleSize::Scaled(scale) => style.font_scale = scale, + } styled(&self.body, &ctx, style) } } diff --git a/src/size.rs b/src/size.rs index 420b815bd..412b657d3 100644 --- a/src/size.rs +++ b/src/size.rs @@ -11,7 +11,7 @@ use crate::layout::prelude::*; #[derive(Copy, Clone, PartialEq, PartialOrd)] pub struct Size { /// The size in typographic points (1/72 inches). - points: f32, + pub points: f32, } impl Size { @@ -117,6 +117,22 @@ impl Size2D { /// Create a 2D-size with `x` and `y` set to the same value `s`. pub fn with_all(s: Size) -> Size2D { Size2D { x: s, y: s } } + /// Get the specificed component. + pub fn get(self, axis: SpecificAxis) -> Size { + match axis { + Horizontal => self.x, + Vertical => self.y, + } + } + + /// Get the specificed component mutably. + pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut Size { + match axis { + Horizontal => &mut self.x, + Vertical => &mut self.y, + } + } + /// Access the primary size of this specialized 2D-size. pub fn get_primary(self, axes: LayoutAxes) -> Size { if axes.primary.axis() == Horizontal { self.x } else { self.y } @@ -242,20 +258,15 @@ impl SizeBox { SizeBox { left: value, top: value, right: value, bottom: value } } - /// Get a mutable reference to the value for the specified axis and + /// Get a mutable reference to the value for the specified direction and /// alignment. Center alignment will be treated the same as origin /// alignment. - pub fn get_mut(&mut self, - axes: LayoutAxes, - axis: GenericAxis, - alignment: Alignment, - ) -> &mut Size { - let mut normalized = axes.get_generic(axis); + pub fn get_mut(&mut self, mut direction: Direction, alignment: Alignment) -> &mut Size { if alignment == End { - normalized = normalized.inv(); + direction = direction.inv(); } - match normalized { + match direction { LeftToRight => &mut self.left, RightToLeft => &mut self.right, TopToBottom => &mut self.top, diff --git a/src/style.rs b/src/style.rs index 616619e1a..1dcae0da3 100644 --- a/src/style.rs +++ b/src/style.rs @@ -4,6 +4,7 @@ use toddle::query::FontClass; use FontClass::*; use crate::size::{Size, Size2D, SizeBox}; +use crate::syntax::ParseResult; /// Defines properties of pages and text. #[derive(Debug, Default, Clone)] @@ -20,8 +21,10 @@ pub struct TextStyle { /// The fallback classes from which the font needs to match the /// leftmost possible one. pub fallback: Vec, - /// The font size. - pub font_size: Size, + /// The base font size. + pub base_font_size: Size, + /// The font scale to apply on the base font size. + pub font_scale: f32, /// The word spacing (as a multiple of the font size). pub word_spacing: f32, /// The line spacing (as a multiple of the font size). @@ -31,6 +34,11 @@ pub struct TextStyle { } impl TextStyle { + /// The scaled font size. + pub fn font_size(&self) -> Size { + self.base_font_size * self.font_scale + } + /// Toggle a class. /// /// If the class was one of _italic_ or _bold_, then: @@ -73,7 +81,8 @@ impl Default for TextStyle { TextStyle { classes: vec![Regular], fallback: vec![Serif], - font_size: Size::pt(11.0), + base_font_size: Size::pt(11.0), + font_scale: 1.0, word_spacing: 0.25, line_spacing: 1.2, paragraph_spacing: 1.5, @@ -109,3 +118,61 @@ impl Default for PageStyle { } } } + +/// The size of a page with the given name. +pub fn parse_paper_name(paper: &str) -> ParseResult { + Ok(match paper { + "A0" | "a0" => PAPER_A0, + "A1" | "a1" => PAPER_A1, + "A2" | "a2" => PAPER_A2, + "A3" | "a3" => PAPER_A3, + "A4" | "a4" => PAPER_A4, + "A5" | "a5" => PAPER_A5, + "A6" | "a6" => PAPER_A6, + "A7" | "a7" => PAPER_A7, + "A8" | "a8" => PAPER_A8, + "A9" | "a9" => PAPER_A9, + "A10" | "a10" => PAPER_A10, + "A11" | "a11" => PAPER_A11, + "Letter" | "letter" => PAPER_LETTER, + "Legal" | "legal" => PAPER_LEGAL, + "Tabloid" | "tabloid" => PAPER_TABLOID, + "Ledger" | "ledger" => PAPER_LEDGER, + "Junior-Legal" | "junior-legal" => PAPER_JUNIOR_LEGAL, + "Half-Letter" | "half-letter" => PAPER_HALF_LETTER, + "Government-Letter" | "government-letter" => PAPER_GOVERNMENT_LETTER, + + _ => error!("unknown paper size: `{}`", paper), + }) +} + +macro_rules! paper { + ($var:ident: $width:expr, $height: expr) => { + /// The size of the paper that's in the name. + pub const $var: Size2D = Size2D { + x: Size { points: 2.83465 * $width }, + y: Size { points: 2.83465 * $height }, + }; + }; +} + +// Common paper sizes in mm. +paper!(PAPER_A0: 841.0, 1189.0); +paper!(PAPER_A1: 594.0, 841.0); +paper!(PAPER_A2: 420.0, 594.0); +paper!(PAPER_A3: 297.0, 420.0); +paper!(PAPER_A4: 210.0, 297.0); +paper!(PAPER_A5: 148.0, 210.0); +paper!(PAPER_A6: 105.0, 148.0); +paper!(PAPER_A7: 74.0, 105.0); +paper!(PAPER_A8: 52.0, 74.0); +paper!(PAPER_A9: 37.0, 52.0); +paper!(PAPER_A10: 26.0, 37.0); +paper!(PAPER_A11: 18.0, 26.0); +paper!(PAPER_LETTER: 216.0, 279.0); +paper!(PAPER_LEGAL: 216.0, 356.0); +paper!(PAPER_TABLOID: 279.0, 432.0); +paper!(PAPER_LEDGER: 432.0, 279.0); +paper!(PAPER_JUNIOR_LEGAL: 127.0, 203.0); +paper!(PAPER_HALF_LETTER: 140.0, 216.0); +paper!(PAPER_GOVERNMENT_LETTER: 203.0, 267.0); diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index d887d8981..41a8728bb 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -250,7 +250,16 @@ impl<'s> Parser<'s> { } else if let Ok(size) = text.parse::() { Expression::Size(size) } else { - Expression::Ident(Ident::new(text.to_string())?) + // This loop does not actually loop, but is used for breaking. + loop { + if text.ends_with('%') { + if let Ok(percent) = text[..text.len() - 1].parse::() { + break Expression::Num(percent / 100.0); + } + } + + break Expression::Ident(Ident::new(text.to_string())?); + } } } _ => error!("expected expression"), diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index cca8bee34..ab6bc3154 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -1,3 +1,5 @@ +//! Tokenization of source code. + use std::str::CharIndices; use smallvec::SmallVec; diff --git a/tests/layouts/stack.typ b/tests/layouts/stack.typ index 934480ca9..cbca41dcf 100644 --- a/tests/layouts/stack.typ +++ b/tests/layouts/stack.typ @@ -6,76 +6,66 @@ [box][ [align: center] [box: ps=3cm, ss=1cm] - [direction: ttb, ltr] + [direction: p=ttb, s=ltr] [box: ps=3cm, ss=1cm] [box: ps=1cm, ss=1cm] [box: ps=2cm, ss=1cm] [box: ps=1cm, ss=1cm] ] ] -[page.break] // Test 2 [box: w=1, h=1, debug=false][ - [align: secondary=top] Top - [align: secondary=center] Center - [align: secondary=bottom] Bottom - [direction: ttb, ltr] - [align: secondary=origin, primary=bottom] + [align: s=top] Top + [align: s=center] Center + [align: s=bottom] Bottom + [direction: p=ttb, s=ltr] + [align: p=bottom, s=origin] [box: w=1cm, h=1cm] ] -[page.break] // Test 3 [box: w=1, h=1, debug=false][ [align: center][ - Somelongspacelessword! + Some-long-spaceless-word! [align: left] Some [align: right] word! ] ] -[page.break] // Test 4 [box: w=1, h=1, debug=false][ - [direction: ltr, ttb] + [direction: p=ltr, s=ttb] [align: center] - [align: secondary=origin] - [box: ps=1cm, ss=1cm] - [align: secondary=center] - [box: ps=3cm, ss=1cm] - [box: ps=4cm, ss=0.5cm] - [align: secondary=end] - [box: ps=2cm, ss=1cm] + [align: s=origin] [box: ps=1cm, ss=1cm] + [align: s=center] [box: ps=3cm, ss=1cm] [box: ps=4cm, ss=0.5cm] + [align: s=end] [box: ps=2cm, ss=1cm] ] -[page.break] // Test 5 [box: w=1, h=1, debug=false][ - [direction: primary=btt, secondary=ltr] - [align: primary=center, secondary=left] + [direction: p=btt, s=ltr] + [align: p=center, s=left] [box: h=2cm, w=1cm] - [direction: rtl, btt] + [direction: p=rtl, s=btt] [align: center] - [align: vertical=origin] ORIGIN - [align: vertical=center] CENTER - [align: vertical=end] END + [align: v=origin] ORIGIN + [align: v=center] CENTER + [align: v=end] END ] -[page.break] // Test 6 [box: w=1, h=1, debug=false][ [box: w=4cm, h=1cm] - [align: primary=right, secondary=center] CENTER + [align: p=right, s=center] CENTER - [direction: btt, rtl] - [align: primary=center, secondary=origin] + [direction: p=btt, s=rtl] + [align: p=center, s=origin] [box: w=0.5cm, h=0.5cm] [box: w=0.5cm, h=1cm] [box: w=0.5cm, h=0.5cm] - [align: primary=origin, secondary=end] - END + [align: p=origin, s=end] END ]