summaryrefslogtreecommitdiff
path: root/src/library
diff options
context:
space:
mode:
Diffstat (limited to 'src/library')
-rw-r--r--src/library/align.rs52
-rw-r--r--src/library/boxed.rs36
-rw-r--r--src/library/direction.rs38
-rw-r--r--src/library/keys.rs186
-rw-r--r--src/library/maps.rs188
-rw-r--r--src/library/maps/alignment.rs76
-rw-r--r--src/library/maps/axis.rs122
-rw-r--r--src/library/maps/mod.rs103
-rw-r--r--src/library/maps/padding.rs92
-rw-r--r--src/library/mod.rs99
10 files changed, 495 insertions, 497 deletions
diff --git a/src/library/align.rs b/src/library/align.rs
index e6952dc2..524ada61 100644
--- a/src/library/align.rs
+++ b/src/library/align.rs
@@ -1,64 +1,34 @@
use crate::func::prelude::*;
-use super::maps::ConsistentMap;
-use super::keys::{AxisKey, AlignmentKey};
+use super::maps::{PosAxisMap, AlignmentKey};
function! {
/// `align`: Aligns content along the layouting axes.
#[derive(Debug, PartialEq)]
pub struct Align {
body: Option<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),
-}
diff --git a/src/library/boxed.rs b/src/library/boxed.rs
index 2d0d6e6b..3e9a4c7a 100644
--- a/src/library/boxed.rs
+++ b/src/library/boxed.rs
@@ -1,3 +1,5 @@
+use smallvec::smallvec;
+
use crate::func::prelude::*;
use super::maps::ExtentMap;
@@ -19,18 +21,34 @@ function! {
}
layout(self, mut ctx) {
+ ctx.repeat = false;
ctx.debug = self.debug;
- let space = &mut ctx.spaces[0];
- self.map.apply_with(ctx.axes, |axis, p| {
- let entity = match axis {
- Horizontal => { space.expansion.horizontal = true; &mut space.dimensions.x },
- Vertical => { space.expansion.vertical = true; &mut space.dimensions.y },
- };
+ let map = self.map.dedup(ctx.axes)?;
+
+ // 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;
+
+ 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;
+ }
+ }
- *entity = p.concretize(*entity)
- })?;
+ ctx.spaces = smallvec![space];
+
+ match layout(&self.body, ctx) {
+ Ok(layouts) => return Ok(vec![AddMultiple(layouts)]),
+ Err(err) => error = Some(err),
+ }
+ }
- vec![AddMultiple(layout_tree(&self.body, ctx)?)]
+ return Err(error.expect("expected at least one space"));
}
}
diff --git a/src/library/direction.rs b/src/library/direction.rs
index 59c0d1fd..7bb11c99 100644
--- a/src/library/direction.rs
+++ b/src/library/direction.rs
@@ -1,59 +1,41 @@
use crate::func::prelude::*;
-use super::maps::ConsistentMap;
-use super::keys::AxisKey;
+use super::maps::PosAxisMap;
function! {
/// `direction`: Sets the directions of the layouting axes.
#[derive(Debug, PartialEq)]
pub struct DirectionChange {
body: Option<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)],
}
}
diff --git a/src/library/keys.rs b/src/library/keys.rs
deleted file mode 100644
index bee45638..00000000
--- a/src/library/keys.rs
+++ /dev/null
@@ -1,186 +0,0 @@
-//! Keys for the consistent maps.
-
-use super::*;
-
-macro_rules! kind {
- ($type:ty, $name:expr, $($patterns:tt)*) => {
- impl $type {
- /// Parse this key from an identifier.
- pub fn from_ident(ident: &Spanned<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,
-);
diff --git a/src/library/maps.rs b/src/library/maps.rs
deleted file mode 100644
index 13b4dc5d..00000000
--- a/src/library/maps.rs
+++ /dev/null
@@ -1,188 +0,0 @@
-//! Deduplicating maps for argument parsing.
-
-use std::collections::HashMap;
-use std::hash::Hash;
-
-use super::*;
-
-/// A deduplicating map type useful for storing possibly redundant arguments.
-#[derive(Debug, Clone, PartialEq)]
-pub struct ConsistentMap<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(())
- }
-}
diff --git a/src/library/maps/alignment.rs b/src/library/maps/alignment.rs
new file mode 100644
index 00000000..486c8b2e
--- /dev/null
+++ b/src/library/maps/alignment.rs
@@ -0,0 +1,76 @@
+use super::*;
+use AlignmentKey::*;
+
+/// An argument key which describes a target alignment.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum AlignmentKey {
+ Align(Alignment),
+ Left,
+ Top,
+ Right,
+ Bottom,
+}
+
+impl AlignmentKey {
+ /// The generic axis this alignment key corresponds to in the given system
+ /// of layouting axes. `None` if the alignment is generic.
+ pub fn axis(self, axes: LayoutAxes) -> Option<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,
+);
diff --git a/src/library/maps/axis.rs b/src/library/maps/axis.rs
new file mode 100644
index 00000000..a177a324
--- /dev/null
+++ b/src/library/maps/axis.rs
@@ -0,0 +1,122 @@
+use super::*;
+use AxisKey::*;
+
+/// An argument key which identifies a layouting axis.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum AxisKey {
+ Generic(GenericAxis),
+ Specific(SpecificAxis),
+}
+
+impl AxisKey {
+ /// The generic version of this axis key in the given system of axes.
+ pub fn to_generic(self, axes: LayoutAxes) -> GenericAxis {
+ match self {
+ Generic(axis) => axis,
+ Specific(axis) => axis.to_generic(axes),
+ }
+ }
+
+ /// The specific version of this axis key in the given system of axes.
+ pub fn to_specific(self, axes: LayoutAxes) -> SpecificAxis {
+ match self {
+ Generic(axis) => axis.to_specific(axes),
+ Specific(axis) => axis,
+ }
+ }
+}
+
+key!(AxisKey, "axis",
+ "horizontal" | "h" => Specific(Horizontal),
+ "vertical" | "v" => Specific(Vertical),
+ "primary" | "p" => Generic(Primary),
+ "secondary" | "s" => Generic(Secondary),
+);
+
+/// A map for storing extents along axes.
+#[derive(Debug, Clone, PartialEq)]
+pub struct ExtentMap<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))
+ })
+ }
+}
diff --git a/src/library/maps/mod.rs b/src/library/maps/mod.rs
new file mode 100644
index 00000000..284c7181
--- /dev/null
+++ b/src/library/maps/mod.rs
@@ -0,0 +1,103 @@
+//! Deduplicating maps and keys for argument parsing.
+
+use std::collections::HashMap;
+use std::hash::Hash;
+
+use crate::func::prelude::*;
+
+macro_rules! key {
+ ($type:ty, $name:expr, $($patterns:tt)*) => {
+ impl $type {
+ /// Parse this key from an identifier.
+ pub fn from_ident(ident: &Spanned<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,
+);
diff --git a/src/library/maps/padding.rs b/src/library/maps/padding.rs
new file mode 100644
index 00000000..37f2ba4a
--- /dev/null
+++ b/src/library/maps/padding.rs
@@ -0,0 +1,92 @@
+use super::*;
+use AxisKey::*;
+use AlignmentKey::*;
+use PaddingKey::*;
+
+/// An argument key which identifies a margin or padding target.
+///
+/// A is the used axis type.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum PaddingKey<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(())
+ }
+}
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 9a49896b..1e5e406c 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -3,11 +3,10 @@
use toddle::query::FontClass;
use crate::func::prelude::*;
-use self::keys::*;
-use self::maps::*;
+use crate::style::parse_paper_name;
+use self::maps::{ExtentMap, PaddingMap, AxisKey};
pub mod maps;
-pub mod keys;
pub_use_mod!(align);
pub_use_mod!(boxed);
@@ -20,29 +19,23 @@ pub fn std() -> Scope {
std.add::<Align>("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 {
- error!("expected axis and expression")
- }
- };
+ } else if let Some(arg) = args.get_key_next() {
+ let axis = AxisKey::from_ident(&arg.v.key)
+ .map_err(|_| error!(@unexpected_argument))?;
- parse!(forbidden: body);
- spacing
+ let spacing = FSize::from_expr(arg.v.value)?;
+ Spacing { axis, spacing }
+ } else {
+ 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)
}
}