Bad stack layouter 🚑
This commit is contained in:
parent
64f938b449
commit
7e98022435
@ -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(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
||||
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![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)?)]
|
||||
}
|
||||
}
|
||||
|
60
src/library/direction.rs
Normal file
60
src/library/direction.rs
Normal file
@ -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)],
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
);
|
||||
|
@ -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.
|
||||
|
@ -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)]
|
||||
}
|
||||
}
|
||||
|
26
src/size.rs
26
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())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
31
tests/layouts/test.typ
Normal file
31
tests/layouts/test.typ
Normal file
@ -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
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user