mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
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)?);
|
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.
|
/// Write text starting at the current position.
|
||||||
WriteText(String),
|
WriteText(String),
|
||||||
/// Visualize a box for debugging purposes.
|
/// Visualize a box for debugging purposes.
|
||||||
/// The arguments are position and size.
|
DebugBox(Size2D),
|
||||||
DebugBox(Size2D, Size2D),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for LayoutAction {
|
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()),
|
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()),
|
SetFont(i, s) => write!(f, "f {} {}", i, s.to_pt()),
|
||||||
WriteText(s) => write!(f, "w {}", s),
|
WriteText(s) => write!(f, "w {}", s),
|
||||||
DebugBox(p, s) => write!(
|
DebugBox(s) => write!(f, "b {} {}", s.x.to_pt(), s.y.to_pt()),
|
||||||
f,
|
|
||||||
"b {} {} {} {}",
|
|
||||||
p.x.to_pt(),
|
|
||||||
p.y.to_pt(),
|
|
||||||
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),
|
MoveAbsolute(s) => write!(f, "move {} {}", s.x, s.y),
|
||||||
SetFont(i, s) => write!(f, "font {} {}", i, s),
|
SetFont(i, s) => write!(f, "font {} {}", i, s),
|
||||||
WriteText(s) => write!(f, "write \"{}\"", 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) {
|
pub fn add(&mut self, action: LayoutAction) {
|
||||||
match action {
|
match action {
|
||||||
MoveAbsolute(pos) => self.next_pos = Some(self.origin + pos),
|
MoveAbsolute(pos) => self.next_pos = Some(self.origin + pos),
|
||||||
DebugBox(pos, size) => self.actions.push(DebugBox(self.origin + pos, size)),
|
|
||||||
|
|
||||||
SetFont(index, size) => {
|
SetFont(index, size) => {
|
||||||
self.next_font = Some((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.
|
/// The direction factor for this axis.
|
||||||
///
|
///
|
||||||
/// - 1 if the axis is positive.
|
/// - 1 if the axis is positive.
|
||||||
@ -363,7 +373,7 @@ enum LastSpacing {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
fn soft_or_zero(&self) -> Size {
|
||||||
match self {
|
match self {
|
||||||
LastSpacing::Soft(space, _) => *space,
|
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.
|
/// Layout components that can be serialized.
|
||||||
pub trait Serialize {
|
pub trait Serialize {
|
||||||
/// Serialize the data structure into an output writable.
|
/// 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.
|
/// Whether to add the layout for this space even if it would be empty.
|
||||||
hard: bool,
|
hard: bool,
|
||||||
/// The so-far accumulated subspaces.
|
/// The so-far accumulated subspaces.
|
||||||
spaces: Vec<Subspace>,
|
subs: Vec<Subspace>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A part of a space with fixed axes and secondary alignment.
|
/// A part of a space with fixed axes and secondary alignment.
|
||||||
@ -88,8 +88,7 @@ impl StackLayouter {
|
|||||||
/// Add a layout to the stack.
|
/// Add a layout to the stack.
|
||||||
pub fn add(&mut self, layout: Layout) -> LayoutResult<()> {
|
pub fn add(&mut self, layout: Layout) -> LayoutResult<()> {
|
||||||
if layout.alignment.secondary != self.sub.alignment {
|
if layout.alignment.secondary != self.sub.alignment {
|
||||||
// self.finish_subspace();
|
self.finish_subspace(layout.alignment.secondary);
|
||||||
// finish sub and start new with layout's alignment
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a cached soft space if there is one.
|
// 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
|
/// This starts a new subspace (if the axes are actually different from the
|
||||||
/// current ones).
|
/// current ones).
|
||||||
pub fn set_axes(&mut self, axes: LayoutAxes) {
|
pub fn set_axes(&mut self, axes: LayoutAxes) {
|
||||||
if axes != self.ctx.axes {
|
if axes != self.ctx.axes {
|
||||||
self.finish_subspace();
|
self.finish_subspace(Alignment::Origin);
|
||||||
|
|
||||||
let (origin, usable) = self.remaining_subspace();
|
let (origin, usable) = self.remaining_subspace();
|
||||||
self.sub = Subspace::new(axes, Alignment::Origin, origin, usable);
|
self.sub = Subspace::new(axes, Alignment::Origin, origin, usable);
|
||||||
@ -226,9 +225,7 @@ impl StackLayouter {
|
|||||||
|
|
||||||
/// Whether the current layout space (not subspace) is empty.
|
/// Whether the current layout space (not subspace) is empty.
|
||||||
pub fn space_is_empty(&self) -> bool {
|
pub fn space_is_empty(&self) -> bool {
|
||||||
self.sub.layouts.is_empty()
|
self.subspace_is_empty() && self.space.subs.is_empty()
|
||||||
&& self.sub.size == Size2D::zero()
|
|
||||||
&& self.space.spaces.is_empty()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the current layout space is the last is the followup list.
|
/// Whether the current layout space is the last is the followup list.
|
||||||
@ -244,12 +241,127 @@ impl StackLayouter {
|
|||||||
self.layouts
|
self.layouts
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finish_space(&mut self, _hard: bool) {
|
/// Finish the current space and start a new one.
|
||||||
unimplemented!()
|
pub fn finish_space(&mut self, hard: bool) {
|
||||||
}
|
self.finish_subspace(Alignment::Origin);
|
||||||
|
|
||||||
fn finish_subspace(&mut self) {
|
println!();
|
||||||
unimplemented!()
|
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.
|
/// 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());
|
self.sub = Subspace::new(axes, Alignment::Origin, space.start(), space.usable());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The remaining sub
|
/// The index of the next space.
|
||||||
fn remaining_subspace(&self) -> (Size2D, Size2D) {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_space(&self) -> usize {
|
fn next_space(&self) -> usize {
|
||||||
(self.space.index + 1).min(self.ctx.spaces.len() - 1)
|
(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 {
|
impl Space {
|
||||||
@ -278,7 +422,7 @@ impl Space {
|
|||||||
Space {
|
Space {
|
||||||
index,
|
index,
|
||||||
hard,
|
hard,
|
||||||
spaces: vec![],
|
subs: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiL
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct TreeLayouter<'a, 'p> {
|
struct TreeLayouter<'a, 'p> {
|
||||||
ctx: LayoutContext<'a, 'p>,
|
ctx: LayoutContext<'a, 'p>,
|
||||||
flex: FlexLayouter,
|
stack: StackLayouter,
|
||||||
style: LayoutStyle,
|
style: LayoutStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,11 +19,10 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
|||||||
/// Create a new syntax tree layouter.
|
/// Create a new syntax tree layouter.
|
||||||
fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> {
|
fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> {
|
||||||
TreeLayouter {
|
TreeLayouter {
|
||||||
flex: FlexLayouter::new(FlexContext {
|
stack: StackLayouter::new(StackContext {
|
||||||
spaces: ctx.spaces.clone(),
|
spaces: ctx.spaces.clone(),
|
||||||
axes: ctx.axes,
|
axes: ctx.axes,
|
||||||
alignment: ctx.alignment,
|
alignment: ctx.alignment,
|
||||||
flex_spacing: flex_spacing(&ctx.style.text),
|
|
||||||
}),
|
}),
|
||||||
style: ctx.style.clone(),
|
style: ctx.style.clone(),
|
||||||
ctx,
|
ctx,
|
||||||
@ -56,25 +55,22 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
|||||||
alignment: self.ctx.alignment,
|
alignment: self.ctx.alignment,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(self.flex.add(layout))
|
self.stack.add(layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout_space(&mut self) {
|
fn layout_space(&mut self) {
|
||||||
self.flex.add_primary_space(
|
|
||||||
word_spacing(&self.style.text),
|
|
||||||
SPACE_KIND,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout_paragraph(&mut self) -> LayoutResult<()> {
|
fn layout_paragraph(&mut self) -> LayoutResult<()> {
|
||||||
self.flex.add_secondary_space(
|
Ok(self.stack.add_spacing(
|
||||||
paragraph_spacing(&self.style.text),
|
paragraph_spacing(&self.style.text),
|
||||||
PARAGRAPH_KIND,
|
PARAGRAPH_KIND,
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
|
fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
|
||||||
let spaces = self.flex.remaining();
|
let spaces = self.stack.remaining();
|
||||||
|
|
||||||
let commands = func.0.layout(LayoutContext {
|
let commands = func.0.layout(LayoutContext {
|
||||||
loader: self.ctx.loader,
|
loader: self.ctx.loader,
|
||||||
@ -97,16 +93,16 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
|||||||
match command {
|
match command {
|
||||||
LayoutTree(tree) => self.layout(tree)?,
|
LayoutTree(tree) => self.layout(tree)?,
|
||||||
|
|
||||||
Add(layout) => self.flex.add(layout),
|
Add(layout) => self.stack.add(layout)?,
|
||||||
AddMultiple(layouts) => self.flex.add_multiple(layouts),
|
AddMultiple(layouts) => self.stack.add_multiple(layouts)?,
|
||||||
AddSpacing(space, kind, axis) => match axis {
|
AddSpacing(space, kind, axis) => match axis {
|
||||||
GenericAxisKind::Primary => self.flex.add_primary_space(space, kind),
|
GenericAxisKind::Primary => {},
|
||||||
GenericAxisKind::Secondary => self.flex.add_secondary_space(space, kind)?,
|
GenericAxisKind::Secondary => self.stack.add_spacing(space, kind),
|
||||||
}
|
}
|
||||||
|
|
||||||
FinishLine => self.flex.add_break(),
|
FinishLine => {},
|
||||||
FinishRun => { self.flex.finish_run()?; },
|
FinishRun => {},
|
||||||
FinishSpace => self.flex.finish_space(true)?,
|
FinishSpace => self.stack.finish_space(true),
|
||||||
BreakParagraph => self.layout_paragraph()?,
|
BreakParagraph => self.layout_paragraph()?,
|
||||||
|
|
||||||
SetTextStyle(style) => self.style.text = style,
|
SetTextStyle(style) => self.style.text = style,
|
||||||
@ -116,7 +112,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.style.page = style;
|
self.style.page = style;
|
||||||
self.flex.set_spaces(smallvec
|
//! format. Submodules for these formats are located in the [export](crate::export)
|
||||||
//! module. Currently, the only supported output format is _PDF_.
|
//! module. Currently, the only supported output format is _PDF_.
|
||||||
|
|
||||||
|
#![allow(unused)]
|
||||||
|
|
||||||
pub extern crate toddle;
|
pub extern crate toddle;
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
@ -6,7 +6,7 @@ function! {
|
|||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Boxed {
|
pub struct Boxed {
|
||||||
body: SyntaxTree,
|
body: SyntaxTree,
|
||||||
map: ExtentMap,
|
map: ExtentMap<PSize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(args, body, ctx) {
|
parse(args, body, ctx) {
|
||||||
@ -17,7 +17,18 @@ function! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
layout(self, mut ctx) {
|
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)?)]
|
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,
|
"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)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum AlignmentKey {
|
pub enum AlignmentKey {
|
||||||
Left,
|
Left,
|
||||||
@ -172,3 +172,10 @@ kind!(PaddingKey<AxisKey>, "axis or side",
|
|||||||
"vertical-origin" => PaddingKey::AxisAligned(AxisKey::Vertical, AlignmentKey::Origin),
|
"vertical-origin" => PaddingKey::AxisAligned(AxisKey::Vertical, AlignmentKey::Origin),
|
||||||
"vertical-end" => PaddingKey::AxisAligned(AxisKey::Vertical, AlignmentKey::End),
|
"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.
|
/// A map for storing extents along axes.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[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.
|
/// Parse an extent map from the function args.
|
||||||
///
|
///
|
||||||
/// If `enforce` is true other arguments will create an error, otherwise
|
/// If `enforce` is true other arguments will create an error, otherwise
|
||||||
/// they are left intact.
|
/// 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();
|
let mut map = ConsistentMap::new();
|
||||||
|
|
||||||
for arg in args.keys() {
|
for arg in args.keys() {
|
||||||
@ -96,22 +96,38 @@ impl ExtentMap {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let size = Size::from_expr(arg.v.value)?;
|
let e = E::from_expr(arg.v.value)?;
|
||||||
map.add(key, size)?;
|
map.add(key, e)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ExtentMap(map))
|
Ok(ExtentMap(map))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Map from any axis key to the specific axis kind.
|
/// Apply the extents on the dimensions.
|
||||||
pub fn apply(&self, axes: LayoutAxes, dimensions: &mut Size2D) -> LayoutResult<()> {
|
pub fn apply<F>(
|
||||||
let map = self.0.dedup(|key, &val| Ok((key.specific(axes), val)))?;
|
&self,
|
||||||
|
axes: LayoutAxes,
|
||||||
map.with(SpecificAxisKind::Horizontal, |&val| dimensions.x = val);
|
dimensions: &mut Size2D,
|
||||||
map.with(SpecificAxisKind::Vertical, |&val| dimensions.y = val);
|
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(())
|
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.
|
/// A map for storing padding at sides.
|
||||||
|
@ -8,6 +8,7 @@ use maps::*;
|
|||||||
|
|
||||||
pub_use_mod!(align);
|
pub_use_mod!(align);
|
||||||
pub_use_mod!(boxed);
|
pub_use_mod!(boxed);
|
||||||
|
pub_use_mod!(direction);
|
||||||
|
|
||||||
pub mod maps;
|
pub mod maps;
|
||||||
pub mod keys;
|
pub mod keys;
|
||||||
@ -19,6 +20,7 @@ pub fn std() -> Scope {
|
|||||||
|
|
||||||
std.add::<Align>("align");
|
std.add::<Align>("align");
|
||||||
std.add::<Boxed>("box");
|
std.add::<Boxed>("box");
|
||||||
|
std.add::<Direction>("direction");
|
||||||
std.add::<PageSize>("page.size");
|
std.add::<PageSize>("page.size");
|
||||||
std.add::<PageMargins>("page.margins");
|
std.add::<PageMargins>("page.margins");
|
||||||
|
|
||||||
@ -78,7 +80,7 @@ function! {
|
|||||||
/// `page.size`: Set the size of pages.
|
/// `page.size`: Set the size of pages.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct PageSize {
|
pub struct PageSize {
|
||||||
map: ExtentMap,
|
map: ExtentMap<Size>,
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(args, body) {
|
parse(args, body) {
|
||||||
@ -90,7 +92,7 @@ function! {
|
|||||||
|
|
||||||
layout(self, ctx) {
|
layout(self, ctx) {
|
||||||
let mut style = ctx.style.page;
|
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)]
|
vec![SetPageStyle(style)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,11 +152,7 @@ function! {
|
|||||||
|
|
||||||
layout(self, ctx) {
|
layout(self, ctx) {
|
||||||
let axis = self.axis.generic(ctx.axes);
|
let axis = self.axis.generic(ctx.axes);
|
||||||
let spacing = match self.spacing {
|
let spacing = self.spacing.concretize(ctx.style.text.font_size);
|
||||||
FSize::Absolute(size) => size,
|
|
||||||
FSize::Scaled(scale) => scale * ctx.style.text.font_size,
|
|
||||||
};
|
|
||||||
|
|
||||||
vec![AddSpacing(spacing, SpacingKind::Hard, axis)]
|
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::ops::*;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::layout::Alignment;
|
||||||
|
|
||||||
/// A general space type.
|
/// A general space type.
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
pub struct Size {
|
pub struct Size {
|
||||||
@ -80,6 +82,18 @@ impl Size {
|
|||||||
pub fn max_eq(&mut self, other: Size) {
|
pub fn max_eq(&mut self, other: Size) {
|
||||||
*self = max(*self, other);
|
*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 {
|
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.
|
/// The maximum of two sizes.
|
||||||
pub fn max(a: Size, b: Size) -> Size {
|
pub fn max(a: Size, b: Size) -> Size {
|
||||||
if a >= b { a } else { b }
|
if a >= b { a } else { b }
|
||||||
@ -192,7 +216,7 @@ pub fn min(a: Size, b: Size) -> Size {
|
|||||||
|
|
||||||
impl Display for Size {
|
impl Display for Size {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
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")?;
|
create_dir_all("tests/cache/pdf")?;
|
||||||
|
|
||||||
let tests: Vec<_> = read_dir("tests/layouts/")?.collect();
|
let tests: Vec<_> = read_dir("tests/layouts/")?.collect();
|
||||||
|
let mut filtered = Vec::new();
|
||||||
let len = tests.len();
|
|
||||||
println!();
|
|
||||||
println!("Running {} test{}", len, if len > 1 { "s" } else { "" });
|
|
||||||
|
|
||||||
for entry in tests {
|
for entry in tests {
|
||||||
let path = entry?.path();
|
let path = entry?.path();
|
||||||
@ -36,14 +33,23 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
let name = path
|
let name = path
|
||||||
.file_stem().ok_or("expected file stem")?
|
.file_stem().ok_or("expected file stem")?
|
||||||
.to_string_lossy();
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
if opts.matches(&name) {
|
if opts.matches(&name) {
|
||||||
let src = read_to_string(&path)?;
|
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!();
|
println!();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -79,6 +85,7 @@ fn test(name: &str, src: &str) -> Result<()> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
drop(loader);
|
||||||
|
|
||||||
// Write the serialized layout file.
|
// Write the serialized layout file.
|
||||||
let path = format!("tests/cache/serial/{}", name);
|
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>'
|
assert len(sys.argv) == 2, 'usage: python render.py <name>'
|
||||||
name = sys.argv[1]
|
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:
|
with open(filename, encoding='utf-8') as file:
|
||||||
lines = [line[:-1] for line in file.readlines()]
|
lines = [line[:-1] for line in file.readlines()]
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ def main():
|
|||||||
image = renderer.export()
|
image = renderer.export()
|
||||||
|
|
||||||
pathlib.Path(RENDER).mkdir(parents=True, exist_ok=True)
|
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:
|
class MultiboxRenderer:
|
||||||
@ -56,6 +56,7 @@ class MultiboxRenderer:
|
|||||||
|
|
||||||
renderer = BoxRenderer(self.fonts, width, height)
|
renderer = BoxRenderer(self.fonts, width, height)
|
||||||
for i in range(action_count):
|
for i in range(action_count):
|
||||||
|
if i == 0: continue
|
||||||
command = self.content[start + i]
|
command = self.content[start + i]
|
||||||
renderer.execute(command)
|
renderer.execute(command)
|
||||||
|
|
||||||
@ -145,7 +146,8 @@ class BoxRenderer:
|
|||||||
self.draw.text(self.cursor, text, (0, 0, 0, 255), font=self.font)
|
self.draw.text(self.cursor, text, (0, 0, 0, 255), font=self.font)
|
||||||
|
|
||||||
elif cmd == 'b':
|
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]
|
rect = [x, y, x+w-1, y+h-1]
|
||||||
|
|
||||||
forbidden_colors = set()
|
forbidden_colors = set()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user