summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-03-10 17:22:44 +0100
committerLaurenz <laurmaedje@gmail.com>2021-03-10 17:22:44 +0100
commitb0446cbdd189a8cc3b6a7321579f4aa89415a8e0 (patch)
tree821140ec6526750ba50715bd2e471376eb37ee29 /src
parentbbb9ed07ffe8a2a0ea0a232f6cfc52f82f7f7afe (diff)
Move around library types 🚚
Diffstat (limited to 'src')
-rw-r--r--src/library/align.rs170
-rw-r--r--src/library/base.rs62
-rw-r--r--src/library/extend.rs30
-rw-r--r--src/library/font.rs (renamed from src/library/style.rs)38
-rw-r--r--src/library/image.rs (renamed from src/library/insert.rs)4
-rw-r--r--src/library/layout.rs378
-rw-r--r--src/library/mod.rs32
-rw-r--r--src/library/page.rs111
-rw-r--r--src/library/shapes.rs61
-rw-r--r--src/library/spacing.rs36
10 files changed, 468 insertions, 454 deletions
diff --git a/src/library/align.rs b/src/library/align.rs
new file mode 100644
index 00000000..d16e697d
--- /dev/null
+++ b/src/library/align.rs
@@ -0,0 +1,170 @@
+use super::*;
+
+/// `align`: Align content along the layouting axes.
+///
+/// Which axis an alignment should apply to (main or cross) is inferred from
+/// either the argument itself (for anything other than `center`) or from the
+/// second argument if present, defaulting to the cross axis for a single
+/// `center` alignment.
+///
+/// # Positional arguments
+/// - Alignments: variadic, of type `alignment`.
+/// - Body: optional, of type `template`.
+///
+/// # Named arguments
+/// - Horizontal alignment: `horizontal`, of type `alignment`.
+/// - Vertical alignment: `vertical`, of type `alignment`.
+///
+/// # Relevant types and constants
+/// - Type `alignment`
+/// - `left`
+/// - `right`
+/// - `top`
+/// - `bottom`
+/// - `center`
+pub fn align(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
+ let first = args.find(ctx);
+ let second = args.find(ctx);
+ let hor = args.get(ctx, "horizontal");
+ let ver = args.get(ctx, "vertical");
+ let body = args.find::<ValueTemplate>(ctx);
+
+ Value::template("align", move |ctx| {
+ let snapshot = ctx.state.clone();
+
+ let mut had = Gen::uniform(false);
+ let mut had_center = false;
+
+ // Infer the axes alignments belong to.
+ for (axis, Spanned { v: arg, span }) in first
+ .into_iter()
+ .chain(second.into_iter())
+ .map(|arg: Spanned<Alignment>| (arg.v.axis(), arg))
+ .chain(hor.into_iter().map(|arg| (Some(SpecAxis::Horizontal), arg)))
+ .chain(ver.into_iter().map(|arg| (Some(SpecAxis::Vertical), arg)))
+ {
+ // Check whether we know which axis this alignment belongs to.
+ if let Some(axis) = axis {
+ // We know the axis.
+ let gen_axis = axis.switch(ctx.state.dirs);
+ let gen_align = arg.switch(ctx.state.dirs);
+
+ if arg.axis().map_or(false, |a| a != axis) {
+ ctx.diag(error!(span, "invalid alignment for {} axis", axis));
+ } else if had.get(gen_axis) {
+ ctx.diag(error!(span, "duplicate alignment for {} axis", axis));
+ } else {
+ *ctx.state.align.get_mut(gen_axis) = gen_align;
+ *had.get_mut(gen_axis) = true;
+ }
+ } else {
+ // We don't know the axis: This has to be a `center` alignment for a
+ // positional argument.
+ debug_assert_eq!(arg, Alignment::Center);
+
+ if had.main && had.cross {
+ ctx.diag(error!(span, "duplicate alignment"));
+ } else if had_center {
+ // Both this and the previous one are unspecified `center`
+ // alignments. Both axes should be centered.
+ ctx.state.align.main = Align::Center;
+ ctx.state.align.cross = Align::Center;
+ had = Gen::uniform(true);
+ } else {
+ had_center = true;
+ }
+ }
+
+ // If we we know the other alignment, we can handle the unspecified
+ // `center` alignment.
+ if had_center && (had.main || had.cross) {
+ if had.main {
+ ctx.state.align.cross = Align::Center;
+ had.cross = true;
+ } else {
+ ctx.state.align.main = Align::Center;
+ had.main = true;
+ }
+ had_center = false;
+ }
+ }
+
+ // If `had_center` wasn't flushed by now, it's the only argument and then we
+ // default to applying it to the cross axis.
+ if had_center {
+ ctx.state.align.cross = Align::Center;
+ }
+
+ if ctx.state.align.main != snapshot.align.main {
+ ctx.end_par_group();
+ ctx.start_par_group();
+ }
+
+ if let Some(body) = &body {
+ body.exec(ctx);
+ ctx.state = snapshot;
+ }
+ })
+}
+
+/// An alignment argument.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
+pub(super) enum Alignment {
+ Left,
+ Center,
+ Right,
+ Top,
+ Bottom,
+}
+
+impl Alignment {
+ /// The specific axis this alignment refers to.
+ fn axis(self) -> Option<SpecAxis> {
+ match self {
+ Self::Left => Some(SpecAxis::Horizontal),
+ Self::Right => Some(SpecAxis::Horizontal),
+ Self::Top => Some(SpecAxis::Vertical),
+ Self::Bottom => Some(SpecAxis::Vertical),
+ Self::Center => None,
+ }
+ }
+}
+
+impl Switch for Alignment {
+ type Other = Align;
+
+ fn switch(self, dirs: LayoutDirs) -> Self::Other {
+ let get = |dir: Dir, at_positive_start| {
+ if dir.is_positive() == at_positive_start {
+ Align::Start
+ } else {
+ Align::End
+ }
+ };
+
+ let dirs = dirs.switch(dirs);
+ match self {
+ Self::Left => get(dirs.horizontal, true),
+ Self::Right => get(dirs.horizontal, false),
+ Self::Top => get(dirs.vertical, true),
+ Self::Bottom => get(dirs.vertical, false),
+ Self::Center => Align::Center,
+ }
+ }
+}
+
+impl Display for Alignment {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(match self {
+ Self::Left => "left",
+ Self::Center => "center",
+ Self::Right => "right",
+ Self::Top => "top",
+ Self::Bottom => "bottom",
+ })
+ }
+}
+
+typify! {
+ Alignment: "alignment",
+}
diff --git a/src/library/base.rs b/src/library/base.rs
new file mode 100644
index 00000000..3d067faa
--- /dev/null
+++ b/src/library/base.rs
@@ -0,0 +1,62 @@
+use crate::color::{Color, RgbaColor};
+use crate::pretty::pretty;
+
+use super::*;
+
+/// `repr`: Get the string representation of a value.
+///
+/// # Positional arguments
+/// - Any value.
+///
+/// # Return value
+/// The string representation of the value.
+pub fn repr(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
+ match args.require::<Value>(ctx, "value") {
+ Some(value) => pretty(&value).into(),
+ None => Value::Error,
+ }
+}
+
+/// `rgb`: Create an RGB(A) color.
+///
+/// # Positional arguments
+/// - Red component: of type `float`, between 0.0 and 1.0.
+/// - Green component: of type `float`, between 0.0 and 1.0.
+/// - Blue component: of type `float`, between 0.0 and 1.0.
+/// - Alpha component: optional, of type `float`, between 0.0 and 1.0.
+pub fn rgb(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
+ let r = args.require(ctx, "red component");
+ let g = args.require(ctx, "green component");
+ let b = args.require(ctx, "blue component");
+ let a = args.find(ctx);
+
+ let mut clamp = |component: Option<Spanned<f64>>, default| {
+ component.map_or(default, |c| {
+ if c.v < 0.0 || c.v > 1.0 {
+ ctx.diag(warning!(c.span, "should be between 0.0 and 1.0"));
+ }
+ (c.v.max(0.0).min(1.0) * 255.0).round() as u8
+ })
+ };
+
+ Value::Color(Color::Rgba(RgbaColor::new(
+ clamp(r, 0),
+ clamp(g, 0),
+ clamp(b, 0),
+ clamp(a, 255),
+ )))
+}
+
+/// `type`: Find out the name of a value's type.
+///
+/// # Positional arguments
+/// - Any value.
+///
+/// # Return value
+/// The name of the value's type as a string.
+pub fn type_(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
+ match args.require::<Value>(ctx, "value") {
+ Some(value) => value.type_name().into(),
+ None => Value::Error,
+ }
+}
diff --git a/src/library/extend.rs b/src/library/extend.rs
deleted file mode 100644
index cf69dbd0..00000000
--- a/src/library/extend.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-use crate::prelude::*;
-use crate::pretty::pretty;
-
-/// `type`: Find out the name of a value's type.
-///
-/// # Positional arguments
-/// - Any value.
-///
-/// # Return value
-/// The name of the value's type as a string.
-pub fn type_(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
- match args.require::<Value>(ctx, "value") {
- Some(value) => value.type_name().into(),
- None => Value::Error,
- }
-}
-
-/// `repr`: Get the string representation of a value.
-///
-/// # Positional arguments
-/// - Any value.
-///
-/// # Return value
-/// The string representation of the value.
-pub fn repr(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
- match args.require::<Value>(ctx, "value") {
- Some(value) => pretty(&value).into(),
- None => Value::Error,
- }
-}
diff --git a/src/library/style.rs b/src/library/font.rs
index 35727515..439d9805 100644
--- a/src/library/style.rs
+++ b/src/library/font.rs
@@ -1,15 +1,13 @@
-use std::fmt::{self, Display, Formatter};
-
use fontdock::{FontStretch, FontStyle, FontWeight};
-use crate::color::{Color, RgbaColor};
-use crate::prelude::*;
+use super::*;
/// `font`: Configure the font.
///
/// # Positional arguments
/// - Font size: optional, of type `linear` relative to current font size.
/// - Font families: variadic, of type `font-family`.
+/// - Body: optional, of type `template`.
///
/// # Named arguments
/// - Font Style: `style`, of type `font-style`.
@@ -121,7 +119,7 @@ struct FontFamilies(Vec<FontFamily>);
/// A single font family.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
-pub(crate) enum FontFamily {
+pub(super) enum FontFamily {
Serif,
SansSerif,
Monospace,
@@ -183,33 +181,3 @@ typify! {
typify! {
FontStretch: "font stretch"
}
-
-/// `rgb`: Create an RGB(A) color.
-///
-/// # Positional arguments
-/// - Red component: of type `float`, between 0.0 and 1.0.
-/// - Green component: of type `float`, between 0.0 and 1.0.
-/// - Blue component: of type `float`, between 0.0 and 1.0.
-/// - Alpha component: optional, of type `float`, between 0.0 and 1.0.
-pub fn rgb(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
- let r = args.require(ctx, "red component");
- let g = args.require(ctx, "green component");
- let b = args.require(ctx, "blue component");
- let a = args.find(ctx);
-
- let mut clamp = |component: Option<Spanned<f64>>, default| {
- component.map_or(default, |c| {
- if c.v < 0.0 || c.v > 1.0 {
- ctx.diag(warning!(c.span, "should be between 0.0 and 1.0"));
- }
- (c.v.max(0.0).min(1.0) * 255.0).round() as u8
- })
- };
-
- Value::Color(Color::Rgba(RgbaColor::new(
- clamp(r, 0),
- clamp(g, 0),
- clamp(b, 0),
- clamp(a, 255),
- )))
-}
diff --git a/src/library/insert.rs b/src/library/image.rs
index 4f0f6489..06908ce8 100644
--- a/src/library/insert.rs
+++ b/src/library/image.rs
@@ -1,8 +1,8 @@
-use image::GenericImageView;
+use ::image::GenericImageView;
+use super::*;
use crate::env::{ImageResource, ResourceId};
use crate::layout::*;
-use crate::prelude::*;
/// `image`: Insert an image.
///
diff --git a/src/library/layout.rs b/src/library/layout.rs
deleted file mode 100644
index 7fc7154f..00000000
--- a/src/library/layout.rs
+++ /dev/null
@@ -1,378 +0,0 @@
-use std::fmt::{self, Display, Formatter};
-
-use crate::exec::Softness;
-use crate::layout::{Expansion, Fill, NodeBackground, NodeFixed, NodeSpacing, NodeStack};
-use crate::paper::{Paper, PaperClass};
-use crate::prelude::*;
-
-/// `align`: Align content along the layouting axes.
-///
-/// Which axis an alignment should apply to (main or cross) is inferred from
-/// either the argument itself (for anything other than `center`) or from the
-/// second argument if present, defaulting to the cross axis for a single
-/// `center` alignment.
-///
-/// # Positional arguments
-/// - Alignments: variadic, of type `alignment`.
-///
-/// # Named arguments
-/// - Horizontal alignment: `horizontal`, of type `alignment`.
-/// - Vertical alignment: `vertical`, of type `alignment`.
-///
-/// # Relevant types and constants
-/// - Type `alignment`
-/// - `left`
-/// - `right`
-/// - `top`
-/// - `bottom`
-/// - `center`
-pub fn align(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
- let first = args.find(ctx);
- let second = args.find(ctx);
- let hor = args.get(ctx, "horizontal");
- let ver = args.get(ctx, "vertical");
- let body = args.find::<ValueTemplate>(ctx);
-
- Value::template("align", move |ctx| {
- let snapshot = ctx.state.clone();
-
- let mut had = Gen::uniform(false);
- let mut had_center = false;
-
- // Infer the axes alignments belong to.
- for (axis, Spanned { v: arg, span }) in first
- .into_iter()
- .chain(second.into_iter())
- .map(|arg: Spanned<Alignment>| (arg.v.axis(), arg))
- .chain(hor.into_iter().map(|arg| (Some(SpecAxis::Horizontal), arg)))
- .chain(ver.into_iter().map(|arg| (Some(SpecAxis::Vertical), arg)))
- {
- // Check whether we know which axis this alignment belongs to.
- if let Some(axis) = axis {
- // We know the axis.
- let gen_axis = axis.switch(ctx.state.dirs);
- let gen_align = arg.switch(ctx.state.dirs);
-
- if arg.axis().map_or(false, |a| a != axis) {
- ctx.diag(error!(span, "invalid alignment for {} axis", axis));
- } else if had.get(gen_axis) {
- ctx.diag(error!(span, "duplicate alignment for {} axis", axis));
- } else {
- *ctx.state.align.get_mut(gen_axis) = gen_align;
- *had.get_mut(gen_axis) = true;
- }
- } else {
- // We don't know the axis: This has to be a `center` alignment for a
- // positional argument.
- debug_assert_eq!(arg, Alignment::Center);
-
- if had.main && had.cross {
- ctx.diag(error!(span, "duplicate alignment"));
- } else if had_center {
- // Both this and the previous one are unspecified `center`
- // alignments. Both axes should be centered.
- ctx.state.align.main = Align::Center;
- ctx.state.align.cross = Align::Center;
- had = Gen::uniform(true);
- } else {
- had_center = true;
- }
- }
-
- // If we we know the other alignment, we can handle the unspecified
- // `center` alignment.
- if had_center && (had.main || had.cross) {
- if had.main {
- ctx.state.align.cross = Align::Center;
- had.cross = true;
- } else {
- ctx.state.align.main = Align::Center;
- had.main = true;
- }
- had_center = false;
- }
- }
-
- // If `had_center` wasn't flushed by now, it's the only argument and then we
- // default to applying it to the cross axis.
- if had_center {
- ctx.state.align.cross = Align::Center;
- }
-
- if ctx.state.align.main != snapshot.align.main {
- ctx.end_par_group();
- ctx.start_par_group();
- }
-
- if let Some(body) = &body {
- body.exec(ctx);
- ctx.state = snapshot;
- }
- })
-}
-
-/// An alignment argument.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
-pub(crate) enum Alignment {
- Left,
- Center,
- Right,
- Top,
- Bottom,
-}
-
-impl Alignment {
- /// The specific axis this alignment refers to.
- fn axis(self) -> Option<SpecAxis> {
- match self {
- Self::Left => Some(SpecAxis::Horizontal),
- Self::Right => Some(SpecAxis::Horizontal),
- Self::Top => Some(SpecAxis::Vertical),
- Self::Bottom => Some(SpecAxis::Vertical),
- Self::Center => None,
- }
- }
-}
-
-impl Switch for Alignment {
- type Other = Align;
-
- fn switch(self, dirs: LayoutDirs) -> Self::Other {
- let get = |dir: Dir, at_positive_start| {
- if dir.is_positive() == at_positive_start {
- Align::Start
- } else {
- Align::End
- }
- };
-
- let dirs = dirs.switch(dirs);
- match self {
- Self::Left => get(dirs.horizontal, true),
- Self::Right => get(dirs.horizontal, false),
- Self::Top => get(dirs.vertical, true),
- Self::Bottom => get(dirs.vertical, false),
- Self::Center => Align::Center,
- }
- }
-}
-
-typify! {
- Alignment: "alignment",
-}
-
-impl Display for Alignment {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.pad(match self {
- Self::Left => "left",
- Self::Center => "center",
- Self::Right => "right",
- Self::Top => "top",
- Self::Bottom => "bottom",
- })
- }
-}
-
-/// `box`: Layout content into a box.
-///
-/// # Named arguments
-/// - Width of the box: `width`, of type `linear` relative to parent width.
-/// - Height of the box: `height`, of type `linear` relative to parent height.
-/// - Main layouting direction: `main-dir`, of type `direction`.
-/// - Cross layouting direction: `cross-dir`, of type `direction`.
-/// - Background color of the box: `color`, of type `color`.
-///
-/// # Relevant types and constants
-/// - Type `direction`
-/// - `ltr` (left to right)
-/// - `rtl` (right to left)
-/// - `ttb` (top to bottom)
-/// - `btt` (bottom to top)
-pub fn box_(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
- let width = args.get(ctx, "width");
- let height = args.get(ctx, "height");
- let main = args.get(ctx, "main-dir");
- let cross = args.get(ctx, "cross-dir");
- let color = args.get(ctx, "color");
- let body = args.find::<ValueTemplate>(ctx);
-
- Value::template("box", move |ctx| {
- let snapshot = ctx.state.clone();
-
- ctx.set_dirs(Gen::new(main, cross));
- let dirs = ctx.state.dirs;
- let align = ctx.state.align;
-
- ctx.start_content_group();
- if let Some(body) = &body {
- body.exec(ctx);
- }
- let children = ctx.end_content_group();
-
- let fill_if = |c| if c { Expansion::Fill } else { Expansion::Fit };
- let expand = Spec::new(fill_if(width.is_some()), fill_if(height.is_some()));
- let fixed = NodeFixed {
- width,
- height,
- child: NodeStack { dirs, align, expand, children }.into(),
- };
-
- if let Some(color) = color {
- ctx.push(NodeBackground {
- fill: Fill::Color(color),
- child: fixed.into(),
- });
- } else {
- ctx.push(fixed);
- }
-
- ctx.state = snapshot;
- })
-}
-
-typify! {
- Dir: "direction"
-}
-
-/// `h`: Add horizontal spacing.
-///
-/// # Positional arguments
-/// - Amount of spacing: of type `linear` relative to current font size.
-pub fn h(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
- spacing(ctx, args, SpecAxis::Horizontal)
-}
-
-/// `v`: Add vertical spacing.
-///
-/// # Positional arguments
-/// - Amount of spacing: of type `linear` relative to current font size.
-pub fn v(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
- spacing(ctx, args, SpecAxis::Vertical)
-}
-
-/// Apply spacing along a specific axis.
-fn spacing(ctx: &mut EvalContext, args: &mut ValueArgs, axis: SpecAxis) -> Value {
- let spacing: Option<Linear> = args.require(ctx, "spacing");
-
- Value::template("spacing", move |ctx| {
- if let Some(linear) = spacing {
- let amount = linear.resolve(ctx.state.font.font_size());
- let spacing = NodeSpacing { amount, softness: Softness::Hard };
- if axis == ctx.state.dirs.main.axis() {
- ctx.end_par_group();
- ctx.push(spacing);
- ctx.start_par_group();
- } else {
- ctx.push(spacing);
- }
- }
- })
-}
-
-/// `page`: Configure pages.
-///
-/// # Positional arguments
-/// - Paper name: optional, of type `string`, see [here](crate::paper) for a
-/// full list of all paper names.
-///
-/// # Named arguments
-/// - Width of the page: `width`, of type `length`.
-/// - Height of the page: `height`, of type `length`.
-/// - Margins for all sides: `margins`, of type `linear` relative to sides.
-/// - Left margin: `left`, of type `linear` relative to width.
-/// - Right margin: `right`, of type `linear` relative to width.
-/// - Top margin: `top`, of type `linear` relative to height.
-/// - Bottom margin: `bottom`, of type `linear` relative to height.
-/// - Flip width and height: `flip`, of type `bool`.
-/// - Main layouting direction: `main-dir`, of type `direction`.
-/// - Cross layouting direction: `cross-dir`, of type `direction`.
-pub fn page(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
- let paper = args.find::<Spanned<String>>(ctx).and_then(|name| {
- Paper::from_name(&name.v).or_else(|| {
- ctx.diag(error!(name.span, "invalid paper name"));
- None
- })
- });
-
- let width = args.get(ctx, "width");
- let height = args.get(ctx, "height");
- let margins = args.get(ctx, "margins");
- let left = args.get(ctx, "left");
- let top = args.get(ctx, "top");
- let right = args.get(ctx, "right");
- let bottom = args.get(ctx, "bottom");
- let flip = args.get(ctx, "flip");
- let main = args.get(ctx, "main-dir");
- let cross = args.get(ctx, "cross-dir");
- let body = args.find::<ValueTemplate>(ctx);
-
- Value::template("page", move |ctx| {
- let snapshot = ctx.state.clone();
-
- if let Some(paper) = paper {
- ctx.state.page.class = paper.class;
- ctx.state.page.size = paper.size();
- ctx.state.page.expand = Spec::uniform(Expansion::Fill);
- }
-
- if let Some(width) = width {
- ctx.state.page.class = PaperClass::Custom;
- ctx.state.page.size.width = width;
- ctx.state.page.expand.horizontal = Expansion::Fill;
- }
-
- if let Some(height) = height {
- ctx.state.page.class = PaperClass::Custom;
- ctx.state.page.size.height = height;
- ctx.state.page.expand.vertical = Expansion::Fill;
- }
-
- if let Some(margins) = margins {
- ctx.state.page.margins = Sides::uniform(Some(margins));
- }
-
- if let Some(left) = left {
- ctx.state.page.margins.left = Some(left);
- }
-
- if let Some(top) = top {
- ctx.state.page.margins.top = Some(top);
- }
-
- if let Some(right) = right {
- ctx.state.page.margins.right = Some(right);
- }
-
- if let Some(bottom) = bottom {
- ctx.state.page.margins.bottom = Some(bottom);
- }
-
- if flip.unwrap_or(false) {
- let page = &mut ctx.state.page;
- std::mem::swap(&mut page.size.width, &mut page.size.height);
- std::mem::swap(&mut page.expand.horizontal, &mut page.expand.vertical);
- }
-
- ctx.set_dirs(Gen::new(main, cross));
-
- let mut softness = ctx.end_page_group(|_| false);
- if let Some(body) = &body {
- // TODO: Restrict body to a single page?
- ctx.start_page_group(Softness::Hard);
- body.exec(ctx);
- ctx.end_page_group(|s| s == Softness::Hard);
- softness = Softness::Soft;
- ctx.state = snapshot;
- }
-
- ctx.start_page_group(softness);
- })
-}
-
-/// `pagebreak`: Start a new page.
-pub fn pagebreak(_: &mut EvalContext, _: &mut ValueArgs) -> Value {
- Value::template("pagebreak", move |ctx| {
- ctx.end_page_group(|_| true);
- ctx.start_page_group(Softness::Hard);
- })
-}
diff --git a/src/library/mod.rs b/src/library/mod.rs
index d34b338c..f846e6ee 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -3,20 +3,30 @@
//! Call [`new`] to obtain a [`Scope`] containing all standard library
//! definitions.
-mod extend;
-mod insert;
-mod layout;
-mod style;
+mod align;
+mod base;
+mod font;
+mod image;
+mod page;
+mod shapes;
+mod spacing;
-pub use extend::*;
-pub use insert::*;
-pub use layout::*;
-pub use style::*;
+pub use self::image::*;
+pub use align::*;
+pub use base::*;
+pub use font::*;
+pub use page::*;
+pub use shapes::*;
+pub use spacing::*;
+
+use std::fmt::{self, Display, Formatter};
use fontdock::{FontStretch, FontStyle, FontWeight};
use crate::eval::{Scope, ValueAny, ValueFunc};
-use crate::geom::Dir;
+use crate::exec::Softness;
+use crate::layout::*;
+use crate::prelude::*;
/// Construct a scope containing all standard library definitions.
pub fn new() -> Scope {
@@ -80,3 +90,7 @@ pub fn new() -> Scope {
std
}
+
+typify! {
+ Dir: "direction"
+}
diff --git a/src/library/page.rs b/src/library/page.rs
new file mode 100644
index 00000000..1fdf4d3f
--- /dev/null
+++ b/src/library/page.rs
@@ -0,0 +1,111 @@
+use super::*;
+use crate::paper::{Paper, PaperClass};
+
+/// `page`: Configure pages.
+///
+/// # Positional arguments
+/// - Paper name: optional, of type `string`, see [here](crate::paper) for a
+/// full list of all paper names.
+/// - Body: optional, of type `template`.
+///
+/// # Named arguments
+/// - Width of the page: `width`, of type `length`.
+/// - Height of the page: `height`, of type `length`.
+/// - Margins for all sides: `margins`, of type `linear` relative to sides.
+/// - Left margin: `left`, of type `linear` relative to width.
+/// - Right margin: `right`, of type `linear` relative to width.
+/// - Top margin: `top`, of type `linear` relative to height.
+/// - Bottom margin: `bottom`, of type `linear` relative to height.
+/// - Flip width and height: `flip`, of type `bool`.
+/// - Main layouting direction: `main-dir`, of type `direction`.
+/// - Cross layouting direction: `cross-dir`, of type `direction`.
+pub fn page(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
+ let paper = args.find::<Spanned<String>>(ctx).and_then(|name| {
+ Paper::from_name(&name.v).or_else(|| {
+ ctx.diag(error!(name.span, "invalid paper name"));
+ None
+ })
+ });
+
+ let width = args.get(ctx, "width");
+ let height = args.get(ctx, "height");
+ let margins = args.get(ctx, "margins");
+ let left = args.get(ctx, "left");
+ let top = args.get(ctx, "top");
+ let right = args.get(ctx, "right");
+ let bottom = args.get(ctx, "bottom");
+ let flip = args.get(ctx, "flip");
+ let main = args.get(ctx, "main-dir");
+ let cross = args.get(ctx, "cross-dir");
+ let body = args.find::<ValueTemplate>(ctx);
+
+ Value::template("page", move |ctx| {
+ let snapshot = ctx.state.clone();
+
+ if let Some(paper) = paper {
+ ctx.state.page.class = paper.class;
+ ctx.state.page.size = paper.size();
+ ctx.state.page.expand = Spec::uniform(Expansion::Fill);
+ }
+
+ if let Some(width) = width {
+ ctx.state.page.class = PaperClass::Custom;
+ ctx.state.page.size.width = width;
+ ctx.state.page.expand.horizontal = Expansion::Fill;
+ }
+
+ if let Some(height) = height {
+ ctx.state.page.class = PaperClass::Custom;
+ ctx.state.page.size.height = height;
+ ctx.state.page.expand.vertical = Expansion::Fill;
+ }
+
+ if let Some(margins) = margins {
+ ctx.state.page.margins = Sides::uniform(Some(margins));
+ }
+
+ if let Some(left) = left {
+ ctx.state.page.margins.left = Some(left);
+ }
+
+ if let Some(top) = top {
+ ctx.state.page.margins.top = Some(top);
+ }
+
+ if let Some(right) = right {
+ ctx.state.page.margins.right = Some(right);
+ }
+
+ if let Some(bottom) = bottom {
+ ctx.state.page.margins.bottom = Some(bottom);
+ }
+
+ if flip.unwrap_or(false) {
+ let page = &mut ctx.state.page;
+ std::mem::swap(&mut page.size.width, &mut page.size.height);
+ std::mem::swap(&mut page.expand.horizontal, &mut page.expand.vertical);
+ }
+
+ ctx.set_dirs(Gen::new(main, cross));
+
+ let mut softness = ctx.end_page_group(|_| false);
+ if let Some(body) = &body {
+ // TODO: Restrict body to a single page?
+ ctx.start_page_group(Softness::Hard);
+ body.exec(ctx);
+ ctx.end_page_group(|s| s == Softness::Hard);
+ softness = Softness::Soft;
+ ctx.state = snapshot;
+ }
+
+ ctx.start_page_group(softness);
+ })
+}
+
+/// `pagebreak`: Start a new page.
+pub fn pagebreak(_: &mut EvalContext, _: &mut ValueArgs) -> Value {
+ Value::template("pagebreak", move |ctx| {
+ ctx.end_page_group(|_| true);
+ ctx.start_page_group(Softness::Hard);
+ })
+}
diff --git a/src/library/shapes.rs b/src/library/shapes.rs
new file mode 100644
index 00000000..f9685fce
--- /dev/null
+++ b/src/library/shapes.rs
@@ -0,0 +1,61 @@
+use super::*;
+
+/// `box`: Create a rectangular box.
+///
+/// # Positional arguments
+/// - Body: optional, of type `template`.
+///
+/// # Named arguments
+/// - Width of the box: `width`, of type `linear` relative to parent width.
+/// - Height of the box: `height`, of type `linear` relative to parent height.
+/// - Main layouting direction: `main-dir`, of type `direction`.
+/// - Cross layouting direction: `cross-dir`, of type `direction`.
+/// - Background color of the box: `color`, of type `color`.
+///
+/// # Relevant types and constants
+/// - Type `direction`
+/// - `ltr` (left to right)
+/// - `rtl` (right to left)
+/// - `ttb` (top to bottom)
+/// - `btt` (bottom to top)
+pub fn box_(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
+ let width = args.get(ctx, "width");
+ let height = args.get(ctx, "height");
+ let main = args.get(ctx, "main-dir");
+ let cross = args.get(ctx, "cross-dir");
+ let color = args.get(ctx, "color");
+ let body = args.find::<ValueTemplate>(ctx);
+
+ Value::template("box", move |ctx| {
+ let snapshot = ctx.state.clone();
+
+ ctx.set_dirs(Gen::new(main, cross));
+ let dirs = ctx.state.dirs;
+ let align = ctx.state.align;
+
+ ctx.start_content_group();
+ if let Some(body) = &body {
+ body.exec(ctx);
+ }
+ let children = ctx.end_content_group();
+
+ let fill_if = |c| if c { Expansion::Fill } else { Expansion::Fit };
+ let expand = Spec::new(fill_if(width.is_some()), fill_if(height.is_some()));
+ let fixed = NodeFixed {
+ width,
+ height,
+ child: NodeStack { dirs, align, expand, children }.into(),
+ };
+
+ if let Some(color) = color {
+ ctx.push(NodeBackground {
+ fill: Fill::Color(color),
+ child: fixed.into(),
+ });
+ } else {
+ ctx.push(fixed);
+ }
+
+ ctx.state = snapshot;
+ })
+}
diff --git a/src/library/spacing.rs b/src/library/spacing.rs
new file mode 100644
index 00000000..624890e9
--- /dev/null
+++ b/src/library/spacing.rs
@@ -0,0 +1,36 @@
+use super::*;
+
+/// `h`: Add horizontal spacing.
+///
+/// # Positional arguments
+/// - Amount of spacing: of type `linear` relative to current font size.
+pub fn h(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
+ spacing(ctx, args, SpecAxis::Horizontal)
+}
+
+/// `v`: Add vertical spacing.
+///
+/// # Positional arguments
+/// - Amount of spacing: of type `linear` relative to current font size.
+pub fn v(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
+ spacing(ctx, args, SpecAxis::Vertical)
+}
+
+/// Apply spacing along a specific axis.
+fn spacing(ctx: &mut EvalContext, args: &mut ValueArgs, axis: SpecAxis) -> Value {
+ let spacing: Option<Linear> = args.require(ctx, "spacing");
+
+ Value::template("spacing", move |ctx| {
+ if let Some(linear) = spacing {
+ let amount = linear.resolve(ctx.state.font.font_size());
+ let spacing = NodeSpacing { amount, softness: Softness::Hard };
+ if axis == ctx.state.dirs.main.axis() {
+ ctx.end_par_group();
+ ctx.push(spacing);
+ ctx.start_par_group();
+ } else {
+ ctx.push(spacing);
+ }
+ }
+ })
+}