summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/export/pdf.rs2
-rw-r--r--src/layout/actions.rs16
-rw-r--r--src/layout/mod.rs24
-rw-r--r--src/layout/stack.rs178
-rw-r--r--src/layout/tree.rs38
-rw-r--r--src/lib.rs2
-rw-r--r--src/library/boxed.rs15
-rw-r--r--src/library/direction.rs60
-rw-r--r--src/library/keys.rs9
-rw-r--r--src/library/maps.rs38
-rw-r--r--src/library/mod.rs12
-rw-r--r--src/size.rs26
-rw-r--r--tests/layout.rs19
-rw-r--r--tests/layouts/test.typ31
-rw-r--r--tests/render.py8
15 files changed, 382 insertions, 96 deletions
diff --git a/src/export/pdf.rs b/src/export/pdf.rs
index c70fdcc0..8eca0374 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 a8abf9f0..01abc0ba 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 7f9b9b95..c34d881e 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 3f9af350..b11aee79 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<Subspace>,
+ subs: Vec<Subspace>,
}
/// 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);
+
+ 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);
- fn finish_subspace(&mut self) {
- unimplemented!()
+ // ---------------------------------------------------------------------
+ // 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,13 +375,45 @@ impl StackLayouter {
self.sub = Subspace::new(axes, Alignment::Origin, space.start(), space.usable());
}
+ /// 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) {
- unimplemented!()
+ 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)
}
- fn next_space(&self) -> usize {
- (self.space.index + 1).min(self.ctx.spaces.len() - 1)
+ /// 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()
}
}
@@ -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 f36516c1..94a50eea 100644
--- a/src/layout/tree.rs
+++ b/src/layout/tree.rs
@@ -11,7 +11,7 @@ pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiL
#[derive(Debug, Clone)]
struct TreeLayouter<'a, 'p> {
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<MultiLayout> {
- self.flex.finish()
+ Ok(self.stack.finish())
}
}
diff --git a/src/lib.rs b/src/lib.rs
index a7bc38a9..f2ebd572 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 3ec9d001..d3d5b591 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<PSize>,
}
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 00000000..3b9956d1
--- /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<SyntaxTree>,
+ map: ConsistentMap<AxisKey, Axis>,
+ }
+
+ parse(args, body, ctx) {
+ let mut map = ConsistentMap::new();
+
+ map.add_opt_span(AxisKey::Primary, args.get_pos_opt::<Axis>()?)?;
+ map.add_opt_span(AxisKey::Secondary, args.get_pos_opt::<Axis>()?)?;
+
+ 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 e74027ec..c7e34839 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<AxisKey>, "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 01bde38b..c89d46cb 100644
--- a/src/library/maps.rs
+++ b/src/library/maps.rs
@@ -72,14 +72,14 @@ impl<K, V> ConsistentMap<K, V> where K: Hash + Eq {
/// A map for storing extents along axes.
#[derive(Debug, Clone, PartialEq)]
-pub struct ExtentMap(ConsistentMap<AxisKey, Size>);
+pub struct ExtentMap<E: ExpressionKind + Copy>(ConsistentMap<AxisKey, E>);
-impl ExtentMap {
+impl<E: ExpressionKind + Copy> ExtentMap<E> {
/// 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<ExtentMap> {
+ pub fn new(args: &mut FuncArgs, enforce: bool) -> ParseResult<ExtentMap<E>> {
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<F>(
+ &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<F>(&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<ConsistentMap<SpecificAxisKind, E>> {
+ 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 293f9589..4439f137 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>("align");
std.add::<Boxed>("box");
+ std.add::<Direction>("direction");
std.add::<PageSize>("page.size");
std.add::<PageMargins>("page.margins");
@@ -78,7 +80,7 @@ function! {
/// `page.size`: Set the size of pages.
#[derive(Debug, PartialEq)]
pub struct PageSize {
- map: ExtentMap,
+ map: ExtentMap<Size>,
}
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 4c8278ab..4a81a65d 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 33c12a2b..cca0dee0 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 00000000..063a7bfe
--- /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 07a9b5b1..2e105eb2 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>'
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()