diff options
Diffstat (limited to 'src/library')
| -rw-r--r-- | src/library/align.rs | 144 | ||||
| -rw-r--r-- | src/library/image.rs | 18 | ||||
| -rw-r--r-- | src/library/lang.rs | 45 | ||||
| -rw-r--r-- | src/library/mod.rs | 9 | ||||
| -rw-r--r-- | src/library/pad.rs | 6 | ||||
| -rw-r--r-- | src/library/page.rs | 12 | ||||
| -rw-r--r-- | src/library/par.rs | 14 | ||||
| -rw-r--r-- | src/library/shapes.rs | 18 | ||||
| -rw-r--r-- | src/library/spacing.rs | 21 |
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); } }) } |
