mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Pretty good stack layouter ✈
This commit is contained in:
parent
d34707a6ae
commit
a791ef1628
@ -66,6 +66,7 @@ pub struct FlexContext {
|
|||||||
pub axes: LayoutAxes,
|
pub axes: LayoutAxes,
|
||||||
pub alignment: LayoutAlignment,
|
pub alignment: LayoutAlignment,
|
||||||
pub flex_spacing: Size,
|
pub flex_spacing: Size,
|
||||||
|
pub debug: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FlexLayouter {
|
impl FlexLayouter {
|
||||||
@ -75,6 +76,7 @@ impl FlexLayouter {
|
|||||||
spaces: ctx.spaces,
|
spaces: ctx.spaces,
|
||||||
axes: ctx.axes,
|
axes: ctx.axes,
|
||||||
alignment: ctx.alignment,
|
alignment: ctx.alignment,
|
||||||
|
debug: ctx.debug,
|
||||||
});
|
});
|
||||||
|
|
||||||
let usable = stack.primary_usable();
|
let usable = stack.primary_usable();
|
||||||
@ -176,7 +178,6 @@ impl FlexLayouter {
|
|||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn finish_partial_line(&mut self) {
|
fn finish_partial_line(&mut self) {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
@ -65,14 +65,16 @@ pub struct LayoutContext<'a, 'p> {
|
|||||||
pub loader: &'a SharedFontLoader<'p>,
|
pub loader: &'a SharedFontLoader<'p>,
|
||||||
/// The style for pages and text.
|
/// The style for pages and text.
|
||||||
pub style: &'a LayoutStyle,
|
pub style: &'a LayoutStyle,
|
||||||
/// Whether this layouting process handles the top-level pages.
|
|
||||||
pub top_level: bool,
|
|
||||||
/// The spaces to layout in.
|
/// The spaces to layout in.
|
||||||
pub spaces: LayoutSpaces,
|
pub spaces: LayoutSpaces,
|
||||||
/// The initial axes along which content is laid out.
|
/// The initial axes along which content is laid out.
|
||||||
pub axes: LayoutAxes,
|
pub axes: LayoutAxes,
|
||||||
/// The alignment of the finished layout.
|
/// The alignment of the finished layout.
|
||||||
pub alignment: LayoutAlignment,
|
pub alignment: LayoutAlignment,
|
||||||
|
/// Whether this layouting process handles the top-level pages.
|
||||||
|
pub top_level: bool,
|
||||||
|
/// Whether to debug render a box around the layout.
|
||||||
|
pub debug: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A possibly stack-allocated vector of layout spaces.
|
/// A possibly stack-allocated vector of layout spaces.
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
use smallvec::smallvec;
|
use smallvec::smallvec;
|
||||||
use crate::size::{min, max};
|
use crate::size::max;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// The stack layouter arranges boxes stacked onto each other.
|
/// The stack layouter stack boxes onto each other along the secondary layouting
|
||||||
|
/// axis.
|
||||||
///
|
///
|
||||||
/// The boxes are laid out in the direction of the secondary layouting axis and
|
/// The boxes are aligned along both axes according to their requested
|
||||||
/// are aligned along both axes.
|
/// alignment.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct StackLayouter {
|
pub struct StackLayouter {
|
||||||
/// The context for layouter.
|
/// The context for layouting.
|
||||||
ctx: StackContext,
|
ctx: StackContext,
|
||||||
/// The output layouts.
|
/// The output layouts.
|
||||||
layouts: MultiLayout,
|
layouts: MultiLayout,
|
||||||
@ -17,13 +18,19 @@ pub struct StackLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The context for stack layouting.
|
/// The context for stack layouting.
|
||||||
///
|
|
||||||
/// See [`LayoutContext`] for details about the fields.
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct StackContext {
|
pub struct StackContext {
|
||||||
|
/// The spaces to layout in.
|
||||||
pub spaces: LayoutSpaces,
|
pub spaces: LayoutSpaces,
|
||||||
|
/// The initial layouting axes, which can be updated by the
|
||||||
|
/// [`StackLayouter::set_axes`] method.
|
||||||
pub axes: LayoutAxes,
|
pub axes: LayoutAxes,
|
||||||
|
/// Which alignment to set on the resulting layout. This affects how it will
|
||||||
|
/// be positioned in a parent box.
|
||||||
pub alignment: LayoutAlignment,
|
pub alignment: LayoutAlignment,
|
||||||
|
/// Whether to output a command which renders a debugging box showing the
|
||||||
|
/// extent of the layout.
|
||||||
|
pub debug: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A layout space composed of subspaces which can have different axes and
|
/// A layout space composed of subspaces which can have different axes and
|
||||||
@ -42,19 +49,26 @@ struct Space {
|
|||||||
usable: Size2D,
|
usable: Size2D,
|
||||||
/// The specialized extra-needed dimensions to affect the size at all.
|
/// The specialized extra-needed dimensions to affect the size at all.
|
||||||
extra: Size2D,
|
extra: Size2D,
|
||||||
/// The maximal secondary alignment for both specialized axes (horizontal,
|
/// Dictates the valid alignments for new boxes in this space.
|
||||||
/// vertical).
|
rulers: Rulers,
|
||||||
alignment: (Alignment, Alignment),
|
|
||||||
/// The last added spacing if the last added thing was spacing.
|
/// The last added spacing if the last added thing was spacing.
|
||||||
last_spacing: LastSpacing,
|
last_spacing: LastSpacing,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The rulers of a space dictate which alignments for new boxes are still
|
||||||
|
/// allowed and which require a new space to be started.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Rulers {
|
||||||
|
top: Alignment,
|
||||||
|
bottom: Alignment,
|
||||||
|
left: Alignment,
|
||||||
|
right: Alignment,
|
||||||
|
}
|
||||||
|
|
||||||
impl StackLayouter {
|
impl StackLayouter {
|
||||||
/// Create a new stack layouter.
|
/// Create a new stack layouter.
|
||||||
pub fn new(ctx: StackContext) -> StackLayouter {
|
pub fn new(ctx: StackContext) -> StackLayouter {
|
||||||
let axes = ctx.axes;
|
|
||||||
let space = ctx.spaces[0];
|
let space = ctx.spaces[0];
|
||||||
|
|
||||||
StackLayouter {
|
StackLayouter {
|
||||||
ctx,
|
ctx,
|
||||||
layouts: MultiLayout::new(),
|
layouts: MultiLayout::new(),
|
||||||
@ -64,23 +78,15 @@ 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 the layout's secondary alignment is less than what we have already
|
if !self.update_rulers(layout.alignment) {
|
||||||
// seen, it needs to go into the next space.
|
|
||||||
if layout.alignment.secondary < *self.secondary_alignment() {
|
|
||||||
self.finish_space(true);
|
self.finish_space(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if layout.alignment.secondary == *self.secondary_alignment() {
|
// Now, we add a possibly cached soft space. If the secondary alignment
|
||||||
// Add a cached soft space if there is one and the alignment stayed
|
// changed before, a possibly cached space would have already been
|
||||||
// the same. Soft spaces are discarded if the alignment changes.
|
// discarded.
|
||||||
if let LastSpacing::Soft(spacing, _) = self.space.last_spacing {
|
if let LastSpacing::Soft(spacing, _) = self.space.last_spacing {
|
||||||
self.add_spacing(spacing, SpacingKind::Hard);
|
self.add_spacing(spacing, SpacingKind::Hard);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// We want the new maximal alignment and since the layout's
|
|
||||||
// secondary alignment is at least the previous maximum, we just
|
|
||||||
// take it.
|
|
||||||
*self.secondary_alignment() = layout.alignment.secondary;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the first space that fits the layout.
|
// Find the first space that fits the layout.
|
||||||
@ -93,8 +99,11 @@ impl StackLayouter {
|
|||||||
self.finish_space(true);
|
self.finish_space(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Change the usable space and size of the space.
|
||||||
self.update_metrics(layout.dimensions.generalized(self.ctx.axes));
|
self.update_metrics(layout.dimensions.generalized(self.ctx.axes));
|
||||||
|
|
||||||
|
// Add the box to the vector and remember that spacings are allowed
|
||||||
|
// again.
|
||||||
self.space.layouts.push((self.ctx.axes, layout));
|
self.space.layouts.push((self.ctx.axes, layout));
|
||||||
self.space.last_spacing = LastSpacing::None;
|
self.space.last_spacing = LastSpacing::None;
|
||||||
|
|
||||||
@ -103,7 +112,7 @@ impl StackLayouter {
|
|||||||
|
|
||||||
/// Add multiple layouts to the stack.
|
/// Add multiple layouts to the stack.
|
||||||
///
|
///
|
||||||
/// This function simply calls `add` for each layout.
|
/// This function simply calls `add` repeatedly for each layout.
|
||||||
pub fn add_multiple(&mut self, layouts: MultiLayout) -> LayoutResult<()> {
|
pub fn add_multiple(&mut self, layouts: MultiLayout) -> LayoutResult<()> {
|
||||||
for layout in layouts {
|
for layout in layouts {
|
||||||
self.add(layout)?;
|
self.add(layout)?;
|
||||||
@ -121,7 +130,6 @@ impl StackLayouter {
|
|||||||
let dimensions = Size2D::with_y(spacing);
|
let dimensions = Size2D::with_y(spacing);
|
||||||
|
|
||||||
self.update_metrics(dimensions);
|
self.update_metrics(dimensions);
|
||||||
|
|
||||||
self.space.layouts.push((self.ctx.axes, Layout {
|
self.space.layouts.push((self.ctx.axes, Layout {
|
||||||
dimensions: dimensions.specialized(self.ctx.axes),
|
dimensions: dimensions.specialized(self.ctx.axes),
|
||||||
alignment: LayoutAlignment::default(),
|
alignment: LayoutAlignment::default(),
|
||||||
@ -147,6 +155,26 @@ impl StackLayouter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update the rulers to account for the new layout. Returns true if a
|
||||||
|
/// space break is necessary.
|
||||||
|
fn update_rulers(&mut self, alignment: LayoutAlignment) -> bool {
|
||||||
|
let axes = self.ctx.axes;
|
||||||
|
let allowed = self.alignment_allowed(axes.primary, alignment.primary)
|
||||||
|
&& self.alignment_allowed(axes.secondary, alignment.secondary);
|
||||||
|
|
||||||
|
if allowed {
|
||||||
|
*self.space.rulers.get(axes.secondary) = alignment.secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
allowed
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the given alignment is still allowed according to the rulers.
|
||||||
|
fn alignment_allowed(&mut self, axis: Axis, alignment: Alignment) -> bool {
|
||||||
|
alignment >= *self.space.rulers.get(axis)
|
||||||
|
&& alignment <= self.space.rulers.get(axis.inv()).inv()
|
||||||
|
}
|
||||||
|
|
||||||
/// Update the size metrics to reflect that a layout or spacing with the
|
/// Update the size metrics to reflect that a layout or spacing with the
|
||||||
/// given generalized dimensions has been added.
|
/// given generalized dimensions has been added.
|
||||||
fn update_metrics(&mut self, dimensions: Size2D) {
|
fn update_metrics(&mut self, dimensions: Size2D) {
|
||||||
@ -196,8 +224,12 @@ impl StackLayouter {
|
|||||||
/// The remaining unpadded, unexpanding spaces. If a multi-layout is laid
|
/// The remaining unpadded, unexpanding spaces. If a multi-layout is laid
|
||||||
/// out into these spaces, it will fit into this stack.
|
/// out into these spaces, it will fit into this stack.
|
||||||
pub fn remaining(&self) -> LayoutSpaces {
|
pub fn remaining(&self) -> LayoutSpaces {
|
||||||
|
let dimensions = self.space.usable
|
||||||
|
- Size2D::with_y(self.space.last_spacing.soft_or_zero())
|
||||||
|
.specialized(self.ctx.axes);
|
||||||
|
|
||||||
let mut spaces = smallvec![LayoutSpace {
|
let mut spaces = smallvec![LayoutSpace {
|
||||||
dimensions: self.space.usable,
|
dimensions,
|
||||||
padding: SizeBox::ZERO,
|
padding: SizeBox::ZERO,
|
||||||
expand: LayoutExpansion::new(false, false),
|
expand: LayoutExpansion::new(false, false),
|
||||||
}];
|
}];
|
||||||
@ -280,18 +312,34 @@ impl StackLayouter {
|
|||||||
// Step 3: Backward pass. Reduce the bounding boxes from the previous
|
// Step 3: Backward pass. Reduce the bounding boxes from the previous
|
||||||
// layouts by what is taken by the following ones.
|
// layouts by what is taken by the following ones.
|
||||||
|
|
||||||
let mut extent = Size::ZERO;
|
// The `x` field stores the maximal primary extent in one axis-aligned
|
||||||
|
// run, while the `y` fields stores the accumulated secondary extent.
|
||||||
|
let mut extent = Size2D::ZERO;
|
||||||
|
let mut rotated = false;
|
||||||
|
|
||||||
for (bound, entry) in bounds.iter_mut().zip(&self.space.layouts).rev() {
|
for (bound, entry) in bounds.iter_mut().zip(&self.space.layouts).rev() {
|
||||||
let (axes, layout) = entry;
|
let (axes, layout) = entry;
|
||||||
|
|
||||||
|
// When the axes get rotated, the the maximal primary size
|
||||||
|
// (`extent.x`) dictates how much secondary extent the whole run
|
||||||
|
// had. This value is thus stored in `extent.y`. The primary extent
|
||||||
|
// is reset for this new axis-aligned run.
|
||||||
|
let is_horizontal = axes.secondary.is_horizontal();
|
||||||
|
if is_horizontal != rotated {
|
||||||
|
extent.y = extent.x;
|
||||||
|
extent.x = Size::ZERO;
|
||||||
|
rotated = is_horizontal;
|
||||||
|
}
|
||||||
|
|
||||||
// We reduce the bounding box of this layout at it's end by the
|
// We reduce the bounding box of this layout at it's end by the
|
||||||
// accumulated secondary extent of all layouts we have seen so far,
|
// accumulated secondary extent of all layouts we have seen so far,
|
||||||
// which are the layouts after this one since we iterate reversed.
|
// which are the layouts after this one since we iterate reversed.
|
||||||
*bound.secondary_end_mut(*axes) -= axes.secondary.factor() * extent;
|
*bound.secondary_end_mut(*axes) -= axes.secondary.factor() * extent.y;
|
||||||
|
|
||||||
// Then, we add this layout's secondary extent to the accumulator.
|
// Then, we add this layout's secondary extent to the accumulator.
|
||||||
extent += layout.dimensions.secondary(*axes);
|
let size = layout.dimensions.generalized(*axes);
|
||||||
|
extent.x.max_eq(size.x);
|
||||||
|
extent.y += size.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------ //
|
// ------------------------------------------------------------------ //
|
||||||
@ -299,38 +347,26 @@ impl StackLayouter {
|
|||||||
// into a single finished layout.
|
// into a single finished layout.
|
||||||
|
|
||||||
let mut actions = LayoutActions::new();
|
let mut actions = LayoutActions::new();
|
||||||
actions.add(LayoutAction::DebugBox(dimensions));
|
|
||||||
|
if self.ctx.debug {
|
||||||
|
actions.add(LayoutAction::DebugBox(dimensions));
|
||||||
|
}
|
||||||
|
|
||||||
let layouts = std::mem::replace(&mut self.space.layouts, vec![]);
|
let layouts = std::mem::replace(&mut self.space.layouts, vec![]);
|
||||||
|
|
||||||
for ((axes, layout), bound) in layouts.into_iter().zip(bounds) {
|
for ((axes, layout), bound) in layouts.into_iter().zip(bounds) {
|
||||||
let LayoutAxes { primary, secondary } = axes;
|
|
||||||
|
|
||||||
let size = layout.dimensions.specialized(axes);
|
let size = layout.dimensions.specialized(axes);
|
||||||
let alignment = layout.alignment;
|
let alignment = layout.alignment;
|
||||||
|
|
||||||
// The space in which this layout is aligned is given by it's
|
// The space in which this layout is aligned is given by the
|
||||||
// corresponding bound box.
|
// distances between the borders of it's bounding box.
|
||||||
let usable = Size2D::new(
|
let usable =
|
||||||
bound.right - bound.left,
|
Size2D::new(bound.right - bound.left, bound.bottom - bound.top)
|
||||||
bound.bottom - bound.top
|
.generalized(axes);
|
||||||
).generalized(axes);
|
|
||||||
|
|
||||||
let offsets = Size2D {
|
let local = usable.anchor(alignment, axes) - size.anchor(alignment, axes);
|
||||||
x: usable.x.anchor(alignment.primary, primary.is_positive())
|
let pos = Size2D::new(bound.left, bound.top) + local.specialized(axes);
|
||||||
- size.x.anchor(alignment.primary, primary.is_positive()),
|
|
||||||
y: usable.y.anchor(alignment.secondary, secondary.is_positive())
|
|
||||||
- size.y.anchor(alignment.secondary, secondary.is_positive()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let position = Size2D::new(bound.left, bound.top)
|
actions.add_layout(pos, layout);
|
||||||
+ offsets.specialized(axes);
|
|
||||||
|
|
||||||
println!("pos: {}", position);
|
|
||||||
println!("usable: {}", usable);
|
|
||||||
println!("size: {}", size);
|
|
||||||
|
|
||||||
actions.add_layout(position, layout);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.layouts.push(Layout {
|
self.layouts.push(Layout {
|
||||||
@ -339,6 +375,9 @@ impl StackLayouter {
|
|||||||
actions: actions.to_vec(),
|
actions: actions.to_vec(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ //
|
||||||
|
// Step 5: Start the next space.
|
||||||
|
|
||||||
self.start_space(self.next_space(), hard);
|
self.start_space(self.next_space(), hard);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,14 +391,6 @@ impl StackLayouter {
|
|||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Access the secondary alignment in the current system of axes.
|
|
||||||
fn secondary_alignment(&mut self) -> &mut Alignment {
|
|
||||||
match self.ctx.axes.primary.is_horizontal() {
|
|
||||||
true => &mut self.space.alignment.1,
|
|
||||||
false => &mut self.space.alignment.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Space {
|
impl Space {
|
||||||
@ -371,8 +402,24 @@ impl Space {
|
|||||||
size: Size2D::ZERO,
|
size: Size2D::ZERO,
|
||||||
usable,
|
usable,
|
||||||
extra: Size2D::ZERO,
|
extra: Size2D::ZERO,
|
||||||
alignment: (Alignment::Origin, Alignment::Origin),
|
rulers: Rulers {
|
||||||
|
top: Alignment::Origin,
|
||||||
|
bottom: Alignment::Origin,
|
||||||
|
left: Alignment::Origin,
|
||||||
|
right: Alignment::Origin,
|
||||||
|
},
|
||||||
last_spacing: LastSpacing::Hard,
|
last_spacing: LastSpacing::Hard,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Rulers {
|
||||||
|
fn get(&mut self, axis: Axis) -> &mut Alignment {
|
||||||
|
match axis {
|
||||||
|
Axis::TopToBottom => &mut self.top,
|
||||||
|
Axis::BottomToTop => &mut self.bottom,
|
||||||
|
Axis::LeftToRight => &mut self.left,
|
||||||
|
Axis::RightToLeft => &mut self.right,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -23,6 +23,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
|||||||
spaces: ctx.spaces.clone(),
|
spaces: ctx.spaces.clone(),
|
||||||
axes: ctx.axes,
|
axes: ctx.axes,
|
||||||
alignment: ctx.alignment,
|
alignment: ctx.alignment,
|
||||||
|
debug: ctx.debug,
|
||||||
}),
|
}),
|
||||||
style: ctx.style.clone(),
|
style: ctx.style.clone(),
|
||||||
ctx,
|
ctx,
|
||||||
@ -75,8 +76,9 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
|||||||
let commands = func.0.layout(LayoutContext {
|
let commands = func.0.layout(LayoutContext {
|
||||||
loader: self.ctx.loader,
|
loader: self.ctx.loader,
|
||||||
style: &self.style,
|
style: &self.style,
|
||||||
top_level: false,
|
|
||||||
spaces,
|
spaces,
|
||||||
|
top_level: false,
|
||||||
|
debug: true,
|
||||||
.. self.ctx
|
.. self.ctx
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ use toddle::Error as FontError;
|
|||||||
|
|
||||||
use crate::func::Scope;
|
use crate::func::Scope;
|
||||||
use crate::layout::{layout_tree, MultiLayout, LayoutContext};
|
use crate::layout::{layout_tree, MultiLayout, LayoutContext};
|
||||||
use crate::layout::{LayoutAxes, LayoutAlignment, Axis, Alignment};
|
use crate::layout::{LayoutAxes, LayoutAlignment};
|
||||||
use crate::layout::{LayoutResult, LayoutSpace, LayoutExpansion};
|
use crate::layout::{LayoutResult, LayoutSpace, LayoutExpansion};
|
||||||
use crate::syntax::{parse, SyntaxTree, ParseContext, Span, ParseResult};
|
use crate::syntax::{parse, SyntaxTree, ParseContext, Span, ParseResult};
|
||||||
use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
||||||
@ -94,7 +94,6 @@ impl<'p> Typesetter<'p> {
|
|||||||
&tree,
|
&tree,
|
||||||
LayoutContext {
|
LayoutContext {
|
||||||
loader: &self.loader,
|
loader: &self.loader,
|
||||||
top_level: true,
|
|
||||||
style: &self.style,
|
style: &self.style,
|
||||||
spaces: smallvec![LayoutSpace {
|
spaces: smallvec![LayoutSpace {
|
||||||
dimensions: self.style.page.dimensions,
|
dimensions: self.style.page.dimensions,
|
||||||
@ -103,6 +102,8 @@ impl<'p> Typesetter<'p> {
|
|||||||
}],
|
}],
|
||||||
axes: LayoutAxes::default(),
|
axes: LayoutAxes::default(),
|
||||||
alignment: LayoutAlignment::default(),
|
alignment: LayoutAlignment::default(),
|
||||||
|
top_level: true,
|
||||||
|
debug: false,
|
||||||
},
|
},
|
||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
|
@ -7,19 +7,25 @@ function! {
|
|||||||
pub struct Boxed {
|
pub struct Boxed {
|
||||||
body: SyntaxTree,
|
body: SyntaxTree,
|
||||||
map: ExtentMap<PSize>,
|
map: ExtentMap<PSize>,
|
||||||
|
debug: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(args, body, ctx) {
|
parse(args, body, ctx) {
|
||||||
Boxed {
|
Boxed {
|
||||||
body: parse!(optional: body, ctx).unwrap_or(SyntaxTree::new()),
|
body: parse!(optional: body, ctx).unwrap_or(SyntaxTree::new()),
|
||||||
map: ExtentMap::new(&mut args, false)?,
|
map: ExtentMap::new(&mut args, false)?,
|
||||||
|
debug: args.get_key_opt::<bool>("debug")?
|
||||||
|
.map(Spanned::value)
|
||||||
|
.unwrap_or(true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
layout(self, mut ctx) {
|
layout(self, mut ctx) {
|
||||||
use SpecificAxisKind::*;
|
use SpecificAxisKind::*;
|
||||||
|
|
||||||
|
ctx.debug = self.debug;
|
||||||
let space = &mut ctx.spaces[0];
|
let space = &mut ctx.spaces[0];
|
||||||
|
|
||||||
self.map.apply_with(ctx.axes, |axis, p| {
|
self.map.apply_with(ctx.axes, |axis, p| {
|
||||||
let entity = match axis {
|
let entity = match axis {
|
||||||
Horizontal => { space.expand.horizontal = true; &mut space.dimensions.x },
|
Horizontal => { space.expand.horizontal = true; &mut space.dimensions.x },
|
||||||
@ -27,7 +33,7 @@ function! {
|
|||||||
};
|
};
|
||||||
|
|
||||||
*entity = p.concretize(*entity)
|
*entity = p.concretize(*entity)
|
||||||
});
|
})?;
|
||||||
|
|
||||||
vec![AddMultiple(layout_tree(&self.body, ctx)?)]
|
vec![AddMultiple(layout_tree(&self.body, ctx)?)]
|
||||||
}
|
}
|
||||||
|
23
src/size.rs
23
src/size.rs
@ -6,7 +6,7 @@ use std::iter::Sum;
|
|||||||
use std::ops::*;
|
use std::ops::*;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::layout::{LayoutAxes, Axis, Alignment};
|
use crate::layout::{LayoutAxes, LayoutAlignment, Axis, Alignment};
|
||||||
|
|
||||||
/// A general space type.
|
/// A general space type.
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
@ -91,11 +91,11 @@ impl Size {
|
|||||||
*self = min(*self, other);
|
*self = min(*self, other);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The specialized anchor position for an item with the given alignment in a
|
/// The anchor position along the given axis for an item with the given
|
||||||
/// container with a given size along the given axis.
|
/// alignment in a container with this size.
|
||||||
pub fn anchor(&self, alignment: Alignment, positive: bool) -> Size {
|
pub fn anchor(&self, alignment: Alignment, axis: Axis) -> Size {
|
||||||
use Alignment::*;
|
use Alignment::*;
|
||||||
match (positive, alignment) {
|
match (axis.is_positive(), alignment) {
|
||||||
(true, Origin) | (false, End) => Size::ZERO,
|
(true, Origin) | (false, End) => Size::ZERO,
|
||||||
(_, Center) => *self / 2,
|
(_, Center) => *self / 2,
|
||||||
(true, End) | (false, Origin) => *self,
|
(true, End) | (false, Origin) => *self,
|
||||||
@ -219,9 +219,16 @@ impl Size2D {
|
|||||||
self.y.min_eq(other.y);
|
self.y.min_eq(other.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Swap the two dimensions.
|
/// The anchor position along the given axis for an item with the given
|
||||||
pub fn swap(&mut self) {
|
/// alignment in a container with this size.
|
||||||
std::mem::swap(&mut self.x, &mut self.y);
|
///
|
||||||
|
/// This assumes the size to be generalized such that `x` corresponds to the
|
||||||
|
/// primary axis.
|
||||||
|
pub fn anchor(&self, alignment: LayoutAlignment, axes: LayoutAxes) -> Size2D {
|
||||||
|
Size2D {
|
||||||
|
x: self.x.anchor(alignment.primary, axes.primary),
|
||||||
|
y: self.y.anchor(alignment.secondary, axes.secondary),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
81
tests/layouts/stack.typ
Normal file
81
tests/layouts/stack.typ
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
[page.size: w=5cm, h=5cm]
|
||||||
|
[page.margins: 0cm]
|
||||||
|
|
||||||
|
// Test 1
|
||||||
|
[box: w=1, h=1, debug=false][
|
||||||
|
[box][
|
||||||
|
[align: center]
|
||||||
|
[box: ps=3cm, ss=1cm]
|
||||||
|
[direction: ttb, ltr]
|
||||||
|
[box: ps=3cm, ss=1cm]
|
||||||
|
[box: ps=1cm, ss=1cm]
|
||||||
|
[box: ps=2cm, ss=1cm]
|
||||||
|
[box: ps=1cm, ss=1cm]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
[page.break]
|
||||||
|
|
||||||
|
// Test 2
|
||||||
|
[box: w=1, h=1, debug=false][
|
||||||
|
[align: secondary=top] Top
|
||||||
|
[align: secondary=center] Center
|
||||||
|
[align: secondary=bottom] Bottom
|
||||||
|
[direction: ttb, ltr]
|
||||||
|
[align: secondary=origin, primary=bottom]
|
||||||
|
[box: w=1cm, h=1cm]
|
||||||
|
]
|
||||||
|
[page.break]
|
||||||
|
|
||||||
|
// Test 3
|
||||||
|
[box: w=1, h=1, debug=false][
|
||||||
|
[align: center][
|
||||||
|
Somelongspacelessword!
|
||||||
|
[align: left] Some
|
||||||
|
[align: right] word!
|
||||||
|
]
|
||||||
|
]
|
||||||
|
[page.break]
|
||||||
|
|
||||||
|
// Test 4
|
||||||
|
[box: w=1, h=1, debug=false][
|
||||||
|
[direction: ltr, ttb]
|
||||||
|
[align: center]
|
||||||
|
[align: secondary=origin]
|
||||||
|
[box: ps=1cm, ss=1cm]
|
||||||
|
[align: secondary=center]
|
||||||
|
[box: ps=3cm, ss=1cm]
|
||||||
|
[box: ps=4cm, ss=0.5cm]
|
||||||
|
[align: secondary=end]
|
||||||
|
[box: ps=2cm, ss=1cm]
|
||||||
|
]
|
||||||
|
[page.break]
|
||||||
|
|
||||||
|
// Test 5
|
||||||
|
[box: w=1, h=1, debug=false][
|
||||||
|
[direction: primary=btt, secondary=ltr]
|
||||||
|
[align: primary=center, secondary=left]
|
||||||
|
[box: h=2cm, w=1cm]
|
||||||
|
|
||||||
|
[direction: rtl, btt]
|
||||||
|
[align: center]
|
||||||
|
[align: vertical=origin] ORIGIN
|
||||||
|
[align: vertical=center] CENTER
|
||||||
|
[align: vertical=end] END
|
||||||
|
]
|
||||||
|
[page.break]
|
||||||
|
|
||||||
|
// Test 6
|
||||||
|
[box: w=1, h=1, debug=false][
|
||||||
|
[box: w=4cm, h=1cm]
|
||||||
|
|
||||||
|
[align: primary=right, secondary=center] CENTER
|
||||||
|
|
||||||
|
[direction: btt, rtl]
|
||||||
|
[align: primary=center, secondary=origin]
|
||||||
|
[box: w=0.5cm, h=0.5cm]
|
||||||
|
[box: w=0.5cm, h=1cm]
|
||||||
|
[box: w=0.5cm, h=0.5cm]
|
||||||
|
|
||||||
|
[align: primary=origin, secondary=end]
|
||||||
|
END
|
||||||
|
]
|
@ -1,52 +0,0 @@
|
|||||||
[page.size: w=5cm, h=5cm]
|
|
||||||
[page.margins: 0cm]
|
|
||||||
|
|
||||||
// Test 1
|
|
||||||
// [box][
|
|
||||||
// [align: center]
|
|
||||||
// [box: ps=3cm, ss=1cm]
|
|
||||||
// [direction: ttb, ltr]
|
|
||||||
// [box: ps=3cm, ss=1cm]
|
|
||||||
// [box: ps=1cm, ss=1cm]
|
|
||||||
// [box: ps=2cm, ss=1cm]
|
|
||||||
// [box: ps=1cm, ss=1cm]
|
|
||||||
// ]
|
|
||||||
|
|
||||||
// Test 2
|
|
||||||
// [align: secondary=top] Top
|
|
||||||
// [align: secondary=center] Center
|
|
||||||
// [align: secondary=bottom] Bottom
|
|
||||||
// [direction: ttb, ltr]
|
|
||||||
// [align: primary=bottom]
|
|
||||||
// [box: w=1cm, h=1cm]
|
|
||||||
|
|
||||||
// Test 3
|
|
||||||
// [align: center][
|
|
||||||
// Somelongspacelessword!
|
|
||||||
// [align: left] Some
|
|
||||||
// [align: right] word!
|
|
||||||
// ]
|
|
||||||
|
|
||||||
// Test 4: In all combinations, please!
|
|
||||||
// [direction: ltr, ttb]
|
|
||||||
// [align: center]
|
|
||||||
// [align: secondary=origin]
|
|
||||||
// [box: ps=1cm, ss=1cm]
|
|
||||||
// [align: secondary=center]
|
|
||||||
// [box: ps=3cm, ss=1cm]
|
|
||||||
// [box: ps=4cm, ss=0.5cm]
|
|
||||||
// [align: secondary=end]
|
|
||||||
// [box: ps=2cm, ss=1cm]
|
|
||||||
|
|
||||||
[align: primary=left, secondary=center]
|
|
||||||
[box: w=4cm, h=2cm]
|
|
||||||
|
|
||||||
[direction: primary=btt, secondary=ltr]
|
|
||||||
[align: primary=center, secondary=left]
|
|
||||||
[box: h=2cm, w=1cm]
|
|
||||||
|
|
||||||
// [direction: rtl, btt]
|
|
||||||
// [align: center]
|
|
||||||
// [align: vertical=origin] ORIGIN
|
|
||||||
// [align: vertical=center] CENTER
|
|
||||||
// [align: vertical=end] END
|
|
@ -56,7 +56,6 @@ 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)
|
||||||
|
|
||||||
@ -134,7 +133,7 @@ class BoxRenderer:
|
|||||||
|
|
||||||
if cmd == 'm':
|
if cmd == 'm':
|
||||||
x, y = (pix(float(s)) for s in parts)
|
x, y = (pix(float(s)) for s in parts)
|
||||||
self.cursor = (x, y)
|
self.cursor = [x, y]
|
||||||
|
|
||||||
elif cmd == 'f':
|
elif cmd == 'f':
|
||||||
index = int(parts[0])
|
index = int(parts[0])
|
||||||
@ -143,7 +142,9 @@ class BoxRenderer:
|
|||||||
|
|
||||||
elif cmd == 'w':
|
elif cmd == 'w':
|
||||||
text = command[2:]
|
text = command[2:]
|
||||||
|
width = self.draw.textsize(text, font=self.font)[0]
|
||||||
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)
|
||||||
|
self.cursor[0] += width
|
||||||
|
|
||||||
elif cmd == 'b':
|
elif cmd == 'b':
|
||||||
x, y = self.cursor
|
x, y = self.cursor
|
||||||
|
Loading…
x
Reference in New Issue
Block a user