More consistent library code and functions 🎄
This commit is contained in:
parent
971ff3a2dc
commit
665b4d2aca
1
build.rs
1
build.rs
@ -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.
|
||||
|
@ -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)*);
|
||||
};
|
||||
|
||||
|
@ -107,6 +107,7 @@ pub enum Command<'a> {
|
||||
FinishRun,
|
||||
FinishSpace,
|
||||
BreakParagraph,
|
||||
BreakPage,
|
||||
|
||||
SetTextStyle(TextStyle),
|
||||
SetPageStyle(PageStyle),
|
||||
|
@ -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())
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
@ -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)],
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
@ -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(())
|
||||
}
|
||||
}
|
76
src/library/maps/alignment.rs
Normal file
76
src/library/maps/alignment.rs
Normal 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
122
src/library/maps/axis.rs
Normal 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
103
src/library/maps/mod.rs
Normal 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,
|
||||
);
|
92
src/library/maps/padding.rs
Normal file
92
src/library/maps/padding.rs
Normal 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(())
|
||||
}
|
||||
}
|
@ -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() {
|
||||
} 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")
|
||||
error!("expected axis and spacing")
|
||||
}
|
||||
};
|
||||
|
||||
parse!(forbidden: body);
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
31
src/size.rs
31
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,
|
||||
|
73
src/style.rs
73
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<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);
|
||||
|
@ -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"),
|
||||
|
@ -1,3 +1,5 @@
|
||||
//! Tokenization of source code.
|
||||
|
||||
use std::str::CharIndices;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
|
@ -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
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user