mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Generalize stack layouter 🎲
This commit is contained in:
parent
40c879e3c0
commit
9473ae61e9
@ -3,6 +3,8 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use toddle::query::{FontClass, SharedFontLoader};
|
use toddle::query::{FontClass, SharedFontLoader};
|
||||||
use toddle::Error as FontError;
|
use toddle::Error as FontError;
|
||||||
|
|
||||||
@ -133,35 +135,38 @@ impl<'a> IntoIterator for &'a MultiLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The general context for layouting.
|
/// The general context for layouting.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LayoutContext<'a, 'p> {
|
pub struct LayoutContext<'a, 'p> {
|
||||||
/// The font loader to retrieve fonts from when typesetting text
|
/// The font loader to retrieve fonts from when typesetting text
|
||||||
/// using [`layout_text`].
|
/// using [`layout_text`].
|
||||||
pub loader: &'a SharedFontLoader<'p>,
|
pub loader: &'a SharedFontLoader<'p>,
|
||||||
|
|
||||||
/// The style to set text with. This includes sizes and font classes
|
/// The style to set text with. This includes sizes and font classes
|
||||||
/// which determine which font from the loaders selection is used.
|
/// which determine which font from the loaders selection is used.
|
||||||
pub style: &'a TextStyle,
|
pub style: &'a TextStyle,
|
||||||
/// The alignment to use for the content.
|
|
||||||
pub alignment: Alignment,
|
/// The spaces to layout in.
|
||||||
/// How to stack the context.
|
pub spaces: LayoutSpaces,
|
||||||
pub flow: Flow,
|
|
||||||
/// The primary space to layout in.
|
/// The axes to flow on.
|
||||||
pub space: LayoutSpace,
|
pub axes: LayoutAxes,
|
||||||
/// The additional spaces which are used when the primary space
|
|
||||||
/// cannot fit the whole content.
|
|
||||||
pub followup_spaces: Option<LayoutSpace>,
|
|
||||||
/// Whether to shrink the dimensions to fit the content or the keep the
|
|
||||||
/// dimensions from the layout spaces.
|
|
||||||
pub shrink_to_fit: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A possibly stack-allocated vector of layout spaces.
|
||||||
|
pub type LayoutSpaces = SmallVec<[LayoutSpace; 2]>;
|
||||||
|
|
||||||
/// Spacial layouting constraints.
|
/// Spacial layouting constraints.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct LayoutSpace {
|
pub struct LayoutSpace {
|
||||||
/// The maximum size of the box to layout in.
|
/// The maximum size of the box to layout in.
|
||||||
pub dimensions: Size2D,
|
pub dimensions: Size2D,
|
||||||
|
|
||||||
/// Padding that should be respected on each side.
|
/// Padding that should be respected on each side.
|
||||||
pub padding: SizeBox,
|
pub padding: SizeBox,
|
||||||
|
|
||||||
|
/// Whether to shrink the space to fit the content or to keep
|
||||||
|
/// the original dimensions.
|
||||||
|
pub shrink_to_fit: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutSpace {
|
impl LayoutSpace {
|
||||||
@ -170,11 +175,89 @@ impl LayoutSpace {
|
|||||||
self.dimensions.unpadded(self.padding)
|
self.dimensions.unpadded(self.padding)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A layout without padding and dimensions reduced by the padding.
|
/// The offset from the origin to the start of content, that is,
|
||||||
pub fn usable_space(&self) -> LayoutSpace {
|
/// `(padding.left, padding.top)`.
|
||||||
|
pub fn start(&self) -> Size2D {
|
||||||
|
Size2D::new(self.padding.left, self.padding.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A layout space without padding and dimensions reduced by the padding.
|
||||||
|
pub fn usable_space(&self, shrink_to_fit: bool) -> LayoutSpace {
|
||||||
LayoutSpace {
|
LayoutSpace {
|
||||||
dimensions: self.usable(),
|
dimensions: self.usable(),
|
||||||
padding: SizeBox::zero(),
|
padding: SizeBox::zero(),
|
||||||
|
shrink_to_fit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The axes along which the content is laid out.
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct LayoutAxes {
|
||||||
|
pub primary: AlignedAxis,
|
||||||
|
pub secondary: AlignedAxis,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutAxes {
|
||||||
|
/// The position of the anchor specified by the two aligned axes
|
||||||
|
/// in the given generalized space.
|
||||||
|
pub fn anchor(&self, area: Size2D) -> Size2D {
|
||||||
|
Size2D::new(self.primary.anchor(area.x), self.secondary.anchor(area.y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An axis with an alignment.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub struct AlignedAxis {
|
||||||
|
pub axis: Axis,
|
||||||
|
pub alignment: Alignment,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AlignedAxis {
|
||||||
|
/// Returns an aligned axis if the alignment is compatible with the axis.
|
||||||
|
pub fn new(axis: Axis, alignment: Alignment) -> AlignedAxis {
|
||||||
|
AlignedAxis { axis, alignment }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The pair of axis and alignment.
|
||||||
|
pub fn pair(&self) -> (Axis, Alignment) {
|
||||||
|
(self.axis, self.alignment)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The position of the anchor specified by this axis on the given line.
|
||||||
|
pub fn anchor(&self, line: Size) -> Size {
|
||||||
|
use Alignment::*;
|
||||||
|
match (self.axis.is_positive(), self.alignment) {
|
||||||
|
(true, Origin) | (false, End) => Size::zero(),
|
||||||
|
(_, Center) => line / 2,
|
||||||
|
(true, End) | (false, Origin) => line,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where to put content.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub enum Axis {
|
||||||
|
LeftToRight,
|
||||||
|
RightToLeft,
|
||||||
|
TopToBottom,
|
||||||
|
BottomToTop,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Axis {
|
||||||
|
/// Whether this is a horizontal axis.
|
||||||
|
pub fn is_horizontal(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Axis::LeftToRight | Axis::RightToLeft => true,
|
||||||
|
Axis::TopToBottom | Axis::BottomToTop => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this axis points into the positive coordinate direction.
|
||||||
|
pub fn is_positive(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Axis::LeftToRight | Axis::TopToBottom => true,
|
||||||
|
Axis::RightToLeft | Axis::BottomToTop => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -182,16 +265,9 @@ impl LayoutSpace {
|
|||||||
/// Where to align content.
|
/// Where to align content.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
pub enum Alignment {
|
pub enum Alignment {
|
||||||
Left,
|
Origin,
|
||||||
Right,
|
|
||||||
Center,
|
Center,
|
||||||
}
|
End,
|
||||||
|
|
||||||
/// The flow of content.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
|
||||||
pub enum Flow {
|
|
||||||
Vertical,
|
|
||||||
Horizontal,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The error type for layouting.
|
/// The error type for layouting.
|
||||||
|
@ -7,66 +7,37 @@ use super::*;
|
|||||||
pub struct StackLayouter {
|
pub struct StackLayouter {
|
||||||
ctx: StackContext,
|
ctx: StackContext,
|
||||||
layouts: MultiLayout,
|
layouts: MultiLayout,
|
||||||
actions: LayoutActionList,
|
/// Offset on secondary axis, anchor of the layout and the layout itself.
|
||||||
|
boxes: Vec<(Size, Size2D, Layout)>,
|
||||||
|
|
||||||
space: LayoutSpace,
|
|
||||||
usable: Size2D,
|
usable: Size2D,
|
||||||
dimensions: Size2D,
|
dimensions: Size2D,
|
||||||
cursor: Size2D,
|
active_space: usize,
|
||||||
in_extra_space: bool,
|
include_empty: bool,
|
||||||
started: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The context for stack layouting.
|
/// The context for stack layouting.
|
||||||
///
|
///
|
||||||
/// See [`LayoutContext`] for details about the fields.
|
/// See [`LayoutContext`] for details about the fields.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct StackContext {
|
pub struct StackContext {
|
||||||
pub alignment: Alignment,
|
pub spaces: LayoutSpaces,
|
||||||
pub space: LayoutSpace,
|
pub axes: LayoutAxes,
|
||||||
pub followup_spaces: Option<LayoutSpace>,
|
|
||||||
pub shrink_to_fit: bool,
|
|
||||||
pub flow: Flow,
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! reuse {
|
|
||||||
($ctx:expr, $flow:expr) => (
|
|
||||||
StackContext {
|
|
||||||
alignment: $ctx.alignment,
|
|
||||||
space: $ctx.space,
|
|
||||||
followup_spaces: $ctx.followup_spaces,
|
|
||||||
shrink_to_fit: $ctx.shrink_to_fit,
|
|
||||||
flow: $flow
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StackContext {
|
|
||||||
/// Create a stack context from a generic layout context.
|
|
||||||
pub fn from_layout_ctx(ctx: LayoutContext) -> StackContext {
|
|
||||||
reuse!(ctx, ctx.flow)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a stack context from a flex context.
|
|
||||||
pub fn from_flex_ctx(ctx: FlexContext, flow: Flow) -> StackContext {
|
|
||||||
reuse!(ctx, flow)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 usable = ctx.spaces[0].usable().generalized(ctx.axes);
|
||||||
StackLayouter {
|
StackLayouter {
|
||||||
ctx,
|
ctx,
|
||||||
layouts: MultiLayout::new(),
|
layouts: MultiLayout::new(),
|
||||||
actions: LayoutActionList::new(),
|
boxes: vec![],
|
||||||
|
|
||||||
space: ctx.space,
|
usable,
|
||||||
usable: ctx.space.usable(),
|
active_space: 0,
|
||||||
dimensions: start_dimensions(ctx.alignment, ctx.space),
|
dimensions: start_dimensions(usable, ctx.axes),
|
||||||
cursor: start_cursor(ctx.alignment, ctx.space),
|
include_empty: true,
|
||||||
in_extra_space: false,
|
|
||||||
started: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,51 +46,27 @@ impl StackLayouter {
|
|||||||
self.ctx
|
self.ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a sublayout to the bottom.
|
/// Add a sublayout.
|
||||||
pub fn add(&mut self, layout: Layout) -> LayoutResult<()> {
|
pub fn add(&mut self, layout: Layout) -> LayoutResult<()> {
|
||||||
if !self.started {
|
let size = layout.dimensions.generalized(self.ctx.axes);
|
||||||
self.start_new_space()?;
|
let mut new_dimensions = self.size_with(size);
|
||||||
|
|
||||||
|
// Search for a suitable space to insert the box.
|
||||||
|
while !self.usable.fits(new_dimensions) {
|
||||||
|
if self.active_space == self.ctx.spaces.len() - 1 {
|
||||||
|
return Err(LayoutError::NotEnoughSpace("box is to large for stack spaces"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_dimensions = match self.ctx.flow {
|
self.finish_layout()?;
|
||||||
Flow::Vertical => Size2D {
|
self.start_new_space(true);
|
||||||
x: crate::size::max(self.dimensions.x, layout.dimensions.x),
|
new_dimensions = self.size_with(size);
|
||||||
y: self.dimensions.y + layout.dimensions.y,
|
|
||||||
},
|
|
||||||
Flow::Horizontal => Size2D {
|
|
||||||
x: self.dimensions.x + layout.dimensions.x,
|
|
||||||
y: crate::size::max(self.dimensions.y, layout.dimensions.y),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.overflows(new_dimensions) {
|
|
||||||
if self.ctx.followup_spaces.is_some() &&
|
|
||||||
!(self.in_extra_space && self.overflows(layout.dimensions))
|
|
||||||
{
|
|
||||||
self.finish_layout(true)?;
|
|
||||||
return self.add(layout);
|
|
||||||
} else {
|
|
||||||
return Err(LayoutError::NotEnoughSpace("cannot fit box into stack"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine where to put the box. When we right-align it, we want the
|
let ofset = self.dimensions.y;
|
||||||
// cursor to point to the top-right corner of the box. Therefore, the
|
let anchor = self.ctx.axes.anchor(size);
|
||||||
// position has to be moved to the left by the width of the box.
|
self.boxes.push((ofset, anchor, layout));
|
||||||
let position = match self.ctx.alignment {
|
|
||||||
Alignment::Left => self.cursor,
|
|
||||||
Alignment::Right => self.cursor - Size2D::with_x(layout.dimensions.x),
|
|
||||||
Alignment::Center => self.cursor - Size2D::with_x(layout.dimensions.x / 2),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.dimensions = new_dimensions;
|
self.dimensions.y += size.y;
|
||||||
|
|
||||||
match self.ctx.flow {
|
|
||||||
Flow::Vertical => self.cursor.y += layout.dimensions.y,
|
|
||||||
Flow::Horizontal => self.cursor.x += layout.dimensions.x,
|
|
||||||
}
|
|
||||||
|
|
||||||
self.actions.add_layout(position, layout);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -134,24 +81,11 @@ impl StackLayouter {
|
|||||||
|
|
||||||
/// Add space after the last layout.
|
/// Add space after the last layout.
|
||||||
pub fn add_space(&mut self, space: Size) -> LayoutResult<()> {
|
pub fn add_space(&mut self, space: Size) -> LayoutResult<()> {
|
||||||
if !self.started {
|
if self.dimensions.y + space > self.usable.y {
|
||||||
self.start_new_space()?;
|
self.finish_layout()?;
|
||||||
}
|
self.start_new_space(false);
|
||||||
|
|
||||||
let new_space = match self.ctx.flow {
|
|
||||||
Flow::Vertical => Size2D::with_y(space),
|
|
||||||
Flow::Horizontal => Size2D::with_x(space),
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.overflows(self.dimensions + new_space) {
|
|
||||||
if self.ctx.followup_spaces.is_some() {
|
|
||||||
self.finish_layout(false)?;
|
|
||||||
} else {
|
} else {
|
||||||
return Err(LayoutError::NotEnoughSpace("cannot fit space into stack"));
|
self.dimensions.y += space;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.cursor += new_space;
|
|
||||||
self.dimensions += new_space;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -160,96 +94,76 @@ impl StackLayouter {
|
|||||||
/// Finish the layouting.
|
/// Finish the layouting.
|
||||||
///
|
///
|
||||||
/// The layouter is not consumed by this to prevent ownership problems.
|
/// The layouter is not consumed by this to prevent ownership problems.
|
||||||
/// It should not be used further.
|
/// Nevertheless, it should not be used further.
|
||||||
pub fn finish(&mut self) -> LayoutResult<MultiLayout> {
|
pub fn finish(&mut self) -> LayoutResult<MultiLayout> {
|
||||||
if self.started {
|
if self.include_empty || !self.boxes.is_empty() {
|
||||||
self.finish_layout(false)?;
|
self.finish_layout()?;
|
||||||
}
|
}
|
||||||
Ok(std::mem::replace(&mut self.layouts, MultiLayout::new()))
|
Ok(std::mem::replace(&mut self.layouts, MultiLayout::new()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish the current layout and start a new one in an extra space
|
/// Finish the current layout and start a new one in a new space.
|
||||||
/// (if there is an extra space).
|
|
||||||
///
|
///
|
||||||
/// If `start_new_empty` is true, a new empty layout will be started. Otherwise,
|
/// If `start_new_empty` is true, a new empty layout will be started. Otherwise,
|
||||||
/// the new layout only emerges when new content is added.
|
/// the new layout only appears once new content is added.
|
||||||
pub fn finish_layout(&mut self, start_new_empty: bool) -> LayoutResult<()> {
|
pub fn finish_layout(&mut self) -> LayoutResult<()> {
|
||||||
let actions = std::mem::replace(&mut self.actions, LayoutActionList::new());
|
let mut actions = LayoutActionList::new();
|
||||||
|
|
||||||
|
let space = self.ctx.spaces[self.active_space];
|
||||||
|
let anchor = self.ctx.axes.anchor(self.usable);
|
||||||
|
let factor = if self.ctx.axes.secondary.axis.is_positive() { 1 } else { -1 };
|
||||||
|
let start = space.start();
|
||||||
|
|
||||||
|
for (offset, layout_anchor, layout) in self.boxes.drain(..) {
|
||||||
|
let general_position = anchor - layout_anchor + Size2D::with_y(offset * factor);
|
||||||
|
let position = general_position.specialized(self.ctx.axes) + start;
|
||||||
|
|
||||||
|
actions.add_layout(position, layout);
|
||||||
|
}
|
||||||
|
|
||||||
self.layouts.add(Layout {
|
self.layouts.add(Layout {
|
||||||
dimensions: if self.ctx.shrink_to_fit {
|
dimensions: if space.shrink_to_fit {
|
||||||
self.dimensions.padded(self.space.padding)
|
self.dimensions.padded(space.padding)
|
||||||
} else {
|
} else {
|
||||||
self.space.dimensions
|
space.dimensions
|
||||||
},
|
},
|
||||||
actions: actions.into_vec(),
|
actions: actions.into_vec(),
|
||||||
debug_render: true,
|
debug_render: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
self.started = false;
|
|
||||||
|
|
||||||
if start_new_empty {
|
|
||||||
self.start_new_space()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_new_space(&mut self) -> LayoutResult<()> {
|
/// Set up layouting in the next space. Should be preceded by `finish_layout`.
|
||||||
if let Some(space) = self.ctx.followup_spaces {
|
///
|
||||||
self.started = true;
|
/// If `include_empty` is true, the new empty layout will always be added when
|
||||||
self.space = space;
|
/// finishing this stack. Otherwise, the new layout only appears if new
|
||||||
self.usable = space.usable();
|
/// content is added to it.
|
||||||
self.dimensions = start_dimensions(self.ctx.alignment, space);
|
pub fn start_new_space(&mut self, include_empty: bool) {
|
||||||
self.cursor = start_cursor(self.ctx.alignment, space);
|
self.active_space = (self.active_space + 1).min(self.ctx.spaces.len() - 1);
|
||||||
self.in_extra_space = true;
|
self.usable = self.ctx.spaces[self.active_space].usable().generalized(self.ctx.axes);
|
||||||
Ok(())
|
self.dimensions = start_dimensions(self.usable, self.ctx.axes);
|
||||||
} else {
|
self.include_empty = include_empty;
|
||||||
Err(LayoutError::NotEnoughSpace("no extra space to start"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The remaining space for new layouts.
|
/// The remaining space for new layouts.
|
||||||
pub fn remaining(&self) -> Size2D {
|
pub fn remaining(&self) -> Size2D {
|
||||||
match self.ctx.flow {
|
Size2D::new(self.usable.x, self.usable.y - self.dimensions.y)
|
||||||
Flow::Vertical => Size2D {
|
.specialized(self.ctx.axes)
|
||||||
x: self.usable.x,
|
|
||||||
y: self.usable.y - self.dimensions.y,
|
|
||||||
},
|
|
||||||
Flow::Horizontal => Size2D {
|
|
||||||
x: self.usable.x - self.dimensions.x,
|
|
||||||
y: self.usable.y,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
/// The combined size of the so-far included boxes with the other size.
|
||||||
|
fn size_with(&self, other: Size2D) -> Size2D {
|
||||||
/// Whether the active space of this layouter contains no content.
|
|
||||||
pub fn current_space_is_empty(&self) -> bool {
|
|
||||||
!self.started || self.actions.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn overflows(&self, dimensions: Size2D) -> bool {
|
|
||||||
!self.usable.fits(dimensions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_dimensions(alignment: Alignment, space: LayoutSpace) -> Size2D {
|
|
||||||
match alignment {
|
|
||||||
Alignment::Left => Size2D::zero(),
|
|
||||||
Alignment::Right | Alignment::Center => Size2D::with_x(space.usable().x),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_cursor(alignment: Alignment, space: LayoutSpace) -> Size2D {
|
|
||||||
Size2D {
|
Size2D {
|
||||||
// If left-align, the cursor points to the top-left corner of
|
x: crate::size::max(self.dimensions.x, other.x),
|
||||||
// each box. If we right-align, it points to the top-right
|
y: self.dimensions.y + other.y,
|
||||||
// corner.
|
|
||||||
x: match alignment {
|
|
||||||
Alignment::Left => space.padding.left,
|
|
||||||
Alignment::Right => space.dimensions.x - space.padding.right,
|
|
||||||
Alignment::Center => space.padding.left + (space.usable().x / 2),
|
|
||||||
},
|
|
||||||
y: space.padding.top,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_dimensions(usable: Size2D, axes: LayoutAxes) -> Size2D {
|
||||||
|
Size2D::with_x(match axes.primary.alignment {
|
||||||
|
Alignment::Origin => Size::zero(),
|
||||||
|
Alignment::Center | Alignment::End => usable.x,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -136,7 +136,8 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
|||||||
|
|
||||||
Command::FinishLayout => {
|
Command::FinishLayout => {
|
||||||
self.finish_flex()?;
|
self.finish_flex()?;
|
||||||
self.stack.finish_layout(true)?;
|
self.stack.finish_layout()?;
|
||||||
|
self.stack.start_new_space(true);
|
||||||
self.start_new_flex();
|
self.start_new_flex();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,8 @@ use toddle::query::{FontLoader, FontProvider, SharedFontLoader};
|
|||||||
|
|
||||||
use crate::func::Scope;
|
use crate::func::Scope;
|
||||||
use crate::layout::{layout_tree, LayoutContext, MultiLayout};
|
use crate::layout::{layout_tree, LayoutContext, MultiLayout};
|
||||||
use crate::layout::{Alignment, Flow, LayoutError, LayoutResult, LayoutSpace};
|
use crate::layout::{LayoutAxes, AlignedAxis, Axis, Alignment};
|
||||||
|
use crate::layout::{LayoutError, LayoutResult, LayoutSpace};
|
||||||
use crate::syntax::{SyntaxTree, parse, ParseContext, ParseError, ParseResult};
|
use crate::syntax::{SyntaxTree, parse, ParseContext, ParseError, ParseResult};
|
||||||
use crate::style::{PageStyle, TextStyle};
|
use crate::style::{PageStyle, TextStyle};
|
||||||
|
|
||||||
@ -102,11 +103,13 @@ impl<'p> Typesetter<'p> {
|
|||||||
LayoutContext {
|
LayoutContext {
|
||||||
loader: &self.loader,
|
loader: &self.loader,
|
||||||
style: &self.text_style,
|
style: &self.text_style,
|
||||||
alignment: Alignment::Left,
|
|
||||||
flow: Flow::Vertical,
|
|
||||||
space,
|
space,
|
||||||
followup_spaces: Some(space),
|
followup_spaces: Some(space),
|
||||||
shrink_to_fit: false,
|
shrink_to_fit: false,
|
||||||
|
axes: LayoutAxes {
|
||||||
|
primary: AlignedAxis::new(Axis::LeftToRight, Alignment::Left).unwrap(),
|
||||||
|
secondary: AlignedAxis::new(Axis::TopToBottom, Alignment::Top).unwrap(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -40,6 +40,16 @@ macro_rules! error_type {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether an expression matches a pattern.
|
||||||
|
macro_rules! matches {
|
||||||
|
($val:expr, $($pattern:tt)*) => (
|
||||||
|
match $val {
|
||||||
|
$($pattern)* => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a `Debug` implementation from a `Display` implementation.
|
/// Create a `Debug` implementation from a `Display` implementation.
|
||||||
macro_rules! debug_display {
|
macro_rules! debug_display {
|
||||||
($type:ident) => (
|
($type:ident) => (
|
||||||
|
24
src/size.rs
24
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::LayoutAxes;
|
||||||
|
|
||||||
/// A general space type.
|
/// A general space type.
|
||||||
#[derive(Copy, Clone, PartialEq, Default)]
|
#[derive(Copy, Clone, PartialEq, Default)]
|
||||||
pub struct Size {
|
pub struct Size {
|
||||||
@ -140,6 +142,28 @@ impl Size2D {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the generalized version of this Size2D dependent on
|
||||||
|
/// the given layouting axes, that is:
|
||||||
|
/// - The x coordinate describes the primary axis instead of the horizontal one.
|
||||||
|
/// - The y coordinate describes the secondary axis instead of the vertical one.
|
||||||
|
#[inline]
|
||||||
|
pub fn generalized(&self, axes: LayoutAxes) -> Size2D {
|
||||||
|
if axes.primary.axis.is_horizontal() {
|
||||||
|
*self
|
||||||
|
} else {
|
||||||
|
Size2D { x: self.y, y: self.x }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the specialized version of this generalized Size2D.
|
||||||
|
/// (Inverse to `generalized`).
|
||||||
|
#[inline]
|
||||||
|
pub fn specialized(&self, axes: LayoutAxes) -> Size2D {
|
||||||
|
// In fact, generalized is its own inverse. For reasons of clarity
|
||||||
|
// at the call site, we still have this second function.
|
||||||
|
self.generalized(axes)
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether the given 2D-size fits into this one, that is,
|
/// Whether the given 2D-size fits into this one, that is,
|
||||||
/// both coordinate values are smaller or equal.
|
/// both coordinate values are smaller or equal.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user