More consistent library code and functions 🎄

This commit is contained in:
Laurenz 2019-12-13 23:59:01 +01:00
parent 971ff3a2dc
commit 665b4d2aca
24 changed files with 700 additions and 583 deletions

View File

@ -7,6 +7,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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.

View File

@ -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)*);
};

View File

@ -107,6 +107,7 @@ pub enum Command<'a> {
FinishRun,
FinishSpace,
BreakParagraph,
BreakPage,
SetTextStyle(TextStyle),
SetPageStyle(PageStyle),

View File

@ -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<MultiLayout> {
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())
}

View File

@ -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,
}
}
}

View File

@ -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<MultiLayout> {
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.

View File

@ -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));
}

View File

@ -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<MultiLayout> {
pub fn layout(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiLayout> {
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<MultiLayout> {
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()
}

View File

@ -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<MultiLayout> {
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,

View File

@ -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<SyntaxTree>,
map: ConsistentMap<Key, AlignmentKey>,
map: PosAxisMap<AlignmentKey>,
}
parse(args, body, ctx) {
let mut map = ConsistentMap::new();
map.add_opt(Key::First, args.get_pos_opt::<AlignmentKey>()?)?;
map.add_opt(Key::Second, args.get_pos_opt::<AlignmentKey>()?)?;
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),
}

View File

@ -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"));
}
}

View File

@ -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<SyntaxTree>,
map: ConsistentMap<AxisKey, Direction>,
map: PosAxisMap<Direction>,
}
parse(args, body, ctx) {
let mut map = ConsistentMap::new();
map.add_opt(AxisKey::Primary, args.get_pos_opt::<Direction>()?)?;
map.add_opt(AxisKey::Secondary, args.get_pos_opt::<Direction>()?)?;
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)],
}
}

View File

@ -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<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 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<Alignment> {
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<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!(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<AxisKey>, "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,
);

View File

@ -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<K, V> where K: Hash + Eq {
map: HashMap<K, V>,
}
impl<K, V> ConsistentMap<K, V> where K: Hash + Eq {
pub fn new() -> ConsistentMap<K, V> {
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<V>) -> 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<F>(&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<F, K2, V2>(&self, f: F) -> LayoutResult<ConsistentMap<K2, V2>>
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<E: ExpressionKind + Copy>(ConsistentMap<AxisKey, E>);
impl<E: ExpressionKind + Copy> ExtentMap<E> {
/// 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<ExtentMap<E>> {
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<F>(
&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<F>(&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<ConsistentMap<SpecificAxis, E>> {
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<PaddingKey<AxisKey>, 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<PaddingMap> {
let mut map = ConsistentMap::new();
map.add_opt(PaddingKey::All, args.get_pos_opt::<Size>()?)?;
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(())
}
}

View File

@ -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<GenericAxis> {
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<Alignment> {
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,
);

122
src/library/maps/axis.rs Normal file
View File

@ -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<E: ExpressionKind + Copy>(ConsistentMap<AxisKey, E>);
impl<E: ExpressionKind + Copy> ExtentMap<E> {
/// 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<ExtentMap<E>> {
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<ConsistentMap<SpecificAxis, E>> {
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<E: ExpressionKind + Copy>(ConsistentMap<PosAxisKey, E>);
impl<E: ExpressionKind + Copy> PosAxisMap<E> {
/// Parse a positional axis map from the function args.
pub fn new(args: &mut FuncArgs) -> ParseResult<PosAxisMap<E>> {
let mut map = ConsistentMap::new();
map.add_opt(PosAxisKey::First, args.get_pos_opt::<E>()?)?;
map.add_opt(PosAxisKey::Second, args.get_pos_opt::<E>()?)?;
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<F>(&self, axes: LayoutAxes, f: F) -> LayoutResult<ConsistentMap<GenericAxis, E>>
where F: Fn(E) -> Option<GenericAxis> {
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))
})
}
}

103
src/library/maps/mod.rs Normal file
View File

@ -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<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);
}
}
}
};
}
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<K, V> where K: Hash + Eq {
map: HashMap<K, V>,
}
impl<K, V> ConsistentMap<K, V> where K: Hash + Eq {
pub fn new() -> ConsistentMap<K, V> {
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<V>) -> 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<F>(&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<F, K2, V2>(&self, f: F) -> LayoutResult<ConsistentMap<K2, V2>>
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,
);

View File

@ -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<A> {
/// 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<AxisKey>, "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<PaddingKey<AxisKey>, Size>);
impl PaddingMap {
/// Parse a padding map from the function args.
pub fn new(args: &mut FuncArgs) -> ParseResult<PaddingMap> {
let mut map = ConsistentMap::new();
map.add_opt(PaddingKey::All, args.get_pos_opt::<Size>()?)?;
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(())
}
}

View File

@ -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>("align");
std.add::<Boxed>("box");
std.add::<DirectionChange>("direction");
std.add::<PageSize>("page.size");
std.add::<PageMargins>("page.margins");
std.add::<LineBreak>("n");
std.add::<LineBreak>("line.break");
std.add::<ParBreak>("par.break");
std.add::<PageBreak>("page.break");
std.add::<FontSize>("font.size");
std.add::<PageSize>("page.size");
std.add::<PageMargins>("page.margins");
std.add_with_metadata::<Spacing>("spacing", None);
std.add_with_metadata::<Spacing>("h", Some(Horizontal));
std.add_with_metadata::<Spacing>("v", Some(Vertical));
for (name, key) in &[("h", AxisKey::Horizontal), ("v", AxisKey::Vertical)] {
std.add_with_metadata::<Spacing>(name, Some(*key));
}
std.add_with_metadata::<StyleChange>("bold", FontClass::Bold);
std.add_with_metadata::<StyleChange>("italic", FontClass::Italic);
std.add_with_metadata::<StyleChange>("mono", FontClass::Monospace);
for (name, class) in &[
("bold", FontClass::Bold),
("italic", FontClass::Italic),
("mono", FontClass::Monospace),
] {
std.add_with_metadata::<StyleChange>(name, class.clone());
}
std.add::<FontSize>("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<Size>,
pub enum PageSize {
Map(ExtentMap<PSize>),
Size(Size2D),
}
parse(args, body) {
parse!(forbidden: body);
PageSize {
map: ExtentMap::new(&mut args, true)?,
if let Some(name) = args.get_pos_opt::<Ident>()? {
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<AxisKey>;
type Meta = Option<SpecificAxis>;
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::<Spanned<Expression>>()?)?,
}
} 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<SyntaxTree>,
size: Size,
size: ScaleSize,
}
parse(args, body, ctx) {
FontSize {
body: parse!(optional: body, ctx),
size: args.get_pos::<Size>()?,
size: args.get_pos::<ScaleSize>()?,
}
}
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)
}
}

View File

@ -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,

View File

@ -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<FontClass>,
/// 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<Size2D> {
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);

View File

@ -250,7 +250,16 @@ impl<'s> Parser<'s> {
} else if let Ok(size) = text.parse::<Size>() {
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::<f64>() {
break Expression::Num(percent / 100.0);
}
}
break Expression::Ident(Ident::new(text.to_string())?);
}
}
}
_ => error!("expected expression"),

View File

@ -1,3 +1,5 @@
//! Tokenization of source code.
use std::str::CharIndices;
use smallvec::SmallVec;

View File

@ -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
]