summaryrefslogtreecommitdiff
path: root/src/library
diff options
context:
space:
mode:
Diffstat (limited to 'src/library')
-rw-r--r--src/library/align.rs144
-rw-r--r--src/library/image.rs18
-rw-r--r--src/library/lang.rs45
-rw-r--r--src/library/mod.rs9
-rw-r--r--src/library/pad.rs6
-rw-r--r--src/library/page.rs12
-rw-r--r--src/library/par.rs14
-rw-r--r--src/library/shapes.rs18
-rw-r--r--src/library/spacing.rs21
9 files changed, 121 insertions, 166 deletions
diff --git a/src/library/align.rs b/src/library/align.rs
index 765ed988..d5811bf4 100644
--- a/src/library/align.rs
+++ b/src/library/align.rs
@@ -6,11 +6,6 @@ use super::*;
/// - Alignments: variadic, of type `alignment`.
/// - Body: optional, of type `template`.
///
-/// 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.
-///
/// # Named parameters
/// - Horizontal alignment: `horizontal`, of type `alignment`.
/// - Vertical alignment: `vertical`, of type `alignment`.
@@ -21,32 +16,44 @@ use super::*;
///
/// # Relevant types and constants
/// - Type `alignment`
+/// - `start`
+/// - `center`
+/// - `end`
/// - `left`
/// - `right`
/// - `top`
/// - `bottom`
-/// - `center`
pub fn align(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- let first = args.find(ctx);
- let second = args.find(ctx);
- let hor = args.get(ctx, "horizontal");
- let ver = args.get(ctx, "vertical");
+ let first = args.find::<AlignValue>(ctx);
+ let second = args.find::<AlignValue>(ctx);
+ let mut horizontal = args.get::<AlignValue>(ctx, "horizontal");
+ let mut vertical = args.get::<AlignValue>(ctx, "vertical");
let body = args.find::<TemplateValue>(ctx);
+ for value in first.into_iter().chain(second) {
+ match value.axis() {
+ Some(SpecAxis::Horizontal) | None if horizontal.is_none() => {
+ horizontal = Some(value);
+ }
+ Some(SpecAxis::Vertical) | None if vertical.is_none() => {
+ vertical = Some(value);
+ }
+ _ => {}
+ }
+ }
+
Value::template("align", move |ctx| {
let snapshot = ctx.state.clone();
- let values = first
- .into_iter()
- .chain(second.into_iter())
- .map(|arg: Spanned<AlignValue>| (arg.v.axis(), arg))
- .chain(hor.into_iter().map(|arg| (Some(SpecAxis::Horizontal), arg)))
- .chain(ver.into_iter().map(|arg| (Some(SpecAxis::Vertical), arg)));
-
- apply(ctx, values);
+ if let Some(horizontal) = horizontal {
+ ctx.state.aligns.cross = horizontal.to_align(ctx.state.lang.dir);
+ }
- if ctx.state.aligns.main != snapshot.aligns.main {
- ctx.push_linebreak();
+ if let Some(vertical) = vertical {
+ ctx.state.aligns.main = vertical.to_align(Dir::TTB);
+ if ctx.state.aligns.main != snapshot.aligns.main {
+ ctx.push_linebreak();
+ }
}
if let Some(body) = &body {
@@ -56,109 +63,48 @@ pub fn align(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
})
}
-/// Deduplicate and apply the alignments.
-fn apply(
- ctx: &mut ExecContext,
- values: impl Iterator<Item = (Option<SpecAxis>, Spanned<AlignValue>)>,
-) {
- let mut had = Gen::uniform(false);
- let mut had_center = false;
-
- for (axis, Spanned { v: arg, span }) in values {
- // 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.aligns.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, AlignValue::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.aligns.main = Align::Center;
- ctx.state.aligns.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.aligns.cross = Align::Center;
- } else {
- ctx.state.aligns.main = Align::Center;
- }
- had = Gen::uniform(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.aligns.cross = Align::Center;
- }
-}
-
-/// An alignment value.
+/// An alignment specifier passed to `align`.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub(super) enum AlignValue {
- Left,
+ Start,
Center,
+ End,
+ Left,
Right,
Top,
Bottom,
}
impl AlignValue {
- /// The specific axis this alignment refers to.
fn axis(self) -> Option<SpecAxis> {
match self {
+ Self::Start => None,
+ Self::Center => None,
+ Self::End => None,
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 AlignValue {
- type Other = Align;
- fn switch(self, dirs: LayoutDirs) -> Self::Other {
- let get = |dir: Dir, at_positive_start| {
- if dir.is_positive() == at_positive_start {
+ fn to_align(self, dir: Dir) -> Align {
+ let side = |is_at_positive_start| {
+ if dir.is_positive() == is_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::Start => Align::Start,
Self::Center => Align::Center,
+ Self::End => Align::End,
+ Self::Left => side(true),
+ Self::Right => side(false),
+ Self::Top => side(true),
+ Self::Bottom => side(false),
}
}
}
@@ -166,8 +112,10 @@ impl Switch for AlignValue {
impl Display for AlignValue {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
- Self::Left => "left",
+ Self::Start => "start",
Self::Center => "center",
+ Self::End => "end",
+ Self::Left => "left",
Self::Right => "right",
Self::Top => "top",
Self::Bottom => "bottom",
diff --git a/src/library/image.rs b/src/library/image.rs
index 9f39073b..020f7d50 100644
--- a/src/library/image.rs
+++ b/src/library/image.rs
@@ -2,9 +2,7 @@ use ::image::GenericImageView;
use super::*;
use crate::env::{ImageResource, ResourceId};
-use crate::layout::{
- AnyNode, Areas, Element, Fragment, Frame, Image, Layout, LayoutContext,
-};
+use crate::layout::{AnyNode, Areas, Element, Frame, Image, Layout, LayoutContext};
/// `image`: An image.
///
@@ -25,13 +23,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let loaded = ctx.env.resources.load(&path.v, ImageResource::parse);
if let Some((res, img)) = loaded {
let dimensions = img.buf.dimensions();
- ctx.push(ImageNode {
- res,
- dimensions,
- width,
- height,
- aligns: ctx.state.aligns,
- });
+ ctx.push_into_par(ImageNode { res, dimensions, width, height });
} else {
ctx.diag(error!(path.span, "failed to load image"));
}
@@ -42,8 +34,6 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
/// An image node.
#[derive(Debug, Clone, PartialEq)]
struct ImageNode {
- /// How to align this image node in its parent.
- aligns: LayoutAligns,
/// The resource id of the image file.
res: ResourceId,
/// The pixel dimensions of the image.
@@ -55,7 +45,7 @@ struct ImageNode {
}
impl Layout for ImageNode {
- fn layout(&self, _: &mut LayoutContext, areas: &Areas) -> Fragment {
+ fn layout(&self, _: &mut LayoutContext, areas: &Areas) -> Vec<Frame> {
let Areas { current, full, .. } = areas;
let pixel_width = self.dimensions.0 as f64;
@@ -86,7 +76,7 @@ impl Layout for ImageNode {
let mut frame = Frame::new(size);
frame.push(Point::ZERO, Element::Image(Image { res: self.res, size }));
- Fragment::Frame(frame, self.aligns)
+ vec![frame]
}
}
diff --git a/src/library/lang.rs b/src/library/lang.rs
new file mode 100644
index 00000000..79015c7d
--- /dev/null
+++ b/src/library/lang.rs
@@ -0,0 +1,45 @@
+use super::*;
+
+/// `lang`: Configure the language.
+///
+/// # Positional parameters
+/// - Language: of type `string`. Has to be a valid ISO 639-1 code.
+///
+/// # Named parameters
+/// - Text direction: `dir`, of type `direction`, must be horizontal.
+///
+/// # Return value
+/// A template that configures language properties.
+///
+/// # Relevant types and constants
+/// - Type `direction`
+/// - `ltr`
+/// - `rtl`
+pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
+ let iso = args.find::<String>(ctx).map(|s| s.to_ascii_lowercase());
+ let dir = args.get::<Spanned<Dir>>(ctx, "dir");
+
+ Value::template("lang", move |ctx| {
+ if let Some(iso) = &iso {
+ ctx.state.lang.dir = lang_dir(iso);
+ }
+
+ if let Some(dir) = dir {
+ if dir.v.axis() == SpecAxis::Horizontal {
+ ctx.state.lang.dir = dir.v;
+ } else {
+ ctx.diag(error!(dir.span, "must be horizontal"));
+ }
+ }
+
+ ctx.push_parbreak();
+ })
+}
+
+/// The default direction for the language identified by `iso`.
+fn lang_dir(iso: &str) -> Dir {
+ match iso {
+ "ar" | "he" | "fa" | "ur" | "ps" | "yi" => Dir::RTL,
+ "en" | "fr" | "de" | _ => Dir::LTR,
+ }
+}
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 1f412cd0..9c2a661a 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -7,6 +7,7 @@ mod align;
mod base;
mod font;
mod image;
+mod lang;
mod markup;
mod pad;
mod page;
@@ -18,6 +19,7 @@ pub use self::image::*;
pub use align::*;
pub use base::*;
pub use font::*;
+pub use lang::*;
pub use markup::*;
pub use pad::*;
pub use page::*;
@@ -31,7 +33,7 @@ use fontdock::{FontStyle, FontWeight};
use crate::eval::{AnyValue, FuncValue, Scope};
use crate::eval::{EvalContext, FuncArgs, TemplateValue, Value};
-use crate::exec::{Exec, ExecContext, FontFamily};
+use crate::exec::{Exec, FontFamily};
use crate::font::VerticalFontMetric;
use crate::geom::*;
use crate::syntax::{Node, Spanned};
@@ -67,6 +69,7 @@ pub fn _new() -> Scope {
func!("font", font);
func!("h", h);
func!("image", image);
+ func!("lang", lang);
func!("pad", pad);
func!("page", page);
func!("pagebreak", pagebreak);
@@ -79,8 +82,10 @@ pub fn _new() -> Scope {
func!("v", v);
// Constants.
- constant!("left", AlignValue::Left);
+ constant!("start", AlignValue::Start);
constant!("center", AlignValue::Center);
+ constant!("end", AlignValue::End);
+ constant!("left", AlignValue::Left);
constant!("right", AlignValue::Right);
constant!("top", AlignValue::Top);
constant!("bottom", AlignValue::Bottom);
diff --git a/src/library/pad.rs b/src/library/pad.rs
index 5a685d2a..d6b69007 100644
--- a/src/library/pad.rs
+++ b/src/library/pad.rs
@@ -31,9 +31,7 @@ pub fn pad(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
);
Value::template("pad", move |ctx| {
- let snapshot = ctx.state.clone();
- let child = ctx.exec(&body).into();
- ctx.push(PadNode { padding, child });
- ctx.state = snapshot;
+ let child = ctx.exec_group(&body).into();
+ ctx.push_into_par(PadNode { padding, child });
})
}
diff --git a/src/library/page.rs b/src/library/page.rs
index 89722ba3..fb3542ed 100644
--- a/src/library/page.rs
+++ b/src/library/page.rs
@@ -17,19 +17,10 @@ use crate::paper::{Paper, PaperClass};
/// - 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`.
///
/// # Return value
/// A template that configures page properties. The effect is scoped to the body
/// if present.
-///
-/// # Relevant types and constants
-/// - Type `direction`
-/// - `ltr` (left to right)
-/// - `rtl` (right to left)
-/// - `ttb` (top to bottom)
-/// - `btt` (bottom to top)
pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let paper = args.find::<Spanned<String>>(ctx).and_then(|name| {
Paper::from_name(&name.v).or_else(|| {
@@ -46,8 +37,6 @@ pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
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::<TemplateValue>(ctx);
let span = args.span;
@@ -94,7 +83,6 @@ pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
std::mem::swap(&mut page.size.width, &mut page.size.height);
}
- ctx.set_dirs(Gen::new(main, cross));
ctx.finish_page(false, true, span);
if let Some(body) = &body {
diff --git a/src/library/par.rs b/src/library/par.rs
index 0467af44..cf2549bf 100644
--- a/src/library/par.rs
+++ b/src/library/par.rs
@@ -2,26 +2,19 @@ use super::*;
/// `par`: Configure paragraphs.
///
-/// # Positional parameters
-/// - Body: optional, of type `template`.
-///
/// # Named parameters
/// - Paragraph spacing: `spacing`, of type `linear` relative to current font size.
/// - Line leading: `leading`, of type `linear` relative to current font size.
/// - Word spacing: `word-spacing`, of type `linear` relative to current font size.
///
/// # Return value
-/// A template that configures paragraph properties. The effect is scoped to the
-/// body if present.
+/// A template that configures paragraph properties.
pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let spacing = args.get(ctx, "spacing");
let leading = args.get(ctx, "leading");
let word_spacing = args.get(ctx, "word-spacing");
- let body = args.find::<TemplateValue>(ctx);
Value::template("par", move |ctx| {
- let snapshot = ctx.state.clone();
-
if let Some(spacing) = spacing {
ctx.state.par.spacing = spacing;
}
@@ -35,10 +28,5 @@ pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
}
ctx.push_parbreak();
-
- if let Some(body) = &body {
- body.exec(ctx);
- ctx.state = snapshot;
- }
})
}
diff --git a/src/library/shapes.rs b/src/library/shapes.rs
index 9f705ef7..6f9e6677 100644
--- a/src/library/shapes.rs
+++ b/src/library/shapes.rs
@@ -59,21 +59,18 @@ fn rect_impl(
body: TemplateValue,
) -> Value {
Value::template(name, move |ctx| {
- let snapshot = ctx.state.clone();
- let child = ctx.exec(&body).into();
+ let child = ctx.exec_group(&body).into();
let node = FixedNode { width, height, aspect, child };
if let Some(color) = fill {
- ctx.push(BackgroundNode {
+ ctx.push_into_par(BackgroundNode {
shape: BackgroundShape::Rect,
fill: Fill::Color(color),
child: node.into(),
});
} else {
- ctx.push(node);
+ ctx.push_into_par(node);
}
-
- ctx.state = snapshot;
})
}
@@ -136,8 +133,7 @@ fn ellipse_impl(
// perfectly into the ellipse.
const PAD: f64 = 0.5 - SQRT_2 / 4.0;
- let snapshot = ctx.state.clone();
- let child = ctx.exec(&body).into();
+ let child = ctx.exec_group(&body).into();
let node = FixedNode {
width,
height,
@@ -150,15 +146,13 @@ fn ellipse_impl(
};
if let Some(color) = fill {
- ctx.push(BackgroundNode {
+ ctx.push_into_par(BackgroundNode {
shape: BackgroundShape::Ellipse,
fill: Fill::Color(color),
child: node.into(),
});
} else {
- ctx.push(node);
+ ctx.push_into_par(node);
}
-
- ctx.state = snapshot;
})
}
diff --git a/src/library/spacing.rs b/src/library/spacing.rs
index d4648566..6a67a653 100644
--- a/src/library/spacing.rs
+++ b/src/library/spacing.rs
@@ -1,5 +1,4 @@
use super::*;
-use crate::layout::SpacingNode;
/// `h`: Horizontal spacing.
///
@@ -9,7 +8,7 @@ use crate::layout::SpacingNode;
/// # Return value
/// A template that inserts horizontal spacing.
pub fn h(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- spacing_impl(ctx, args, SpecAxis::Horizontal)
+ spacing_impl("h", ctx, args, GenAxis::Cross)
}
/// `v`: Vertical spacing.
@@ -20,20 +19,20 @@ pub fn h(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
/// # Return value
/// A template that inserts vertical spacing.
pub fn v(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- spacing_impl(ctx, args, SpecAxis::Vertical)
+ spacing_impl("v", ctx, args, GenAxis::Main)
}
-fn spacing_impl(ctx: &mut EvalContext, args: &mut FuncArgs, axis: SpecAxis) -> Value {
+fn spacing_impl(
+ name: &str,
+ ctx: &mut EvalContext,
+ args: &mut FuncArgs,
+ axis: GenAxis,
+) -> Value {
let spacing: Option<Linear> = args.require(ctx, "spacing");
- Value::template("spacing", move |ctx| {
+ Value::template(name, move |ctx| {
if let Some(linear) = spacing {
let amount = linear.resolve(ctx.state.font.resolve_size());
- let spacing = SpacingNode { amount, softness: 0 };
- if axis == ctx.state.dirs.main.axis() {
- ctx.push_into_stack(spacing);
- } else {
- ctx.push(spacing);
- }
+ ctx.push_spacing(axis, amount, 0);
}
})
}