summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/layout/document.rs37
-rw-r--r--src/layout/mod.rs36
-rw-r--r--src/layout/node.rs12
-rw-r--r--src/layout/pad.rs12
-rw-r--r--src/library/align.rs185
-rw-r--r--src/library/boxed.rs44
-rw-r--r--src/library/color.rs27
-rw-r--r--src/library/graphics.rs42
-rw-r--r--src/library/insert.rs (renamed from src/layout/graphics.rs)53
-rw-r--r--src/library/layout.rs337
-rw-r--r--src/library/mod.rs20
-rw-r--r--src/library/page.rs88
-rw-r--r--src/library/spacing.rs39
-rw-r--r--src/library/style.rs (renamed from src/library/font.rs)44
14 files changed, 475 insertions, 501 deletions
diff --git a/src/layout/document.rs b/src/layout/document.rs
deleted file mode 100644
index 112457d6..00000000
--- a/src/layout/document.rs
+++ /dev/null
@@ -1,37 +0,0 @@
-use super::*;
-
-/// The top-level layout node.
-#[derive(Debug, Clone, PartialEq)]
-pub struct Document {
- /// The runs of pages with same properties.
- pub runs: Vec<Pages>,
-}
-
-impl Document {
- /// Layout the document.
- pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<BoxLayout> {
- let mut layouts = vec![];
- for run in &self.runs {
- layouts.extend(run.layout(ctx));
- }
- layouts
- }
-}
-
-/// A variable-length run of pages that all have the same properties.
-#[derive(Debug, Clone, PartialEq)]
-pub struct Pages {
- /// The size of the pages.
- pub size: Size,
- /// The layout node that produces the actual pages (typically a [`Stack`]).
- pub child: LayoutNode,
-}
-
-impl Pages {
- /// Layout the page run.
- pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<BoxLayout> {
- let areas = Areas::repeat(self.size);
- let layouted = self.child.layout(ctx, &areas);
- layouted.into_layouts()
- }
-}
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 2d4553b4..57add044 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -1,8 +1,6 @@
//! Layouting of documents.
-mod document;
mod fixed;
-mod graphics;
mod node;
mod pad;
mod par;
@@ -16,9 +14,7 @@ use crate::font::SharedFontLoader;
use crate::geom::*;
use crate::shaping::Shaped;
-pub use document::*;
pub use fixed::*;
-pub use graphics::*;
pub use node::*;
pub use pad::*;
pub use par::*;
@@ -193,3 +189,35 @@ pub struct ImageElement {
/// The document size of the image.
pub size: Size,
}
+
+/// The top-level layout node.
+#[derive(Debug, Clone, PartialEq)]
+pub struct Document {
+ /// The runs of pages with same properties.
+ pub runs: Vec<Pages>,
+}
+
+impl Document {
+ /// Layout the document.
+ pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<BoxLayout> {
+ self.runs.iter().flat_map(|run| run.layout(ctx)).collect()
+ }
+}
+
+/// A variable-length run of pages that all have the same properties.
+#[derive(Debug, Clone, PartialEq)]
+pub struct Pages {
+ /// The size of each page.
+ pub size: Size,
+ /// The layout node that produces the actual pages (typically a [`Stack`]).
+ pub child: LayoutNode,
+}
+
+impl Pages {
+ /// Layout the page run.
+ pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<BoxLayout> {
+ let areas = Areas::repeat(self.size);
+ let layouted = self.child.layout(ctx, &areas);
+ layouted.into_layouts()
+ }
+}
diff --git a/src/layout/node.rs b/src/layout/node.rs
index 35e742a5..f01ded56 100644
--- a/src/layout/node.rs
+++ b/src/layout/node.rs
@@ -74,12 +74,6 @@ impl Debug for Dynamic {
}
}
-impl From<Dynamic> for LayoutNode {
- fn from(dynamic: Dynamic) -> Self {
- Self::Dyn(dynamic)
- }
-}
-
impl Clone for Dynamic {
fn clone(&self) -> Self {
Self(self.0.dyn_clone())
@@ -92,6 +86,12 @@ impl PartialEq for Dynamic {
}
}
+impl From<Dynamic> for LayoutNode {
+ fn from(dynamic: Dynamic) -> Self {
+ Self::Dyn(dynamic)
+ }
+}
+
/// A dynamic node, which can implement custom layouting behaviour.
///
/// This trait just combines the requirements for types to qualify as dynamic
diff --git a/src/layout/pad.rs b/src/layout/pad.rs
index 09cf016b..00830a07 100644
--- a/src/layout/pad.rs
+++ b/src/layout/pad.rs
@@ -29,6 +29,12 @@ impl Layout for Pad {
}
}
+impl From<Pad> for LayoutNode {
+ fn from(pad: Pad) -> Self {
+ Self::dynamic(pad)
+ }
+}
+
/// Shrink all areas by the padding.
fn shrink_areas(areas: &Areas, padding: Sides<Linear>) -> Areas {
let shrink = |size| size - padding.resolve(size).size();
@@ -52,9 +58,3 @@ fn pad_layout(layout: &mut BoxLayout, padding: Sides<Linear>) {
*point += origin;
}
}
-
-impl From<Pad> for LayoutNode {
- fn from(pad: Pad) -> Self {
- Self::dynamic(pad)
- }
-}
diff --git a/src/library/align.rs b/src/library/align.rs
deleted file mode 100644
index 4f4a1750..00000000
--- a/src/library/align.rs
+++ /dev/null
@@ -1,185 +0,0 @@
-use crate::prelude::*;
-use std::fmt::{self, Display, Formatter};
-
-/// `align`: Align content along the layouting axes.
-///
-/// # Positional arguments
-/// - At most two of `left`, `right`, `top`, `bottom`, `center`.
-///
-/// When `center` is used as a positional argument, it is automatically inferred
-/// which axis it should apply to depending on further arguments, defaulting
-/// to the axis, text is set along.
-///
-/// # Keyword arguments
-/// - `horizontal`: Any of `left`, `right` or `center`.
-/// - `vertical`: Any of `top`, `bottom` or `center`.
-///
-/// There may not be two alignment specifications for the same axis.
-pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value {
- let snapshot = ctx.state.clone();
- let body = args.find::<SynTree>();
- let first = args.get::<_, Spanned<AlignArg>>(ctx, 0);
- let second = args.get::<_, Spanned<AlignArg>>(ctx, 1);
- let hor = args.get::<_, Spanned<AlignArg>>(ctx, "horizontal");
- let ver = args.get::<_, Spanned<AlignArg>>(ctx, "vertical");
- args.done(ctx);
-
- let iter = first
- .into_iter()
- .chain(second.into_iter())
- .map(|align| (align.v.axis(), align))
- .chain(hor.into_iter().map(|align| (Some(SpecAxis::Horizontal), align)))
- .chain(ver.into_iter().map(|align| (Some(SpecAxis::Vertical), align)));
-
- let align = dedup_aligns(ctx, iter);
- let ends_par = align.main != ctx.state.align.main;
- ctx.state.align = align;
-
- if ends_par {
- ctx.end_par_group();
- ctx.start_par_group();
- }
-
- if let Some(body) = body {
- body.eval(ctx);
- ctx.state = snapshot;
- }
-
- Value::None
-}
-
-/// Deduplicate alignments and deduce to which axes they apply.
-fn dedup_aligns(
- ctx: &mut EvalContext,
- iter: impl Iterator<Item = (Option<SpecAxis>, Spanned<AlignArg>)>,
-) -> BoxAlign {
- let mut alignments = ctx.state.align;
- let mut had = Gen::uniform(false);
- let mut had_center = false;
-
- for (axis, Spanned { v: align, span }) in iter {
- // 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.flow);
- let gen_align = align.switch(ctx.state.flow);
-
- if align.axis().map_or(false, |a| a != axis) {
- ctx.diag(error!(
- span,
- "invalid alignment `{}` for {} axis", align, axis,
- ));
- } else if had.get(gen_axis) {
- ctx.diag(error!(span, "duplicate alignment for {} axis", axis));
- } else {
- *alignments.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!(align, AlignArg::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.
- alignments = BoxAlign::new(Align::Center, Align::Center);
- had = Gen::uniform(true);
- } else {
- had_center = true;
- }
- }
-
- // If we we know one alignment, we can handle the unspecified `center`
- // alignment.
- if had_center && (had.main || had.cross) {
- if had.main {
- alignments.cross = Align::Center;
- had.cross = true;
- } else {
- alignments.main = Align::Center;
- had.main = true;
- }
- had_center = false;
- }
- }
-
- // If center has not been flushed by now, it is the only argument and then
- // we default to applying it to the cross axis.
- if had_center {
- alignments.cross = Align::Center;
- }
-
- alignments
-}
-
-/// An alignment argument.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
-enum AlignArg {
- Left,
- Right,
- Top,
- Bottom,
- Center,
-}
-
-impl AlignArg {
- /// The specific axis this alignment refers to.
- ///
- /// Returns `None` if this is `Center` since the axis is unknown.
- pub 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 AlignArg {
- type Other = Align;
-
- fn switch(self, flow: Flow) -> Self::Other {
- let get = |dir: Dir, at_positive_start| {
- if dir.is_positive() == at_positive_start {
- Align::Start
- } else {
- Align::End
- }
- };
-
- let flow = flow.switch(flow);
- match self {
- Self::Left => get(flow.horizontal, true),
- Self::Right => get(flow.horizontal, false),
- Self::Top => get(flow.vertical, true),
- Self::Bottom => get(flow.vertical, false),
- Self::Center => Align::Center,
- }
- }
-}
-
-convert_ident!(AlignArg, "alignment", |v| match v {
- "left" => Some(Self::Left),
- "right" => Some(Self::Right),
- "top" => Some(Self::Top),
- "bottom" => Some(Self::Bottom),
- "center" => Some(Self::Center),
- _ => None,
-});
-
-impl Display for AlignArg {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.pad(match self {
- Self::Left => "left",
- Self::Right => "right",
- Self::Top => "top",
- Self::Bottom => "bottom",
- Self::Center => "center",
- })
- }
-}
diff --git a/src/library/boxed.rs b/src/library/boxed.rs
deleted file mode 100644
index 1ec17d88..00000000
--- a/src/library/boxed.rs
+++ /dev/null
@@ -1,44 +0,0 @@
-use crate::geom::Linear;
-use crate::layout::{Expansion, Fixed, Stack};
-use crate::prelude::*;
-
-/// `box`: Layouts its contents into a box.
-///
-/// # Keyword arguments
-/// - `width`: The width of the box (length or relative to parent's width).
-/// - `height`: The height of the box (length or relative to parent's height).
-pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value {
- let snapshot = ctx.state.clone();
- let body = args.find::<SynTree>().unwrap_or_default();
- let width = args.get::<_, Linear>(ctx, "width");
- let height = args.get::<_, Linear>(ctx, "height");
- let main = args.get::<_, Spanned<Dir>>(ctx, "main");
- let cross = args.get::<_, Spanned<Dir>>(ctx, "cross");
- ctx.set_flow(Gen::new(main, cross));
- args.done(ctx);
-
- let flow = ctx.state.flow;
- let align = ctx.state.align;
-
- ctx.start_content_group();
- body.eval(ctx);
- let children = ctx.end_content_group();
-
- ctx.push(Fixed {
- width,
- height,
- child: LayoutNode::dynamic(Stack {
- flow,
- align,
- expansion: Spec::new(
- Expansion::fill_if(width.is_some()),
- Expansion::fill_if(height.is_some()),
- )
- .switch(flow),
- children,
- }),
- });
-
- ctx.state = snapshot;
- Value::None
-}
diff --git a/src/library/color.rs b/src/library/color.rs
deleted file mode 100644
index 17c33806..00000000
--- a/src/library/color.rs
+++ /dev/null
@@ -1,27 +0,0 @@
-use crate::color::RgbaColor;
-use crate::prelude::*;
-
-/// `rgb`: Create an RGB(A) color.
-pub fn rgb(mut args: Args, ctx: &mut EvalContext) -> Value {
- let r = args.need::<_, Spanned<i64>>(ctx, 0, "red value");
- let g = args.need::<_, Spanned<i64>>(ctx, 1, "green value");
- let b = args.need::<_, Spanned<i64>>(ctx, 2, "blue value");
- let a = args.get::<_, Spanned<i64>>(ctx, 3);
- args.done(ctx);
-
- let mut clamp = |component: Option<Spanned<i64>>, default| {
- component.map_or(default, |c| {
- if c.v < 0 || c.v > 255 {
- ctx.diag(error!(c.span, "should be between 0 and 255"));
- }
- c.v.max(0).min(255) as u8
- })
- };
-
- Value::Color(RgbaColor::new(
- clamp(r, 0),
- clamp(g, 0),
- clamp(b, 0),
- clamp(a, 255),
- ))
-}
diff --git a/src/library/graphics.rs b/src/library/graphics.rs
deleted file mode 100644
index 779d78b5..00000000
--- a/src/library/graphics.rs
+++ /dev/null
@@ -1,42 +0,0 @@
-use std::fs::File;
-use std::io::BufReader;
-
-use image::io::Reader;
-
-use crate::layout::Image;
-use crate::prelude::*;
-
-/// `image`: Include an image.
-///
-/// # Positional arguments
-/// - The path to the image (string)
-pub fn image(mut args: Args, ctx: &mut EvalContext) -> Value {
- let path = args.need::<_, Spanned<String>>(ctx, 0, "path");
- let width = args.get::<_, Linear>(ctx, "width");
- let height = args.get::<_, Linear>(ctx, "height");
-
- if let Some(path) = path {
- if let Ok(file) = File::open(path.v) {
- match Reader::new(BufReader::new(file))
- .with_guessed_format()
- .map_err(|err| err.into())
- .and_then(|reader| reader.decode())
- .map(|img| img.into_rgba8())
- {
- Ok(buf) => {
- ctx.push(Image {
- buf,
- width,
- height,
- align: ctx.state.align,
- });
- }
- Err(err) => ctx.diag(error!(path.span, "invalid image: {}", err)),
- }
- } else {
- ctx.diag(error!(path.span, "failed to open image file"));
- }
- }
-
- Value::None
-}
diff --git a/src/layout/graphics.rs b/src/library/insert.rs
index 1fa05605..db1e9e17 100644
--- a/src/layout/graphics.rs
+++ b/src/library/insert.rs
@@ -1,18 +1,59 @@
use std::fmt::{self, Debug, Formatter};
+use std::fs::File;
+use std::io::BufReader;
-use super::*;
+use image::io::Reader;
+use image::RgbaImage;
+
+use crate::layout::*;
+use crate::prelude::*;
+
+/// `image`: Insert an image.
+///
+/// # Positional arguments
+/// - The path to the image (string)
+pub fn image(mut args: Args, ctx: &mut EvalContext) -> Value {
+ let path = args.need::<_, Spanned<String>>(ctx, 0, "path");
+ let width = args.get::<_, Linear>(ctx, "width");
+ let height = args.get::<_, Linear>(ctx, "height");
+
+ if let Some(path) = path {
+ if let Ok(file) = File::open(path.v) {
+ match Reader::new(BufReader::new(file))
+ .with_guessed_format()
+ .map_err(|err| err.into())
+ .and_then(|reader| reader.decode())
+ .map(|img| img.into_rgba8())
+ {
+ Ok(buf) => {
+ ctx.push(Image {
+ buf,
+ width,
+ height,
+ align: ctx.state.align,
+ });
+ }
+ Err(err) => ctx.diag(error!(path.span, "invalid image: {}", err)),
+ }
+ } else {
+ ctx.diag(error!(path.span, "failed to open image file"));
+ }
+ }
+
+ Value::None
+}
/// An image node.
#[derive(Clone, PartialEq)]
-pub struct Image {
+struct Image {
/// The image.
- pub buf: RgbaImage,
+ buf: RgbaImage,
/// The fixed width, if any.
- pub width: Option<Linear>,
+ width: Option<Linear>,
/// The fixed height, if any.
- pub height: Option<Linear>,
+ height: Option<Linear>,
/// How to align this image node in its parent.
- pub align: BoxAlign,
+ align: BoxAlign,
}
impl Layout for Image {
diff --git a/src/library/layout.rs b/src/library/layout.rs
new file mode 100644
index 00000000..26f94f22
--- /dev/null
+++ b/src/library/layout.rs
@@ -0,0 +1,337 @@
+use std::fmt::{self, Display, Formatter};
+
+use crate::geom::{Length, Linear};
+use crate::layout::{Expansion, Fixed, Softness, Spacing, Stack};
+use crate::paper::{Paper, PaperClass};
+use crate::prelude::*;
+
+/// `align`: Align content along the layouting axes.
+///
+/// # Positional arguments
+/// - At most two of `left`, `right`, `top`, `bottom`, `center`.
+///
+/// When `center` is used as a positional argument, it is automatically inferred
+/// which axis it should apply to depending on further arguments, defaulting
+/// to the cross axis.
+///
+/// # Keyword arguments
+/// - `horizontal`: Any of `left`, `right` or `center`.
+/// - `vertical`: Any of `top`, `bottom` or `center`.
+pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value {
+ let snapshot = ctx.state.clone();
+ let body = args.find::<SynTree>();
+ let first = args.get::<_, Spanned<AlignArg>>(ctx, 0);
+ let second = args.get::<_, Spanned<AlignArg>>(ctx, 1);
+ let hor = args.get::<_, Spanned<AlignArg>>(ctx, "horizontal");
+ let ver = args.get::<_, Spanned<AlignArg>>(ctx, "vertical");
+ args.done(ctx);
+
+ let prev_main = ctx.state.align.main;
+ let mut had = Gen::uniform(false);
+ let mut had_center = false;
+
+ for (axis, Spanned { v: arg, span }) in first
+ .into_iter()
+ .chain(second.into_iter())
+ .map(|arg| (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.flow);
+ let gen_align = arg.switch(ctx.state.flow);
+
+ if arg.axis().map_or(false, |a| a != axis) {
+ ctx.diag(error!(
+ span,
+ "invalid alignment `{}` for {} axis", arg, 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, AlignArg::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 != prev_main {
+ ctx.end_par_group();
+ ctx.start_par_group();
+ }
+
+ if let Some(body) = body {
+ body.eval(ctx);
+ ctx.state = snapshot;
+ }
+
+ Value::None
+}
+
+/// An argument to `[align]`.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
+enum AlignArg {
+ Left,
+ Right,
+ Top,
+ Bottom,
+ Center,
+}
+
+convert_ident!(AlignArg, "alignment", |v| match v {
+ "left" => Some(Self::Left),
+ "right" => Some(Self::Right),
+ "top" => Some(Self::Top),
+ "bottom" => Some(Self::Bottom),
+ "center" => Some(Self::Center),
+ _ => None,
+});
+
+impl AlignArg {
+ /// The specific axis this alignment refers to.
+ ///
+ /// Returns `None` if this is `Center` since the axis is unknown.
+ pub 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 AlignArg {
+ type Other = Align;
+
+ fn switch(self, flow: Flow) -> Self::Other {
+ let get = |dir: Dir, at_positive_start| {
+ if dir.is_positive() == at_positive_start {
+ Align::Start
+ } else {
+ Align::End
+ }
+ };
+
+ let flow = flow.switch(flow);
+ match self {
+ Self::Left => get(flow.horizontal, true),
+ Self::Right => get(flow.horizontal, false),
+ Self::Top => get(flow.vertical, true),
+ Self::Bottom => get(flow.vertical, false),
+ Self::Center => Align::Center,
+ }
+ }
+}
+
+impl Display for AlignArg {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(match self {
+ Self::Left => "left",
+ Self::Right => "right",
+ Self::Top => "top",
+ Self::Bottom => "bottom",
+ Self::Center => "center",
+ })
+ }
+}
+
+/// `box`: Layout content into a box.
+///
+/// # Keyword arguments
+/// - `width`: The width of the box (length or relative to parent's width).
+/// - `height`: The height of the box (length or relative to parent's height).
+pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value {
+ let snapshot = ctx.state.clone();
+ let body = args.find::<SynTree>().unwrap_or_default();
+ let width = args.get::<_, Linear>(ctx, "width");
+ let height = args.get::<_, Linear>(ctx, "height");
+ let main = args.get::<_, Spanned<Dir>>(ctx, "main");
+ let cross = args.get::<_, Spanned<Dir>>(ctx, "cross");
+ ctx.set_flow(Gen::new(main, cross));
+ args.done(ctx);
+
+ let flow = ctx.state.flow;
+ let align = ctx.state.align;
+
+ ctx.start_content_group();
+ body.eval(ctx);
+ let children = ctx.end_content_group();
+
+ ctx.push(Fixed {
+ width,
+ height,
+ child: LayoutNode::dynamic(Stack {
+ flow,
+ align,
+ expansion: Spec::new(
+ Expansion::fill_if(width.is_some()),
+ Expansion::fill_if(height.is_some()),
+ )
+ .switch(flow),
+ children,
+ }),
+ });
+
+ ctx.state = snapshot;
+ Value::None
+}
+
+/// `h`: Add horizontal spacing.
+///
+/// # Positional arguments
+/// - The spacing (length or relative to font size).
+pub fn h(args: Args, ctx: &mut EvalContext) -> Value {
+ spacing(args, ctx, SpecAxis::Horizontal)
+}
+
+/// `v`: Add vertical spacing.
+///
+/// # Positional arguments
+/// - The spacing (length or relative to font size).
+pub fn v(args: Args, ctx: &mut EvalContext) -> Value {
+ spacing(args, ctx, SpecAxis::Vertical)
+}
+
+/// Apply spacing along a specific axis.
+fn spacing(mut args: Args, ctx: &mut EvalContext, axis: SpecAxis) -> Value {
+ let spacing = args.need::<_, Linear>(ctx, 0, "spacing");
+ args.done(ctx);
+
+ if let Some(linear) = spacing {
+ let amount = linear.resolve(ctx.state.font.font_size());
+ let spacing = Spacing { amount, softness: Softness::Hard };
+ if ctx.state.flow.main.axis() == axis {
+ ctx.end_par_group();
+ ctx.push(spacing);
+ ctx.start_par_group();
+ } else {
+ ctx.push(spacing);
+ }
+ }
+
+ Value::None
+}
+
+/// `page`: Configure pages.
+///
+/// # Positional arguments
+/// - The name of a paper, e.g. `a4` (optional).
+///
+/// # Keyword arguments
+/// - `width`: The width of pages (length).
+/// - `height`: The height of pages (length).
+/// - `margins`: The margins for all sides (length or relative to side lengths).
+/// - `left`: The left margin (length or relative to width).
+/// - `right`: The right margin (length or relative to width).
+/// - `top`: The top margin (length or relative to height).
+/// - `bottom`: The bottom margin (length or relative to height).
+/// - `flip`: Flips custom or paper-defined width and height (boolean).
+pub fn page(mut args: Args, ctx: &mut EvalContext) -> Value {
+ let snapshot = ctx.state.clone();
+ let body = args.find::<SynTree>();
+
+ if let Some(paper) = args.find::<Paper>() {
+ ctx.state.page.class = paper.class;
+ ctx.state.page.size = paper.size();
+ }
+
+ if let Some(width) = args.get::<_, Length>(ctx, "width") {
+ ctx.state.page.class = PaperClass::Custom;
+ ctx.state.page.size.width = width;
+ }
+
+ if let Some(height) = args.get::<_, Length>(ctx, "height") {
+ ctx.state.page.class = PaperClass::Custom;
+ ctx.state.page.size.height = height;
+ }
+
+ if let Some(margins) = args.get::<_, Linear>(ctx, "margins") {
+ ctx.state.page.margins = Sides::uniform(Some(margins));
+ }
+
+ if let Some(left) = args.get::<_, Linear>(ctx, "left") {
+ ctx.state.page.margins.left = Some(left);
+ }
+
+ if let Some(top) = args.get::<_, Linear>(ctx, "top") {
+ ctx.state.page.margins.top = Some(top);
+ }
+
+ if let Some(right) = args.get::<_, Linear>(ctx, "right") {
+ ctx.state.page.margins.right = Some(right);
+ }
+
+ if let Some(bottom) = args.get::<_, Linear>(ctx, "bottom") {
+ ctx.state.page.margins.bottom = Some(bottom);
+ }
+
+ if args.get::<_, bool>(ctx, "flip").unwrap_or(false) {
+ let size = &mut ctx.state.page.size;
+ std::mem::swap(&mut size.width, &mut size.height);
+ }
+
+ let main = args.get::<_, Spanned<Dir>>(ctx, "main");
+ let cross = args.get::<_, Spanned<Dir>>(ctx, "cross");
+ ctx.set_flow(Gen::new(main, cross));
+
+ args.done(ctx);
+
+ if let Some(body) = body {
+ ctx.end_page_group();
+ ctx.start_page_group(true);
+ body.eval(ctx);
+ ctx.state = snapshot;
+ }
+
+ ctx.end_page_group();
+ ctx.start_page_group(false);
+
+ Value::None
+}
+
+/// `pagebreak`: Start a new page.
+pub fn pagebreak(args: Args, ctx: &mut EvalContext) -> Value {
+ args.done(ctx);
+ ctx.end_page_group();
+ ctx.start_page_group(true);
+ Value::None
+}
diff --git a/src/library/mod.rs b/src/library/mod.rs
index e59201dc..bd1feebb 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -1,20 +1,12 @@
//! The standard library.
-mod align;
-mod boxed;
-mod color;
-mod font;
-mod graphics;
-mod page;
-mod spacing;
+mod insert;
+mod layout;
+mod style;
-pub use align::*;
-pub use boxed::*;
-pub use color::*;
-pub use font::*;
-pub use graphics::*;
-pub use page::*;
-pub use spacing::*;
+pub use insert::*;
+pub use layout::*;
+pub use style::*;
use crate::eval::{Scope, ValueFunc};
diff --git a/src/library/page.rs b/src/library/page.rs
deleted file mode 100644
index 05709865..00000000
--- a/src/library/page.rs
+++ /dev/null
@@ -1,88 +0,0 @@
-use crate::geom::{Length, Linear};
-use crate::paper::{Paper, PaperClass};
-use crate::prelude::*;
-
-/// `page`: Configure pages.
-///
-/// # Positional arguments
-/// - The name of a paper, e.g. `a4` (optional).
-///
-/// # Keyword arguments
-/// - `width`: The width of pages (length).
-/// - `height`: The height of pages (length).
-/// - `margins`: The margins for all sides (length or relative to side lengths).
-/// - `left`: The left margin (length or relative to width).
-/// - `right`: The right margin (length or relative to width).
-/// - `top`: The top margin (length or relative to height).
-/// - `bottom`: The bottom margin (length or relative to height).
-/// - `flip`: Flips custom or paper-defined width and height (boolean).
-pub fn page(mut args: Args, ctx: &mut EvalContext) -> Value {
- let snapshot = ctx.state.clone();
- let body = args.find::<SynTree>();
-
- if let Some(paper) = args.find::<Paper>() {
- ctx.state.page.class = paper.class;
- ctx.state.page.size = paper.size();
- }
-
- if let Some(width) = args.get::<_, Length>(ctx, "width") {
- ctx.state.page.class = PaperClass::Custom;
- ctx.state.page.size.width = width;
- }
-
- if let Some(height) = args.get::<_, Length>(ctx, "height") {
- ctx.state.page.class = PaperClass::Custom;
- ctx.state.page.size.height = height;
- }
-
- if let Some(margins) = args.get::<_, Linear>(ctx, "margins") {
- ctx.state.page.margins = Sides::uniform(Some(margins));
- }
-
- if let Some(left) = args.get::<_, Linear>(ctx, "left") {
- ctx.state.page.margins.left = Some(left);
- }
-
- if let Some(top) = args.get::<_, Linear>(ctx, "top") {
- ctx.state.page.margins.top = Some(top);
- }
-
- if let Some(right) = args.get::<_, Linear>(ctx, "right") {
- ctx.state.page.margins.right = Some(right);
- }
-
- if let Some(bottom) = args.get::<_, Linear>(ctx, "bottom") {
- ctx.state.page.margins.bottom = Some(bottom);
- }
-
- if args.get::<_, bool>(ctx, "flip").unwrap_or(false) {
- let size = &mut ctx.state.page.size;
- std::mem::swap(&mut size.width, &mut size.height);
- }
-
- let main = args.get::<_, Spanned<Dir>>(ctx, "main");
- let cross = args.get::<_, Spanned<Dir>>(ctx, "cross");
- ctx.set_flow(Gen::new(main, cross));
-
- args.done(ctx);
-
- if let Some(body) = body {
- ctx.end_page_group();
- ctx.start_page_group(true);
- body.eval(ctx);
- ctx.state = snapshot;
- }
-
- ctx.end_page_group();
- ctx.start_page_group(false);
-
- Value::None
-}
-
-/// `pagebreak`: Starts a new page.
-pub fn pagebreak(args: Args, ctx: &mut EvalContext) -> Value {
- args.done(ctx);
- ctx.end_page_group();
- ctx.start_page_group(true);
- Value::None
-}
diff --git a/src/library/spacing.rs b/src/library/spacing.rs
deleted file mode 100644
index d6d0d7b0..00000000
--- a/src/library/spacing.rs
+++ /dev/null
@@ -1,39 +0,0 @@
-use crate::geom::Linear;
-use crate::layout::{Softness, Spacing};
-use crate::prelude::*;
-
-/// `h`: Add horizontal spacing.
-///
-/// # Positional arguments
-/// - The spacing (length or relative to font size).
-pub fn h(args: Args, ctx: &mut EvalContext) -> Value {
- spacing(args, ctx, SpecAxis::Horizontal)
-}
-
-/// `v`: Add vertical spacing.
-///
-/// # Positional arguments
-/// - The spacing (length or relative to font size).
-pub fn v(args: Args, ctx: &mut EvalContext) -> Value {
- spacing(args, ctx, SpecAxis::Vertical)
-}
-
-/// Apply spacing along a specific axis.
-fn spacing(mut args: Args, ctx: &mut EvalContext, axis: SpecAxis) -> Value {
- let spacing = args.need::<_, Linear>(ctx, 0, "spacing");
- args.done(ctx);
-
- if let Some(linear) = spacing {
- let amount = linear.resolve(ctx.state.font.font_size());
- let spacing = Spacing { amount, softness: Softness::Hard };
- if ctx.state.flow.main.axis() == axis {
- ctx.end_par_group();
- ctx.push(spacing);
- ctx.start_par_group();
- } else {
- ctx.push(spacing);
- }
- }
-
- Value::None
-}
diff --git a/src/library/font.rs b/src/library/style.rs
index 5de0a953..bb472536 100644
--- a/src/library/font.rs
+++ b/src/library/style.rs
@@ -2,6 +2,7 @@ use std::rc::Rc;
use fontdock::{FontStretch, FontStyle, FontWeight};
+use crate::color::RgbaColor;
use crate::eval::StringLike;
use crate::geom::Linear;
use crate::prelude::*;
@@ -9,8 +10,14 @@ use crate::prelude::*;
/// `font`: Configure the font.
///
/// # Positional arguments
-/// - The font size (optional, length or relative to previous font size).
-/// - A font family fallback list (optional, identifiers or strings).
+/// - The font size (optional, length or relative to current font size).
+/// - All identifier and string arguments are interpreted as an ordered list of
+/// fallback font families.
+///
+/// An example invocation could look like this:
+/// ```typst
+/// [font: 12pt, Arial, "Noto Sans", sans-serif]
+/// ```
///
/// # Keyword arguments
/// - `style`
@@ -28,7 +35,7 @@ use crate::prelude::*;
/// - `bold` (`700`)
/// - `extrabold` (`800`)
/// - `black` (`900`)
-/// - any integer from the range `100` - `900` (inclusive)
+/// - integer between `100` and `900`
///
/// - `stretch`
/// - `ultra-condensed`
@@ -106,3 +113,34 @@ pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value {
Value::None
}
+
+/// `rgb`: Create an RGB(A) color.
+///
+/// # Positional arguments
+/// - The red component (integer between 0 and 255).
+/// - The green component (integer between 0 and 255).
+/// - The blue component (integer between 0 and 255).
+/// - The alpha component (optional, integer between 0 and 255).
+pub fn rgb(mut args: Args, ctx: &mut EvalContext) -> Value {
+ let r = args.need::<_, Spanned<i64>>(ctx, 0, "red value");
+ let g = args.need::<_, Spanned<i64>>(ctx, 1, "green value");
+ let b = args.need::<_, Spanned<i64>>(ctx, 2, "blue value");
+ let a = args.get::<_, Spanned<i64>>(ctx, 3);
+ args.done(ctx);
+
+ let mut clamp = |component: Option<Spanned<i64>>, default| {
+ component.map_or(default, |c| {
+ if c.v < 0 || c.v > 255 {
+ ctx.diag(error!(c.span, "should be between 0 and 255"));
+ }
+ c.v.max(0).min(255) as u8
+ })
+ };
+
+ Value::Color(RgbaColor::new(
+ clamp(r, 0),
+ clamp(g, 0),
+ clamp(b, 0),
+ clamp(a, 255),
+ ))
+}