diff --git a/src/export/pdf.rs b/src/export/pdf.rs index c70fdcc04..8eca03748 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -261,7 +261,7 @@ impl<'d, W: Write> ExportProcess<'d, W> { text.tj(self.fonts[active_font.0].encode_text(&string)?); }, - LayoutAction::DebugBox(_, _) => {} + LayoutAction::DebugBox(_) => {} } } diff --git a/src/layout/actions.rs b/src/layout/actions.rs index a8abf9f0b..01abc0baa 100644 --- a/src/layout/actions.rs +++ b/src/layout/actions.rs @@ -15,8 +15,7 @@ pub enum LayoutAction { /// Write text starting at the current position. WriteText(String), /// Visualize a box for debugging purposes. - /// The arguments are position and size. - DebugBox(Size2D, Size2D), + DebugBox(Size2D), } impl Serialize for LayoutAction { @@ -25,14 +24,7 @@ impl Serialize for LayoutAction { MoveAbsolute(s) => write!(f, "m {:.4} {:.4}", s.x.to_pt(), s.y.to_pt()), SetFont(i, s) => write!(f, "f {} {}", i, s.to_pt()), WriteText(s) => write!(f, "w {}", s), - DebugBox(p, s) => write!( - f, - "b {} {} {} {}", - p.x.to_pt(), - p.y.to_pt(), - s.x.to_pt(), - s.y.to_pt() - ), + DebugBox(s) => write!(f, "b {} {}", s.x.to_pt(), s.y.to_pt()), } } } @@ -44,7 +36,7 @@ impl Display for LayoutAction { MoveAbsolute(s) => write!(f, "move {} {}", s.x, s.y), SetFont(i, s) => write!(f, "font {} {}", i, s), WriteText(s) => write!(f, "write \"{}\"", s), - DebugBox(p, s) => write!(f, "box {} {}", p, s), + DebugBox(s) => write!(f, "box {}", s), } } } @@ -87,8 +79,6 @@ impl LayoutActions { pub fn add(&mut self, action: LayoutAction) { match action { MoveAbsolute(pos) => self.next_pos = Some(self.origin + pos), - DebugBox(pos, size) => self.actions.push(DebugBox(self.origin + pos, size)), - SetFont(index, size) => { self.next_font = Some((index, size)); } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 7f9b9b950..c34d881e2 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -248,6 +248,16 @@ impl Axis { } } + /// The inverse axis. + pub fn inv(&self) -> Axis { + match self { + Axis::LeftToRight => Axis::RightToLeft, + Axis::RightToLeft => Axis::LeftToRight, + Axis::TopToBottom => Axis::BottomToTop, + Axis::BottomToTop => Axis::TopToBottom, + } + } + /// The direction factor for this axis. /// /// - 1 if the axis is positive. @@ -363,7 +373,7 @@ enum LastSpacing { } impl LastSpacing { - #[allow(dead_code)] + /// The size of the soft space if this is a soft space or zero otherwise. fn soft_or_zero(&self) -> Size { match self { LastSpacing::Soft(space, _) => *space, @@ -372,18 +382,6 @@ impl LastSpacing { } } -/// The specialized anchor position for an item with the given alignment in a -/// container with a given size along the given axis. -#[allow(dead_code)] -fn anchor(axis: Axis, size: Size, alignment: Alignment) -> Size { - use Alignment::*; - match (axis.is_positive(), alignment) { - (true, Origin) | (false, End) => Size::zero(), - (_, Center) => size / 2, - (true, End) | (false, Origin) => size, - } -} - /// Layout components that can be serialized. pub trait Serialize { /// Serialize the data structure into an output writable. diff --git a/src/layout/stack.rs b/src/layout/stack.rs index 3f9af3500..b11aee790 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -37,7 +37,7 @@ struct Space { /// Whether to add the layout for this space even if it would be empty. hard: bool, /// The so-far accumulated subspaces. - spaces: Vec, + subs: Vec, } /// A part of a space with fixed axes and secondary alignment. @@ -88,8 +88,7 @@ impl StackLayouter { /// Add a layout to the stack. pub fn add(&mut self, layout: Layout) -> LayoutResult<()> { if layout.alignment.secondary != self.sub.alignment { - // self.finish_subspace(); - // finish sub and start new with layout's alignment + self.finish_subspace(layout.alignment.secondary); } // Add a cached soft space if there is one. @@ -174,13 +173,13 @@ impl StackLayouter { } } - /// Change the layouting axis used by this layouter. + /// Change the layouting axes used by this layouter. /// /// This starts a new subspace (if the axes are actually different from the /// current ones). pub fn set_axes(&mut self, axes: LayoutAxes) { if axes != self.ctx.axes { - self.finish_subspace(); + self.finish_subspace(Alignment::Origin); let (origin, usable) = self.remaining_subspace(); self.sub = Subspace::new(axes, Alignment::Origin, origin, usable); @@ -226,9 +225,7 @@ impl StackLayouter { /// Whether the current layout space (not subspace) is empty. pub fn space_is_empty(&self) -> bool { - self.sub.layouts.is_empty() - && self.sub.size == Size2D::zero() - && self.space.spaces.is_empty() + self.subspace_is_empty() && self.space.subs.is_empty() } /// Whether the current layout space is the last is the followup list. @@ -244,12 +241,127 @@ impl StackLayouter { self.layouts } - pub fn finish_space(&mut self, _hard: bool) { - unimplemented!() - } + /// Finish the current space and start a new one. + pub fn finish_space(&mut self, hard: bool) { + self.finish_subspace(Alignment::Origin); - fn finish_subspace(&mut self) { - unimplemented!() + println!(); + println!("FINISHING SPACE:"); + println!(); + + let space = self.ctx.spaces[self.space.index]; + let mut subs = std::mem::replace(&mut self.space.subs, vec![]); + + // --------------------------------------------------------------------- + // Compute the size of the whole space. + let usable = space.usable(); + let mut max = Size2D { + x: if space.expand.0 { usable.x } else { Size::zero() }, + y: if space.expand.1 { usable.y } else { Size::zero() }, + }; + + // The total size is determined by the maximum position + extent of one + // of the boxes. + for sub in &subs { + max.max_eq(sub.origin + sub.axes.specialize(sub.size)); + } + + let dimensions = max.padded(space.padding); + + println!("WITH DIMENSIONS: {}", dimensions); + + println!("SUBS: {:#?}", subs); + + // --------------------------------------------------------------------- + // Justify the boxes according to their alignment and give each box + // the appropriate origin and usable space. + + // use Alignment::*; + + for sub in &mut subs { + // The usable width should not exceed the total usable width + // (previous value) or the maximum width of the layout as a whole. + sub.usable.x = crate::size::min( + sub.usable.x, + sub.axes.specialize(max - sub.origin).x, + ); + + sub.usable.y = sub.size.y; + } + + // if space.expand.1 { + // let height = subs.iter().map(|sub| sub.size.y).sum(); + // let centers = subs.iter() + // .filter(|sub| sub.alignment == Alignment::Center) + // .count() + // .max(1); + + // let grow = max.y - height; + // let center_grow = grow / (centers as i32); + + // println!("center grow = {}", center_grow); + + // let mut offset = Size::zero(); + // for sub in &mut subs { + // sub.origin.y += offset; + // if sub.alignment == Center { + // sub.usable.y += center_grow; + // offset += center_grow; + // } + // } + + // if let Some(last) = subs.last_mut() { + // last.usable.y += grow - offset; + // } + // } + + // --------------------------------------------------------------------- + // Do the thing + + // Add a debug box with this boxes size. + let mut actions = LayoutActions::new(); + actions.add(LayoutAction::DebugBox(dimensions)); + + for sub in subs { + let LayoutAxes { primary, secondary } = sub.axes; + + // The factor is +1 if the axis is positive and -1 otherwise. + let factor = sub.axes.secondary.factor(); + + // The anchor is the position of the origin-most point of the + // layout. + let anchor = + sub.usable.y.anchor(sub.alignment, secondary.is_positive()) + - factor * sub.size.y.anchor(sub.alignment, true); + + for entry in sub.layouts { + let layout = entry.layout; + let alignment = layout.alignment.primary; + let size = sub.axes.generalize(layout.dimensions); + + let x = + sub.usable.x.anchor(alignment, primary.is_positive()) + - size.x.anchor(alignment, primary.is_positive()); + + let y = anchor + + factor * entry.offset + - size.y.anchor(Alignment::Origin, secondary.is_positive()); + + let pos = sub.origin + sub.axes.specialize(Size2D::new(x, y)); + actions.add_layout(pos, layout); + } + } + + // --------------------------------------------------------------------- + + self.layouts.push(Layout { + dimensions, + baseline: None, + alignment: self.ctx.alignment, + actions: actions.to_vec(), + }); + + self.start_space(self.next_space(), hard); } /// Start a new space with the given index. @@ -263,14 +375,46 @@ impl StackLayouter { self.sub = Subspace::new(axes, Alignment::Origin, space.start(), space.usable()); } - /// The remaining sub - fn remaining_subspace(&self) -> (Size2D, Size2D) { - unimplemented!() - } - + /// The index of the next space. fn next_space(&self) -> usize { (self.space.index + 1).min(self.ctx.spaces.len() - 1) } + + /// Finish the current subspace. + fn finish_subspace(&mut self, new_alignment: Alignment) { + let empty = self.subspace_is_empty(); + + let axes = self.ctx.axes; + let (origin, usable) = self.remaining_subspace(); + let new_sub = Subspace::new(axes, new_alignment, origin, usable); + let sub = std::mem::replace(&mut self.sub, new_sub); + + if !empty { + self.space.subs.push(sub); + } + } + + /// The remaining sub + fn remaining_subspace(&self) -> (Size2D, Size2D) { + let offset = self.sub.size.y + self.sub.last_spacing.soft_or_zero(); + + let new_origin = self.sub.origin + match self.ctx.axes.secondary.is_positive() { + true => self.ctx.axes.specialize(Size2D::with_y(offset)), + false => Size2D::zero(), + }; + + let new_usable = self.ctx.axes.specialize(Size2D { + x: self.sub.usable.x, + y: self.sub.usable.y - offset, + }); + + (new_origin, new_usable) + } + + /// Whether the current layout space (not subspace) is empty. + fn subspace_is_empty(&self) -> bool { + self.sub.layouts.is_empty() && self.sub.size == Size2D::zero() + } } impl Space { @@ -278,7 +422,7 @@ impl Space { Space { index, hard, - spaces: vec![], + subs: vec![], } } } diff --git a/src/layout/tree.rs b/src/layout/tree.rs index f36516c1a..94a50eea0 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -11,7 +11,7 @@ pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult { ctx: LayoutContext<'a, 'p>, - flex: FlexLayouter, + stack: StackLayouter, style: LayoutStyle, } @@ -19,11 +19,10 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { /// Create a new syntax tree layouter. fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> { TreeLayouter { - flex: FlexLayouter::new(FlexContext { + stack: StackLayouter::new(StackContext { spaces: ctx.spaces.clone(), axes: ctx.axes, alignment: ctx.alignment, - flex_spacing: flex_spacing(&ctx.style.text), }), style: ctx.style.clone(), ctx, @@ -56,25 +55,22 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { alignment: self.ctx.alignment, })?; - Ok(self.flex.add(layout)) + self.stack.add(layout) } fn layout_space(&mut self) { - self.flex.add_primary_space( - word_spacing(&self.style.text), - SPACE_KIND, - ); + } fn layout_paragraph(&mut self) -> LayoutResult<()> { - self.flex.add_secondary_space( + Ok(self.stack.add_spacing( paragraph_spacing(&self.style.text), PARAGRAPH_KIND, - ) + )) } fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> { - let spaces = self.flex.remaining(); + let spaces = self.stack.remaining(); let commands = func.0.layout(LayoutContext { loader: self.ctx.loader, @@ -97,16 +93,16 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { match command { LayoutTree(tree) => self.layout(tree)?, - Add(layout) => self.flex.add(layout), - AddMultiple(layouts) => self.flex.add_multiple(layouts), + Add(layout) => self.stack.add(layout)?, + AddMultiple(layouts) => self.stack.add_multiple(layouts)?, AddSpacing(space, kind, axis) => match axis { - GenericAxisKind::Primary => self.flex.add_primary_space(space, kind), - GenericAxisKind::Secondary => self.flex.add_secondary_space(space, kind)?, + GenericAxisKind::Primary => {}, + GenericAxisKind::Secondary => self.stack.add_spacing(space, kind), } - FinishLine => self.flex.add_break(), - FinishRun => { self.flex.finish_run()?; }, - FinishSpace => self.flex.finish_space(true)?, + FinishLine => {}, + FinishRun => {}, + FinishSpace => self.stack.finish_space(true), BreakParagraph => self.layout_paragraph()?, SetTextStyle(style) => self.style.text = style, @@ -116,7 +112,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { } self.style.page = style; - self.flex.set_spaces(smallvec![ + self.stack.set_spaces(smallvec![ LayoutSpace { dimensions: style.dimensions, padding: style.margins, @@ -126,7 +122,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { } SetAlignment(alignment) => self.ctx.alignment = alignment, SetAxes(axes) => { - self.flex.set_axes(axes); + self.stack.set_axes(axes); self.ctx.axes = axes; } } @@ -135,7 +131,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { } fn finish(self) -> LayoutResult { - self.flex.finish() + Ok(self.stack.finish()) } } diff --git a/src/lib.rs b/src/lib.rs index a7bc38a9e..f2ebd5724 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,8 @@ //! format. Submodules for these formats are located in the [export](crate::export) //! module. Currently, the only supported output format is _PDF_. +#![allow(unused)] + pub extern crate toddle; use std::cell::RefCell; diff --git a/src/library/boxed.rs b/src/library/boxed.rs index 3ec9d0016..d3d5b591e 100644 --- a/src/library/boxed.rs +++ b/src/library/boxed.rs @@ -6,7 +6,7 @@ function! { #[derive(Debug, PartialEq)] pub struct Boxed { body: SyntaxTree, - map: ExtentMap, + map: ExtentMap, } parse(args, body, ctx) { @@ -17,7 +17,18 @@ function! { } layout(self, mut ctx) { - self.map.apply(ctx.axes, &mut ctx.spaces[0].dimensions)?; + use SpecificAxisKind::*; + + let space = &mut ctx.spaces[0]; + self.map.apply_with(ctx.axes, |axis, p| { + let entity = match axis { + Horizontal => { space.expand.0 = true; &mut space.dimensions.x }, + Vertical => { space.expand.1 = true; &mut space.dimensions.y }, + }; + + *entity = p.concretize(*entity) + }); + vec![AddMultiple(layout_tree(&self.body, ctx)?)] } } diff --git a/src/library/direction.rs b/src/library/direction.rs new file mode 100644 index 000000000..3b9956d1a --- /dev/null +++ b/src/library/direction.rs @@ -0,0 +1,60 @@ +use crate::func::prelude::*; +use super::maps::ConsistentMap; +use super::keys::AxisKey; + +function! { + /// `direction`: Sets the directions for the layouting axes. + #[derive(Debug, PartialEq)] + pub struct Direction { + body: Option, + map: ConsistentMap, + } + + parse(args, body, ctx) { + let mut map = ConsistentMap::new(); + + map.add_opt_span(AxisKey::Primary, args.get_pos_opt::()?)?; + map.add_opt_span(AxisKey::Secondary, args.get_pos_opt::()?)?; + + for arg in args.keys() { + let axis = AxisKey::from_ident(&arg.v.key)?; + let value = Axis::from_expr(arg.v.value)?; + + map.add(axis, value)?; + } + + Direction { + body: parse!(optional: body, ctx), + map, + } + } + + layout(self, mut ctx) { + let axes = ctx.axes; + + let map = self.map.dedup(|key, &direction| { + Ok((match key { + AxisKey::Primary => GenericAxisKind::Primary, + AxisKey::Secondary => GenericAxisKind::Secondary, + AxisKey::Horizontal => axes.horizontal(), + AxisKey::Vertical => axes.vertical(), + }, direction)) + })?; + + map.with(GenericAxisKind::Primary, |&val| ctx.axes.primary = val); + map.with(GenericAxisKind::Secondary, |&val| ctx.axes.secondary = val); + + if ctx.axes.primary.is_horizontal() == ctx.axes.secondary.is_horizontal() { + error!( + "aligned primary and secondary axes: `{}`, `{}`", + format!("{:?}", ctx.axes.primary).to_lowercase(), + format!("{:?}", ctx.axes.secondary).to_lowercase(), + ); + } + + match &self.body { + Some(body) => vec![AddMultiple(layout_tree(&body, ctx)?)], + None => vec![Command::SetAxes(ctx.axes)], + } + } +} diff --git a/src/library/keys.rs b/src/library/keys.rs index e74027ec5..c7e34839e 100644 --- a/src/library/keys.rs +++ b/src/library/keys.rs @@ -66,7 +66,7 @@ kind!(AxisKey, "axis", "secondary" => AxisKey::Secondary, ); -/// An argument key which identifies a target alignment. +/// An argument key which describes a target alignment. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum AlignmentKey { Left, @@ -172,3 +172,10 @@ kind!(PaddingKey, "axis or side", "vertical-origin" => PaddingKey::AxisAligned(AxisKey::Vertical, AlignmentKey::Origin), "vertical-end" => PaddingKey::AxisAligned(AxisKey::Vertical, AlignmentKey::End), ); + +kind!(Axis, "direction", + "ltr" => Axis::LeftToRight, + "rtl" => Axis::RightToLeft, + "ttb" => Axis::TopToBottom, + "btt" => Axis::BottomToTop, +); diff --git a/src/library/maps.rs b/src/library/maps.rs index 01bde38b4..c89d46cb8 100644 --- a/src/library/maps.rs +++ b/src/library/maps.rs @@ -72,14 +72,14 @@ impl ConsistentMap where K: Hash + Eq { /// A map for storing extents along axes. #[derive(Debug, Clone, PartialEq)] -pub struct ExtentMap(ConsistentMap); +pub struct ExtentMap(ConsistentMap); -impl ExtentMap { +impl ExtentMap { /// Parse an extent map from the function args. /// /// If `enforce` is true other arguments will create an error, otherwise /// they are left intact. - pub fn new(args: &mut FuncArgs, enforce: bool) -> ParseResult { + pub fn new(args: &mut FuncArgs, enforce: bool) -> ParseResult> { let mut map = ConsistentMap::new(); for arg in args.keys() { @@ -96,22 +96,38 @@ impl ExtentMap { } }; - let size = Size::from_expr(arg.v.value)?; - map.add(key, size)?; + let e = E::from_expr(arg.v.value)?; + map.add(key, e)?; } Ok(ExtentMap(map)) } - /// Map from any axis key to the specific axis kind. - pub fn apply(&self, axes: LayoutAxes, dimensions: &mut Size2D) -> LayoutResult<()> { - let map = self.0.dedup(|key, &val| Ok((key.specific(axes), val)))?; - - map.with(SpecificAxisKind::Horizontal, |&val| dimensions.x = val); - map.with(SpecificAxisKind::Vertical, |&val| dimensions.y = val); - + /// Apply the extents on the dimensions. + pub fn apply( + &self, + axes: LayoutAxes, + dimensions: &mut Size2D, + size: F + ) -> LayoutResult<()> where F: Fn(&E) -> Size { + let map = self.dedup(axes)?; + map.with(SpecificAxisKind::Horizontal, |val| dimensions.x = size(val)); + map.with(SpecificAxisKind::Vertical, |val| dimensions.y = size(val)); Ok(()) } + + /// Map from any axis key to the specific axis kind. + pub fn apply_with(&self, axes: LayoutAxes, mut f: F) -> LayoutResult<()> + where F: FnMut(SpecificAxisKind, &E) { + for (&key, value) in self.dedup(axes)?.iter() { + f(key, value); + } + Ok(()) + } + + fn dedup(&self, axes: LayoutAxes) -> LayoutResult> { + self.0.dedup(|key, &val| Ok((key.specific(axes), val))) + } } /// A map for storing padding at sides. diff --git a/src/library/mod.rs b/src/library/mod.rs index 293f95891..4439f1376 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -8,6 +8,7 @@ use maps::*; pub_use_mod!(align); pub_use_mod!(boxed); +pub_use_mod!(direction); pub mod maps; pub mod keys; @@ -19,6 +20,7 @@ pub fn std() -> Scope { std.add::("align"); std.add::("box"); + std.add::("direction"); std.add::("page.size"); std.add::("page.margins"); @@ -78,7 +80,7 @@ function! { /// `page.size`: Set the size of pages. #[derive(Debug, PartialEq)] pub struct PageSize { - map: ExtentMap, + map: ExtentMap, } parse(args, body) { @@ -90,7 +92,7 @@ function! { layout(self, ctx) { let mut style = ctx.style.page; - self.map.apply(ctx.axes, &mut style.dimensions)?; + self.map.apply(ctx.axes, &mut style.dimensions, |&s| s)?; vec![SetPageStyle(style)] } } @@ -150,11 +152,7 @@ function! { layout(self, ctx) { let axis = self.axis.generic(ctx.axes); - let spacing = match self.spacing { - FSize::Absolute(size) => size, - FSize::Scaled(scale) => scale * ctx.style.text.font_size, - }; - + let spacing = self.spacing.concretize(ctx.style.text.font_size); vec![AddSpacing(spacing, SpacingKind::Hard, axis)] } } diff --git a/src/size.rs b/src/size.rs index 4c8278ab6..4a81a65de 100644 --- a/src/size.rs +++ b/src/size.rs @@ -6,6 +6,8 @@ use std::iter::Sum; use std::ops::*; use std::str::FromStr; +use crate::layout::Alignment; + /// A general space type. #[derive(Copy, Clone, PartialEq)] pub struct Size { @@ -80,6 +82,18 @@ impl Size { pub fn max_eq(&mut self, other: Size) { *self = max(*self, other); } + + /// The specialized anchor position for an item with the given alignment in a + /// container with a given size along the given axis. + pub fn anchor(&self, alignment: Alignment, positive: bool) -> Size { + use Alignment::*; + match (positive, alignment) { + (true, Origin) | (false, End) => Size::zero(), + (_, Center) => *self / 2, + (true, End) | (false, Origin) => *self, + } + } + } impl Size2D { @@ -178,6 +192,16 @@ impl SizeBox { } } +impl ScaleSize { + /// Use the absolute value or scale the entity. + pub fn concretize(&self, entity: Size) -> Size { + match self { + ScaleSize::Absolute(s) => *s, + ScaleSize::Scaled(s) => *s * entity, + } + } +} + /// The maximum of two sizes. pub fn max(a: Size, b: Size) -> Size { if a >= b { a } else { b } @@ -192,7 +216,7 @@ pub fn min(a: Size, b: Size) -> Size { impl Display for Size { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}pt", self.points) + write!(f, "{}cm", self.to_cm()) } } diff --git a/tests/layout.rs b/tests/layout.rs index 33c12a2b5..cca0dee0d 100644 --- a/tests/layout.rs +++ b/tests/layout.rs @@ -23,10 +23,7 @@ fn main() -> Result<()> { create_dir_all("tests/cache/pdf")?; let tests: Vec<_> = read_dir("tests/layouts/")?.collect(); - - let len = tests.len(); - println!(); - println!("Running {} test{}", len, if len > 1 { "s" } else { "" }); + let mut filtered = Vec::new(); for entry in tests { let path = entry?.path(); @@ -36,14 +33,23 @@ fn main() -> Result<()> { let name = path .file_stem().ok_or("expected file stem")? - .to_string_lossy(); + .to_string_lossy() + .to_string(); if opts.matches(&name) { let src = read_to_string(&path)?; - panic::catch_unwind(|| test(&name, &src)).ok(); + filtered.push((name, src)); } } + let len = filtered.len(); + println!(); + println!("Running {} test{}", len, if len > 1 { "s" } else { "" }); + + for (name, src) in filtered { + panic::catch_unwind(|| test(&name, &src)).ok(); + } + println!(); Ok(()) @@ -79,6 +85,7 @@ fn test(name: &str, src: &str) -> Result<()> { }); } } + drop(loader); // Write the serialized layout file. let path = format!("tests/cache/serial/{}", name); diff --git a/tests/layouts/test.typ b/tests/layouts/test.typ new file mode 100644 index 000000000..063a7bfe6 --- /dev/null +++ b/tests/layouts/test.typ @@ -0,0 +1,31 @@ +[page.size: w=5cm, h=5cm] +[page.margins: 0cm] + +// [box: w=4cm, h=3cm][1] +// // +// [direction: ttb, ltr] +// [box: w=2cm, h=1][2] +// // +// [direction: btt, rtl] +// [align: bottom, right] +// [box: w=3cm, h=1][3] +// // +// [direction: ltr, ttb] +// [align: center, center] +// [box: w=2cm, h=2cm][4] + +[align: center] + +//[direction: primary=btt, secondary=rtl] +//[align: primary=bottom, secondary=right] +//[box][Hi] + +[box][ +//[align: primary=center, secondary=bottom] +[direction: secondary=btt] +Blabla +[v: 0.5cm] +[align: vertical=end] Origin 2] +//[align: vertical=center] Center +//[align: vertical=center] Center +//[align: vertical=end] End End End diff --git a/tests/render.py b/tests/render.py index 07a9b5b16..2e105eb27 100644 --- a/tests/render.py +++ b/tests/render.py @@ -16,7 +16,7 @@ def main(): assert len(sys.argv) == 2, 'usage: python render.py ' name = sys.argv[1] - filename = os.path.join(SERIAL, f'{name}.tld') + filename = os.path.join(SERIAL, name) with open(filename, encoding='utf-8') as file: lines = [line[:-1] for line in file.readlines()] @@ -25,7 +25,7 @@ def main(): image = renderer.export() pathlib.Path(RENDER).mkdir(parents=True, exist_ok=True) - image.save(os.path.join(RENDER, f'{name}.png') + image.save(os.path.join(RENDER, f'{name}.png')) class MultiboxRenderer: @@ -56,6 +56,7 @@ class MultiboxRenderer: renderer = BoxRenderer(self.fonts, width, height) for i in range(action_count): + if i == 0: continue command = self.content[start + i] renderer.execute(command) @@ -145,7 +146,8 @@ class BoxRenderer: self.draw.text(self.cursor, text, (0, 0, 0, 255), font=self.font) elif cmd == 'b': - x, y, w, h = (pix(float(s)) for s in parts) + x, y = self.cursor + w, h = (pix(float(s)) for s in parts) rect = [x, y, x+w-1, y+h-1] forbidden_colors = set()