summaryrefslogtreecommitdiff
path: root/src/library
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-01-24 12:44:04 +0100
committerLaurenz <laurmaedje@gmail.com>2020-01-24 12:44:04 +0100
commit03fddaf3aea778057aedd74dbcb27bae971ec22f (patch)
tree37e3136e29e0e5d69ec8f56e43d156739d2931ab /src/library
parent78da2bdd5d77d1b8572e5e9da119bfa68127a3fa (diff)
Non-fatal argument parsing 🌋
Diffstat (limited to 'src/library')
-rw-r--r--src/library/align.rs35
-rw-r--r--src/library/boxed.rs57
-rw-r--r--src/library/direction.rs43
-rw-r--r--src/library/font.rs104
-rw-r--r--src/library/layout.rs135
-rw-r--r--src/library/maps/alignment.rs77
-rw-r--r--src/library/maps/axis.rs130
-rw-r--r--src/library/maps/mod.rs128
-rw-r--r--src/library/maps/padding.rs95
-rw-r--r--src/library/mod.rs374
-rw-r--r--src/library/page.rs64
-rw-r--r--src/library/spacing.rs103
12 files changed, 449 insertions, 896 deletions
diff --git a/src/library/align.rs b/src/library/align.rs
deleted file mode 100644
index ca2c787b..00000000
--- a/src/library/align.rs
+++ /dev/null
@@ -1,35 +0,0 @@
-use crate::func::prelude::*;
-use super::maps::{PosAxisMap, AlignmentKey};
-
-
-function! {
- /// `align`: Aligns content along the layouting axes.
- #[derive(Debug, PartialEq)]
- pub struct AlignFunc {
- body: Option<SyntaxTree>,
- map: PosAxisMap<AlignmentKey>,
- }
-
- parse(header, body, ctx) {
- AlignFunc {
- body: parse!(optional: body, ctx),
- map: PosAxisMap::new(&mut header.args)?,
- }
- }
-
- layout(self, mut ctx) {
- ctx.base = ctx.spaces[0].dimensions;
-
- 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(&body, ctx).await?)],
- None => vec![SetAlignment(ctx.alignment)],
- }
- }
-}
diff --git a/src/library/boxed.rs b/src/library/boxed.rs
deleted file mode 100644
index af236da4..00000000
--- a/src/library/boxed.rs
+++ /dev/null
@@ -1,57 +0,0 @@
-use smallvec::smallvec;
-
-use crate::func::prelude::*;
-use super::maps::ExtentMap;
-
-
-function! {
- /// `box`: Layouts content into a box.
- #[derive(Debug, PartialEq)]
- pub struct BoxFunc {
- body: SyntaxTree,
- map: ExtentMap<PSize>,
- debug: Option<bool>,
- }
-
- parse(header, body, ctx) {
- BoxFunc {
- body: parse!(optional: body, ctx).unwrap_or(SyntaxTree::new()),
- map: ExtentMap::new(&mut header.args, false)?,
- debug: header.args.get_key_opt::<bool>("debug")?,
- }
- }
-
- layout(self, mut ctx) {
- ctx.repeat = false;
-
- if let Some(debug) = self.debug {
- ctx.debug = debug;
- }
-
- let map = self.map.dedup(ctx.axes)?;
-
- // Try to layout this box in all spaces until it fits into some space.
- let mut error = None;
- for &(mut space) in &ctx.spaces {
- let mut ctx = ctx.clone();
-
- for &axis in &[Horizontal, Vertical] {
- if let Some(psize) = map.get(axis) {
- let size = psize.scaled(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).await {
- Ok(layouts) => return Ok(vec![AddMultiple(layouts)]),
- Err(err) => error = Some(err),
- }
- }
-
- return Err(error.expect("expected at least one space"));
- }
-}
diff --git a/src/library/direction.rs b/src/library/direction.rs
deleted file mode 100644
index b7a6e212..00000000
--- a/src/library/direction.rs
+++ /dev/null
@@ -1,43 +0,0 @@
-use crate::func::prelude::*;
-use super::maps::PosAxisMap;
-
-
-function! {
- /// `direction`: Sets the directions of the layouting axes.
- #[derive(Debug, PartialEq)]
- pub struct DirectionFunc {
- body: Option<SyntaxTree>,
- map: PosAxisMap<Direction>,
- }
-
- parse(header, body, ctx) {
- DirectionFunc {
- body: parse!(optional: body, ctx),
- map: PosAxisMap::new(&mut header.args)?,
- }
- }
-
- layout(self, mut ctx) {
- ctx.base = ctx.spaces[0].dimensions;
-
- let map = self.map.dedup(ctx.axes, |direction| {
- Some(direction.axis().to_generic(ctx.axes))
- })?;
-
- 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!(
- "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(&body, ctx).await?)],
- None => vec![Command::SetAxes(ctx.axes)],
- }
- }
-}
diff --git a/src/library/font.rs b/src/library/font.rs
new file mode 100644
index 00000000..b4a6218a
--- /dev/null
+++ b/src/library/font.rs
@@ -0,0 +1,104 @@
+use toddle::query::{FontWeight, FontStyle};
+use super::*;
+
+
+function! {
+ /// `font.family`: Set the font family.
+ #[derive(Debug, Clone, PartialEq)]
+ pub struct FontFamilyFunc {
+ body: Option<SyntaxModel>,
+ list: Vec<String>,
+ }
+
+ parse(header, body, ctx, errors, decos) {
+ FontFamilyFunc {
+ body: body!(opt: body, ctx, errors, decos),
+ list: header.args.pos.get_all::<StringLike>(errors).collect(),
+ }
+ }
+
+ layout(self, ctx, errors) {
+ styled(&self.body, ctx, Some(&self.list),
+ |s, l| s.fallback.list = l.clone())
+ }
+}
+
+function! {
+ /// `font.style`: Set the font style (normal / italic).
+ #[derive(Debug, Clone, PartialEq)]
+ pub struct FontStyleFunc {
+ body: Option<SyntaxModel>,
+ style: Option<FontStyle>,
+ }
+
+ parse(header, body, ctx, errors, decos) {
+ FontStyleFunc {
+ body: body!(opt: body, ctx, errors, decos),
+ style: header.args.pos.get::<FontStyle>(errors)
+ .or_missing(errors, header.name.span, "style"),
+ }
+ }
+
+ layout(self, ctx, errors) {
+ styled(&self.body, ctx, self.style, |t, s| t.variant.style = s)
+ }
+}
+
+function! {
+ /// `font.weight`: Set text with a given weight.
+ #[derive(Debug, Clone, PartialEq)]
+ pub struct FontWeightFunc {
+ body: Option<SyntaxModel>,
+ weight: Option<FontWeight>,
+ }
+
+ parse(header, body, ctx, errors, decos) {
+ let body = body!(opt: body, ctx, errors, decos);
+ let weight = header.args.pos.get::<Spanned<FontWeight>>(errors)
+ .map(|Spanned { v: (weight, is_clamped), span }| {
+ if is_clamped {
+ errors.push(err!(@Warning: span;
+ "weight should be between \
+ 100 and 900, clamped to {}", weight.0));
+ }
+
+ weight
+ })
+ .or_missing(errors, header.name.span, "weight");
+
+ FontWeightFunc { body, weight }
+ }
+
+ layout(self, ctx, errors) {
+ styled(&self.body, ctx, self.weight, |t, w| t.variant.weight = w)
+ }
+}
+
+function! {
+ /// `font.size`: Sets the font size.
+ #[derive(Debug, Clone, PartialEq)]
+ pub struct FontSizeFunc {
+ body: Option<SyntaxModel>,
+ size: Option<ScaleSize>,
+ }
+
+ parse(header, body, ctx, errors, decos) {
+ FontSizeFunc {
+ body: body!(opt: body, ctx, errors, decos),
+ size: header.args.pos.get::<FSize>(errors)
+ .or_missing(errors, header.name.span, "size")
+ }
+ }
+
+ layout(self, ctx, errors) {
+ styled(&self.body, ctx, self.size, |t, s| {
+ match s {
+ ScaleSize::Absolute(size) => {
+ t.base_font_size = size;
+ t.font_scale = 1.0;
+ }
+ ScaleSize::Scaled(scale) => t.font_scale = scale,
+ }
+ })
+ }
+}
diff --git a/src/library/layout.rs b/src/library/layout.rs
new file mode 100644
index 00000000..fb8633b1
--- /dev/null
+++ b/src/library/layout.rs
@@ -0,0 +1,135 @@
+use smallvec::smallvec;
+use super::*;
+
+
+function! {
+ /// `align`: Aligns content along the layouting axes.
+ #[derive(Debug, Clone, PartialEq)]
+ pub struct AlignFunc {
+ body: Option<SyntaxModel>,
+ map: PosAxisMap<AlignmentValue>,
+ }
+
+ parse(header, body, ctx, errors, decos) {
+ AlignFunc {
+ body: body!(opt: body, ctx, errors, decos),
+ map: PosAxisMap::parse::<AxisKey, AlignmentValue>(errors, &mut header.args),
+ }
+ }
+
+ layout(self, ctx, errors) {
+ ctx.base = ctx.spaces[0].dimensions;
+
+ let map = self.map.dedup(errors, ctx.axes, |alignment| alignment.axis(ctx.axes));
+ for &axis in &[Primary, Secondary] {
+ if let Some(Spanned { v: alignment, span }) = map.get_spanned(axis) {
+ if let Some(generic) = alignment.to_generic(ctx.axes, axis) {
+ *ctx.alignment.get_mut(axis) = generic;
+ } else {
+ errors.push(err!(span;
+ "invalid alignment `{}` for {} axis", alignment, axis));
+ }
+ }
+ }
+
+ match &self.body {
+ Some(body) => {
+ let layouted = layout(body, ctx).await;
+ errors.extend(layouted.errors);
+ vec![AddMultiple(layouted.output)]
+ }
+ None => vec![SetAlignment(ctx.alignment)],
+ }
+ }
+}
+
+function! {
+ /// `direction`: Sets the directions of the layouting axes.
+ #[derive(Debug, Clone, PartialEq)]
+ pub struct DirectionFunc {
+ name_span: Span,
+ body: Option<SyntaxModel>,
+ map: PosAxisMap<Direction>,
+ }
+
+ parse(header, body, ctx, errors, decos) {
+ DirectionFunc {
+ name_span: header.name.span,
+ body: body!(opt: body, ctx, errors, decos),
+ map: PosAxisMap::parse::<AxisKey, Direction>(errors, &mut header.args),
+ }
+ }
+
+ layout(self, ctx, errors) {
+ ctx.base = ctx.spaces[0].dimensions;
+
+ let map = self.map.dedup(errors, ctx.axes, |direction| {
+ Some(direction.axis().to_generic(ctx.axes))
+ });
+
+ let mut axes = ctx.axes;
+
+ map.with(Primary, |&dir| axes.primary = dir);
+ map.with(Secondary, |&dir| axes.secondary = dir);
+
+ if axes.primary.axis() == axes.secondary.axis() {
+ errors.push(err!(self.name_span;
+ "invalid aligned primary and secondary axes: `{}`, `{}`",
+ ctx.axes.primary, ctx.axes.secondary));
+ } else {
+ ctx.axes = axes;
+ }
+
+ match &self.body {
+ Some(body) => {
+ let layouted = layout(body, ctx).await;
+ errors.extend(layouted.errors);
+ vec![AddMultiple(layouted.output)]
+ }
+ None => vec![SetAxes(ctx.axes)],
+ }
+ }
+}
+
+function! {
+ /// `box`: Layouts content into a box.
+ #[derive(Debug, Clone, PartialEq)]
+ pub struct BoxFunc {
+ body: SyntaxModel,
+ extents: AxisMap<PSize>,
+ debug: Option<bool>,
+ }
+
+ parse(header, body, ctx, errors, decos) {
+ BoxFunc {
+ body: body!(opt: body, ctx, errors, decos).unwrap_or(SyntaxModel::new()),
+ extents: AxisMap::parse::<ExtentKey, PSize>(errors, &mut header.args.key),
+ debug: header.args.key.get::<bool>(errors, "debug"),
+ }
+ }
+
+ layout(self, ctx, errors) {
+ ctx.repeat = false;
+ ctx.spaces.truncate(1);
+
+ if let Some(debug) = self.debug {
+ ctx.debug = debug;
+ }
+
+ let map = self.extents.dedup(errors, ctx.axes);
+ for &axis in &[Horizontal, Vertical] {
+ if let Some(psize) = map.get(axis) {
+ let size = psize.scaled(ctx.base.get(axis));
+ *ctx.base.get_mut(axis) = size;
+ *ctx.spaces[0].dimensions.get_mut(axis) = size;
+ *ctx.spaces[0].expansion.get_mut(axis) = true;
+ }
+ }
+
+ let layouted = layout(&self.body, ctx).await;
+ let layout = layouted.output.into_iter().next().unwrap();
+ errors.extend(layouted.errors);
+
+ vec![Add(layout)]
+ }
+}
diff --git a/src/library/maps/alignment.rs b/src/library/maps/alignment.rs
deleted file mode 100644
index 8654e280..00000000
--- a/src/library/maps/alignment.rs
+++ /dev/null
@@ -1,77 +0,0 @@
-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
deleted file mode 100644
index 3c3a8c11..00000000
--- a/src/library/maps/axis.rs
+++ /dev/null
@@ -1,130 +0,0 @@
-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),
-);
-
-key!(Direction, "direction",
- "left-to-right" | "ltr" => LeftToRight,
- "right-to-left" | "rtl" => RightToLeft,
- "top-to-bottom" | "ttb" => TopToBottom,
- "bottom-to-top" | "btt" => BottomToTop,
-);
-
-/// 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 `all` is true other arguments will create an error, otherwise
- /// they are left intact.
- pub fn new(args: &mut FuncArgs, all: bool) -> ParseResult<ExtentMap<E>> {
- let mut map = ConsistentMap::new();
-
- for arg in args.iter_keys() {
- let key = match arg.key.v.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 all {
- error!("expected dimension")
- } else {
- args.add_key_pair(arg);
- continue;
- }
- };
-
- let e = E::from_expr(arg.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.iter_keys() {
- let axis = AxisKey::from_ident(&arg.key)?;
- let value = E::from_expr(arg.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
deleted file mode 100644
index 3538def7..00000000
--- a/src/library/maps/mod.rs
+++ /dev/null
@@ -1,128 +0,0 @@
-//! 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.as_str() {
- $($patterns)*
- _ => error!("expected {}", <Self as ExpressionKind>::NAME),
- })
- }
- }
-
- impl ExpressionKind for $type {
- const NAME: &'static str = $name;
-
- fn from_expr(expr: Spanned<Expr>) -> ParseResult<Self> {
- if let Expr::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);
-
-
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum DefaultKey<T> {
- Some(T),
- None,
-}
-
-impl<T> Into<Option<T>> for DefaultKey<T> {
- fn into(self) -> Option<T> {
- match self {
- DefaultKey::Some(v) => Some(v),
- DefaultKey::None => None,
- }
- }
-}
-
-impl<T> ExpressionKind for DefaultKey<T> where T: ExpressionKind {
- const NAME: &'static str = T::NAME;
-
- fn from_expr(expr: Spanned<Expr>) -> ParseResult<DefaultKey<T>> {
- if let Expr::Ident(ident) = &expr.v {
- match ident.as_str() {
- "default" => return Ok(DefaultKey::None),
- _ => {},
- }
- }
-
- T::from_expr(expr).map(|v| DefaultKey::Some(v))
- }
-}
-
-/// 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()
- }
-}
diff --git a/src/library/maps/padding.rs b/src/library/maps/padding.rs
deleted file mode 100644
index e2d0ea09..00000000
--- a/src/library/maps/padding.rs
+++ /dev/null
@@ -1,95 +0,0 @@
-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>, Option<PSize>>);
-
-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::<DefaultKey<PSize>>()?.map(Into::into))?;
-
- for arg in args.iter_keys() {
- let key = PaddingKey::from_ident(&arg.key)?;
- let size = DefaultKey::<PSize>::from_expr(arg.value)?.into();
- map.add(key, size)?;
- }
-
- Ok(PaddingMap(map))
- }
-
- /// Apply the specified padding on the size box.
- pub fn apply(&self, axes: LayoutAxes, padding: &mut ValueBox<Option<PSize>>)
- -> 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 8d16ccaf..e706642f 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -1,34 +1,32 @@
//! The standard library.
-use toddle::query::{FontWeight, FontStyle};
-
use crate::func::prelude::*;
-use crate::style::{Paper, PaperClass};
-use self::maps::{ExtentMap, PaddingMap, AxisKey};
-
-pub mod maps;
-pub_use_mod!(align);
-pub_use_mod!(boxed);
-pub_use_mod!(direction);
+pub_use_mod!(font);
+pub_use_mod!(layout);
+pub_use_mod!(page);
+pub_use_mod!(spacing);
/// Create a scope with all standard functions.
pub fn std() -> Scope {
- let mut std = Scope::new();
+ let mut std = Scope::new::<ValFunc>();
+
+ // Basics
+ std.add::<ValFunc>("val");
// Font setup
std.add::<FontFamilyFunc>("font.family");
std.add::<FontStyleFunc>("font.style");
std.add::<FontWeightFunc>("font.weight");
std.add::<FontSizeFunc>("font.size");
+ std.add_with_meta::<ContentSpacingFunc>("word.spacing", ContentKind::Word);
// Layout
+ std.add_with_meta::<ContentSpacingFunc>("line.spacing", ContentKind::Line);
+ std.add_with_meta::<ContentSpacingFunc>("par.spacing", ContentKind::Paragraph);
std.add::<AlignFunc>("align");
std.add::<DirectionFunc>("direction");
- std.add_with_metadata::<ContentSpacingFunc>("par.spacing", ContentKind::Paragraph);
- std.add_with_metadata::<ContentSpacingFunc>("word.spacing", ContentKind::Word);
- std.add_with_metadata::<ContentSpacingFunc>("line.spacing", ContentKind::Line);
std.add::<BoxFunc>("box");
// Spacing
@@ -36,9 +34,9 @@ pub fn std() -> Scope {
std.add::<LineBreakFunc>("line.break");
std.add::<ParBreakFunc>("par.break");
std.add::<PageBreakFunc>("page.break");
- std.add_with_metadata::<SpacingFunc>("spacing", None);
- std.add_with_metadata::<SpacingFunc>("h", Some(Horizontal));
- std.add_with_metadata::<SpacingFunc>("v", Some(Vertical));
+ std.add_with_meta::<SpacingFunc>("spacing", None);
+ std.add_with_meta::<SpacingFunc>("h", Some(Horizontal));
+ std.add_with_meta::<SpacingFunc>("v", Some(Vertical));
// Page setup
std.add::<PageSizeFunc>("page.size");
@@ -47,331 +45,45 @@ pub fn std() -> Scope {
std
}
-// -------------------------------------------------------------------------- //
-// Font setup
-
function! {
- /// `font.family`: Set the font family.
- #[derive(Debug, PartialEq)]
- pub struct FontFamilyFunc {
- body: Option<SyntaxTree>,
- list: Vec<String>,
+ /// `val`: Layouts the body with no special effect.
+ #[derive(Debug, Clone, PartialEq)]
+ pub struct ValFunc {
+ body: Option<SyntaxModel>,
}
- parse(header, body, ctx) {
- FontFamilyFunc {
- body: parse!(optional: body, ctx),
- list: {
- header.args.iter_pos().map(|arg| match arg.v {
- Expr::Str(s) |
- Expr::Ident(Ident(s)) => Ok(s.to_lowercase()),
- _ => error!("expected identifier or string"),
- }).collect::<LayoutResult<Vec<_>>>()?
- }
- }
+ parse(header, body, ctx, errors, decos) {
+ ValFunc { body: body!(opt: body, ctx, errors, decos) }
}
- layout(self, ctx) {
- let mut style = ctx.style.text.clone();
- style.fallback.list = self.list.clone();
- styled(&self.body, &ctx, style)
- }
-}
-
-function! {
- /// `font.style`: Set the font style (normal / italic).
- #[derive(Debug, PartialEq)]
- pub struct FontStyleFunc {
- body: Option<SyntaxTree>,
- style: FontStyle,
- }
-
- parse(header, body, ctx) {
- FontStyleFunc {
- body: parse!(optional: body, ctx),
- style: {
- let s = header.args.get_pos::<String>()?;
- match FontStyle::from_str(&s) {
- Some(style) => style,
- None => error!("invalid font style: `{}`", s),
- }
- }
+ layout(self, ctx, errors) {
+ match &self.body {
+ Some(model) => vec![LayoutSyntaxModel(model)],
+ None => vec![],
}
}
-
- layout(self, ctx) {
- let mut style = ctx.style.text.clone();
- style.variant.style = self.style;
- styled(&self.body, &ctx, style)
- }
}
-function! {
- /// `font.weight`: Set text with a given weight.
- #[derive(Debug, PartialEq)]
- pub struct FontWeightFunc {
- body: Option<SyntaxTree>,
- weight: FontWeight,
- }
-
- parse(header, body, ctx) {
- FontWeightFunc {
- body: parse!(optional: body, ctx),
- weight: match header.args.get_pos::<Expr>()? {
- Expr::Number(weight) => {
- let weight = weight.round() as i16;
- FontWeight(
- if weight < 100 { 100 }
- else if weight <= 900 { weight }
- else { 900 }
- )
- }
- Expr::Ident(Ident(s)) => {
- match FontWeight::from_str(&s) {
- Some(weight) => weight,
- None => error!("invalid font weight: `{}`", s),
- }
- }
- _ => error!("expected identifier or number"),
- },
- }
- }
-
- layout(self, ctx) {
+/// Layout an optional body with a change of the text style.
+fn styled<'a, T, F>(
+ body: &'a Option<SyntaxModel>,
+ ctx: LayoutContext,
+ data: Option<T>,
+ f: F,
+) -> Commands<'a> where F: FnOnce(&mut TextStyle, T) {
+ if let Some(data) = data {
let mut style = ctx.style.text.clone();
- style.variant.weight = self.weight;
- styled(&self.body, &ctx, style)
- }
-}
-
-function! {
- /// `font.size`: Sets the font size.
- #[derive(Debug, PartialEq)]
- pub struct FontSizeFunc {
- body: Option<SyntaxTree>,
- size: ScaleSize,
- }
-
- parse(header, body, ctx) {
- FontSizeFunc {
- body: parse!(optional: body, ctx),
- size: header.args.get_pos::<ScaleSize>()?,
+ f(&mut style, data);
+
+ match body {
+ Some(model) => vec![
+ SetTextStyle(style),
+ LayoutSyntaxModel(model),
+ SetTextStyle(ctx.style.text.clone()),
+ ],
+ None => vec![SetTextStyle(style)],
}
- }
-
- layout(self, ctx) {
- let mut style = ctx.style.text.clone();
- 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)
- }
-}
-
-// -------------------------------------------------------------------------- //
-// Layout
-
-function! {
- /// `word.spacing`, `line.spacing`, `par.spacing`: The spacing between
- /// words, lines or paragraphs as a multiple of the font size.
- #[derive(Debug, PartialEq)]
- pub struct ContentSpacingFunc {
- body: Option<SyntaxTree>,
- content: ContentKind,
- spacing: f32,
- }
-
- type Meta = ContentKind;
-
- parse(header, body, ctx, meta) {
- ContentSpacingFunc {
- body: parse!(optional: body, ctx),
- content: meta,
- spacing: header.args.get_pos::<f64>()? as f32,
- }
- }
-
- layout(self, ctx) {
- let mut style = ctx.style.text.clone();
- match self.content {
- ContentKind::Word => style.word_spacing_scale = self.spacing,
- ContentKind::Line => style.line_spacing_scale = self.spacing,
- ContentKind::Paragraph => style.paragraph_spacing_scale = self.spacing,
- }
- styled(&self.body, &ctx, style)
- }
-}
-
-/// The different kinds of content that can be spaced.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub enum ContentKind {
- Word,
- Line,
- Paragraph,
-}
-
-// -------------------------------------------------------------------------- //
-// Spacing
-
-function! {
- /// `line.break`, `n`: Ends the current line.
- #[derive(Debug, Default, PartialEq)]
- pub struct LineBreakFunc;
-
- parse(default)
- layout() { vec![FinishLine] }
-}
-
-function! {
- /// `par.break`: Ends the current paragraph.
- ///
- /// self has the same effect as two subsequent newlines.
- #[derive(Debug, Default, PartialEq)]
- pub struct ParBreakFunc;
-
- parse(default)
- layout() { vec![BreakParagraph] }
-}
-
-function! {
- /// `page.break`: Ends the current page.
- #[derive(Debug, Default, PartialEq)]
- pub struct PageBreakFunc;
-
- parse(default)
- layout() { vec![BreakPage] }
-}
-
-function! {
- /// `spacing`, `h`, `v`: Adds spacing along an axis.
- #[derive(Debug, PartialEq)]
- pub struct SpacingFunc {
- axis: AxisKey,
- spacing: FSize,
- }
-
- type Meta = Option<SpecificAxis>;
-
- parse(header, body, _, meta) {
- parse!(forbidden: body);
-
- if let Some(axis) = meta {
- SpacingFunc {
- axis: AxisKey::Specific(axis),
- spacing: FSize::from_expr(
- header.args.get_pos::<Spanned<Expr>>()?
- )?,
- }
- } else {
- for arg in header.args.iter_keys() {
- let axis = AxisKey::from_ident(&arg.key)
- .map_err(|_| error!(@unexpected_argument))?;
-
- let spacing = FSize::from_expr(arg.value)?;
- return Ok(SpacingFunc { axis, spacing });
- }
-
- error!("expected axis and spacing")
- }
- }
-
- layout(self, ctx) {
- let axis = self.axis.to_generic(ctx.axes);
- let spacing = self.spacing.scaled(ctx.style.text.font_size());
- vec![SpacingFunc(spacing, SpacingKind::Hard, axis)]
- }
-}
-
-// -------------------------------------------------------------------------- //
-// Page setup
-
-function! {
- /// `page.size`: Set the size of pages.
- #[derive(Debug, PartialEq)]
- pub enum PageSizeFunc {
- Paper(Paper, bool),
- Custom(ExtentMap<PSize>),
- }
-
- parse(header, body) {
- parse!(forbidden: body);
-
- if let Some(name) = header.args.get_pos_opt::<Ident>()? {
- let flip = header.args.get_key_opt::<bool>("flip")?.unwrap_or(false);
- let paper = Paper::from_name(name.as_str())
- .ok_or_else(|| error!(@"invalid paper name: `{}`", name))?;
- PageSizeFunc::Paper(paper, flip)
- } else {
- PageSizeFunc::Custom(ExtentMap::new(&mut header.args, true)?)
- }
- }
-
- layout(self, ctx) {
- let mut style = ctx.style.page;
-
- match self {
- PageSizeFunc::Paper(paper, flip) => {
- style.class = paper.class;
- style.dimensions = paper.dimensions;
- if *flip {
- style.dimensions.swap();
- }
- }
-
- PageSizeFunc::Custom(map) => {
- style.class = PaperClass::Custom;
-
- let map = map.dedup(ctx.axes)?;
- let dims = &mut style.dimensions;
- map.with(Horizontal, |&psize| dims.x = psize.scaled(dims.x));
- map.with(Vertical, |&psize| dims.y = psize.scaled(dims.y));
- }
- }
-
- vec![SetPageStyle(style)]
- }
-}
-
-function! {
- /// `page.margins`: Sets the page margins.
- #[derive(Debug, PartialEq)]
- pub struct PageMarginsFunc {
- map: PaddingMap,
- }
-
- parse(header, body) {
- parse!(forbidden: body);
- PageMarginsFunc {
- map: PaddingMap::new(&mut header.args)?,
- }
- }
-
- layout(self, ctx) {
- let mut style = ctx.style.page;
- self.map.apply(ctx.axes, &mut style.margins)?;
- vec![SetPageStyle(style)]
- }
-}
-
-// -------------------------------------------------------------------------- //
-// Helpers
-
-/// Layout the body with the style or update the style if there is no body.
-fn styled<'a>(
- body: &'a Option<SyntaxTree>,
- ctx: &LayoutContext,
- style: TextStyle
-) -> Commands<'a> {
- match &body {
- Some(body) => vec![
- SetTextStyle(style),
- LayoutTree(body),
- SetTextStyle(ctx.style.text.clone()),
- ],
- None => vec![SetTextStyle(style)]
+ } else {
+ vec![]
}
}
diff --git a/src/library/page.rs b/src/library/page.rs
new file mode 100644
index 00000000..25f81bc1
--- /dev/null
+++ b/src/library/page.rs
@@ -0,0 +1,64 @@
+use crate::style::{Paper, PaperClass};
+use super::*;
+
+
+function! {
+ /// `page.size`: Set the size of pages.
+ #[derive(Debug, Clone, PartialEq)]
+ pub struct PageSizeFunc {
+ paper: Option<Paper>,
+ extents: AxisMap<Size>,
+ flip: bool,
+ }
+
+ parse(header, body, ctx, errors, decos) {
+ body!(nope: body, errors);
+ PageSizeFunc {
+ paper: header.args.pos.get::<Paper>(errors),
+ extents: AxisMap::parse::<ExtentKey, Size>(errors, &mut header.args.key),
+ flip: header.args.key.get::<bool>(errors, "flip").unwrap_or(false),
+ }
+ }
+
+ layout(self, ctx, errors) {
+ let mut style = ctx.style.page;
+
+ if let Some(paper) = self.paper {
+ style.class = paper.class;
+ style.dimensions = paper.dimensions;
+ } else {
+ style.class = PaperClass::Custom;
+ }
+
+ let map = self.extents.dedup(errors, ctx.axes);
+ map.with(Horizontal, |&width| style.dimensions.x = width);
+ map.with(Vertical, |&height| style.dimensions.y = height);
+
+ if self.flip {
+ style.dimensions.swap();
+ }
+
+ vec![SetPageStyle(style)]
+ }
+}
+
+function! {
+ /// `page.margins`: Sets the page margins.
+ #[derive(Debug, Clone, PartialEq)]
+ pub struct PageMarginsFunc {
+ padding: PaddingMap,
+ }
+
+ parse(header, body, ctx, errors, decos) {
+ body!(nope: body, errors);
+ PageMarginsFunc {
+ padding: PaddingMap::parse(errors, &mut header.args),
+ }
+ }
+
+ layout(self, ctx, errors) {
+ let mut style = ctx.style.page;
+ self.padding.apply(errors, ctx.axes, &mut style.margins);
+ vec![SetPageStyle(style)]
+ }
+}
diff --git a/src/library/spacing.rs b/src/library/spacing.rs
new file mode 100644
index 00000000..6c818292
--- /dev/null
+++ b/src/library/spacing.rs
@@ -0,0 +1,103 @@
+use super::*;
+use ContentKind::*;
+
+
+function! {
+ /// `line.break`, `n`: Ends the current line.
+ #[derive(Debug, Default, Clone, PartialEq)]
+ pub struct LineBreakFunc;
+
+ parse(default)
+ layout(self, ctx, errors) { vec![FinishLine] }
+}
+
+function! {
+ /// `par.break`: Ends the current paragraph.
+ ///
+ /// self has the same effect as two subsequent newlines.
+ #[derive(Debug, Default, Clone, PartialEq)]
+ pub struct ParBreakFunc;
+
+ parse(default)
+ layout(self, ctx, errors) { vec![BreakParagraph] }
+}
+
+function! {
+ /// `page.break`: Ends the current page.
+ #[derive(Debug, Default, Clone, PartialEq)]
+ pub struct PageBreakFunc;
+
+ parse(default)
+ layout(self, ctx, errors) { vec![BreakPage] }
+}
+
+function! {
+ /// `word.spacing`, `line.spacing`, `par.spacing`: The spacing between
+ /// words, lines or paragraphs as a multiple of the font size.
+ #[derive(Debug, Clone, PartialEq)]
+ pub struct ContentSpacingFunc {
+ body: Option<SyntaxModel>,
+ content: ContentKind,
+ spacing: Option<f32>,
+ }
+
+ type Meta = ContentKind;
+
+ parse(header, body, ctx, errors, decos, meta) {
+ ContentSpacingFunc {
+ body: body!(opt: body, ctx, errors, decos),
+ content: meta,
+ spacing: header.args.pos.get::<f64>(errors)
+ .map(|num| num as f32)
+ .or_missing(errors, header.name.span, "spacing"),
+ }
+ }
+
+ layout(self, ctx, errors) {
+ styled(&self.body, ctx, self.spacing, |t, s| match self.content {
+ Word => t.word_spacing_scale = s,
+ Line => t.line_spacing_scale = s,
+ Paragraph => t.paragraph_spacing_scale = s,
+ })
+ }
+}
+
+/// The different kinds of content that can be spaced.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum ContentKind {
+ Word,
+ Line,
+ Paragraph,
+}
+
+function! {
+ /// `spacing`, `h`, `v`: Adds spacing along an axis.
+ #[derive(Debug, Clone, PartialEq)]
+ pub struct SpacingFunc {
+ spacing: Option<(AxisKey, FSize)>,
+ }
+
+ type Meta = Option<SpecificAxis>;
+
+ parse(header, body, ctx, errors, decos, meta) {
+ body!(nope: body, errors);
+ SpacingFunc {
+ spacing: if let Some(axis) = meta {
+ header.args.pos.get::<FSize>(errors)
+ .map(|s| (AxisKey::Specific(axis), s))
+ } else {
+ header.args.key.get_with_key::<AxisKey, FSize>(errors)
+ }.or_missing(errors, header.name.span, "spacing"),
+ }
+ }
+
+ layout(self, ctx, errors) {
+ if let Some((axis, spacing)) = self.spacing {
+ let axis = axis.to_generic(ctx.axes);
+ let spacing = spacing.scaled(ctx.style.text.font_size());
+ vec![AddSpacing(spacing, SpacingKind::Hard, axis)]
+ } else {
+ vec![]
+ }
+ }
+}