summaryrefslogtreecommitdiff
path: root/src/library/layout
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-11-02 14:48:51 +0100
committerLaurenz <laurmaedje@gmail.com>2022-11-02 14:48:51 +0100
commit56342bd972a13ffe21beaf2b87ab7eb1597704b4 (patch)
tree78f9549141e753dde4a938670c54f3fe8695a058 /src/library/layout
parent37ac5d966ebaf97ac79c507028cd5b742b510b89 (diff)
Move layout traits into library
Diffstat (limited to 'src/library/layout')
-rw-r--r--src/library/layout/align.rs21
-rw-r--r--src/library/layout/columns.rs12
-rw-r--r--src/library/layout/container.rs26
-rw-r--r--src/library/layout/flow.rs45
-rw-r--r--src/library/layout/grid.rs32
-rw-r--r--src/library/layout/mod.rs789
-rw-r--r--src/library/layout/pad.rs14
-rw-r--r--src/library/layout/page.rs2
-rw-r--r--src/library/layout/place.rs12
-rw-r--r--src/library/layout/spacing.rs2
-rw-r--r--src/library/layout/stack.rs42
-rw-r--r--src/library/layout/transform.rs36
12 files changed, 888 insertions, 145 deletions
diff --git a/src/library/layout/align.rs b/src/library/layout/align.rs
index f49763b5..2ee565cc 100644
--- a/src/library/layout/align.rs
+++ b/src/library/layout/align.rs
@@ -1,26 +1,23 @@
use crate::library::prelude::*;
use crate::library::text::{HorizontalAlign, ParNode};
-/// Align a node along the layouting axes.
+/// Align content along the layouting axes.
#[derive(Debug, Hash)]
pub struct AlignNode {
- /// How to align the node horizontally and vertically.
+ /// How to align the content horizontally and vertically.
pub aligns: Axes<Option<RawAlign>>,
- /// The node to be aligned.
+ /// The content to be aligned.
pub child: Content,
}
-#[node(Layout)]
+#[node(LayoutBlock)]
impl AlignNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let aligns: Axes<Option<RawAlign>> = args.find()?.unwrap_or_default();
let body: Content = args.expect("body")?;
if let Axes { x: Some(x), y: None } = aligns {
- if body
- .to::<dyn Layout>()
- .map_or(true, |node| node.level() == Level::Inline)
- {
+ if !body.has::<dyn LayoutBlock>() {
return Ok(body.styled(ParNode::ALIGN, HorizontalAlign(x)));
}
}
@@ -29,8 +26,8 @@ impl AlignNode {
}
}
-impl Layout for AlignNode {
- fn layout(
+impl LayoutBlock for AlignNode {
+ fn layout_block(
&self,
world: Tracked<dyn World>,
regions: &Regions,
@@ -62,8 +59,4 @@ impl Layout for AlignNode {
Ok(frames)
}
-
- fn level(&self) -> Level {
- Level::Block
- }
}
diff --git a/src/library/layout/columns.rs b/src/library/layout/columns.rs
index 79d98e11..df259eab 100644
--- a/src/library/layout/columns.rs
+++ b/src/library/layout/columns.rs
@@ -11,7 +11,7 @@ pub struct ColumnsNode {
pub child: Content,
}
-#[node(Layout)]
+#[node(LayoutBlock)]
impl ColumnsNode {
/// The size of the gutter space between each column.
#[property(resolve)]
@@ -26,8 +26,8 @@ impl ColumnsNode {
}
}
-impl Layout for ColumnsNode {
- fn layout(
+impl LayoutBlock for ColumnsNode {
+ fn layout_block(
&self,
world: Tracked<dyn World>,
regions: &Regions,
@@ -66,7 +66,7 @@ impl Layout for ColumnsNode {
// Stitch together the columns for each region.
for region in regions.iter().take(total_regions) {
- // The height should be the parent height if the node shall expand.
+ // The height should be the parent height if we should expand.
// Otherwise its the maximum column height for the frame. In that
// case, the frame is first created with zero height and then
// resized.
@@ -100,10 +100,6 @@ impl Layout for ColumnsNode {
Ok(finished)
}
-
- fn level(&self) -> Level {
- Level::Block
- }
}
/// A column break.
diff --git a/src/library/layout/container.rs b/src/library/layout/container.rs
index 60f9139b..023809d0 100644
--- a/src/library/layout/container.rs
+++ b/src/library/layout/container.rs
@@ -1,15 +1,15 @@
use crate::library::prelude::*;
-/// An inline-level container that sizes content and places it into a paragraph.
+/// An inline-level container that sizes content.
#[derive(Debug, Clone, Hash)]
pub struct BoxNode {
- /// How to size the node horizontally and vertically.
+ /// How to size the content horizontally and vertically.
pub sizing: Axes<Option<Rel<Length>>>,
- /// The node to be sized.
+ /// The content to be sized.
pub child: Content,
}
-#[node(Layout)]
+#[node(LayoutInline)]
impl BoxNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let width = args.named("width")?;
@@ -19,8 +19,8 @@ impl BoxNode {
}
}
-impl Layout for BoxNode {
- fn layout(
+impl LayoutInline for BoxNode {
+ fn layout_inline(
&self,
world: Tracked<dyn World>,
regions: &Regions,
@@ -55,25 +55,21 @@ impl Layout for BoxNode {
Ok(frames)
}
-
- fn level(&self) -> Level {
- Level::Inline
- }
}
/// A block-level container that places content into a separate flow.
#[derive(Debug, Clone, Hash)]
pub struct BlockNode(pub Content);
-#[node(Layout)]
+#[node(LayoutBlock)]
impl BlockNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.eat()?.unwrap_or_default()).pack())
}
}
-impl Layout for BlockNode {
- fn layout(
+impl LayoutBlock for BlockNode {
+ fn layout_block(
&self,
world: Tracked<dyn World>,
regions: &Regions,
@@ -81,8 +77,4 @@ impl Layout for BlockNode {
) -> SourceResult<Vec<Frame>> {
self.0.layout_block(world, regions, styles)
}
-
- fn level(&self) -> Level {
- Level::Block
- }
}
diff --git a/src/library/layout/flow.rs b/src/library/layout/flow.rs
index 01ee9dc9..f4d18699 100644
--- a/src/library/layout/flow.rs
+++ b/src/library/layout/flow.rs
@@ -4,7 +4,7 @@ use super::{AlignNode, PlaceNode, Spacing};
use crate::library::prelude::*;
use crate::library::text::ParNode;
-/// Arrange spacing, paragraphs and other block-level nodes into a flow.
+/// Arrange spacing, paragraphs and block-level nodes into a flow.
///
/// This node is reponsible for layouting both the top-level content flow and
/// the contents of boxes.
@@ -16,17 +16,17 @@ pub struct FlowNode(pub StyleVec<FlowChild>);
pub enum FlowChild {
/// Vertical spacing between other children.
Spacing(Spacing),
- /// An arbitrary block-level node.
- Node(Content),
+ /// Arbitrary block-level content.
+ Block(Content),
/// A column / region break.
Colbreak,
}
-#[node(Layout)]
+#[node(LayoutBlock)]
impl FlowNode {}
-impl Layout for FlowNode {
- fn layout(
+impl LayoutBlock for FlowNode {
+ fn layout_block(
&self,
world: Tracked<dyn World>,
regions: &Regions,
@@ -40,8 +40,8 @@ impl Layout for FlowNode {
FlowChild::Spacing(kind) => {
layouter.layout_spacing(*kind, styles);
}
- FlowChild::Node(ref node) => {
- layouter.layout_node(world, node, styles)?;
+ FlowChild::Block(block) => {
+ layouter.layout_block(world, block, styles)?;
}
FlowChild::Colbreak => {
layouter.finish_region();
@@ -51,10 +51,6 @@ impl Layout for FlowNode {
Ok(layouter.finish())
}
-
- fn level(&self) -> Level {
- Level::Block
- }
}
impl Debug for FlowNode {
@@ -68,7 +64,7 @@ impl Debug for FlowChild {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Spacing(kind) => write!(f, "{:?}", kind),
- Self::Node(node) => node.fmt(f),
+ Self::Block(block) => block.fmt(f),
Self::Colbreak => f.pad("Colbreak"),
}
}
@@ -96,7 +92,7 @@ pub struct FlowLayouter {
used: Size,
/// The sum of fractions in the current region.
fr: Fr,
- /// Spacing and layouted nodes.
+ /// Spacing and layouted blocks.
items: Vec<FlowItem>,
/// Finished frames for previous regions.
finished: Vec<Frame>,
@@ -108,7 +104,7 @@ enum FlowItem {
Absolute(Abs),
/// Fractional spacing between other items.
Fractional(Fr),
- /// A frame for a layouted child node and how to align it.
+ /// A frame for a layouted block and how to align it.
Frame(Frame, Axes<Align>),
/// An absolutely placed frame.
Placed(Frame),
@@ -153,11 +149,11 @@ impl FlowLayouter {
}
}
- /// Layout a node.
- pub fn layout_node(
+ /// Layout a block.
+ pub fn layout_block(
&mut self,
world: Tracked<dyn World>,
- node: &Content,
+ block: &Content,
styles: StyleChain,
) -> SourceResult<()> {
// Don't even try layouting into a full region.
@@ -167,27 +163,28 @@ impl FlowLayouter {
// Placed nodes that are out of flow produce placed items which aren't
// aligned later.
- if let Some(placed) = node.downcast::<PlaceNode>() {
+ if let Some(placed) = block.downcast::<PlaceNode>() {
if placed.out_of_flow() {
- let frame = node.layout_block(world, &self.regions, styles)?.remove(0);
+ let frame = block.layout_block(world, &self.regions, styles)?.remove(0);
self.items.push(FlowItem::Placed(frame));
return Ok(());
}
}
- // How to align the node.
+ // How to align the block.
let aligns = Axes::new(
// For non-expanding paragraphs it is crucial that we align the
// whole paragraph as it is itself aligned.
styles.get(ParNode::ALIGN),
- // Vertical align node alignment is respected by the flow node.
- node.downcast::<AlignNode>()
+ // Vertical align node alignment is respected by the flow.
+ block
+ .downcast::<AlignNode>()
.and_then(|aligned| aligned.aligns.y)
.map(|align| align.resolve(styles))
.unwrap_or(Align::Top),
);
- let frames = node.layout_block(world, &self.regions, styles)?;
+ let frames = block.layout_block(world, &self.regions, styles)?;
let len = frames.len();
for (i, mut frame) in frames.into_iter().enumerate() {
// Set the generic block role.
diff --git a/src/library/layout/grid.rs b/src/library/layout/grid.rs
index 7e5cbbd5..1bb67691 100644
--- a/src/library/layout/grid.rs
+++ b/src/library/layout/grid.rs
@@ -1,17 +1,17 @@
use crate::library::prelude::*;
-/// Arrange nodes in a grid.
+/// Arrange content in a grid.
#[derive(Debug, Hash)]
pub struct GridNode {
/// Defines sizing for content rows and columns.
pub tracks: Axes<Vec<TrackSizing>>,
/// Defines sizing of gutter rows and columns between content.
pub gutter: Axes<Vec<TrackSizing>>,
- /// The nodes to be arranged in a grid.
+ /// The content to be arranged in a grid.
pub cells: Vec<Content>,
}
-#[node(Layout)]
+#[node(LayoutBlock)]
impl GridNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let columns = args.named("columns")?.unwrap_or_default();
@@ -31,8 +31,8 @@ impl GridNode {
}
}
-impl Layout for GridNode {
- fn layout(
+impl LayoutBlock for GridNode {
+ fn layout_block(
&self,
world: Tracked<dyn World>,
regions: &Regions,
@@ -51,10 +51,6 @@ impl Layout for GridNode {
// Measure the columns and layout the grid row-by-row.
layouter.layout()
}
-
- fn level(&self) -> Level {
- Level::Block
- }
}
/// Defines how to size a grid cell along an axis.
@@ -293,7 +289,7 @@ impl<'a> GridLayouter<'a> {
let mut resolved = Abs::zero();
for y in 0 .. self.rows.len() {
- if let Some(node) = self.cell(x, y) {
+ if let Some(cell) = self.cell(x, y) {
let size = Size::new(available, self.regions.base.y);
let mut pod =
Regions::one(size, self.regions.base, Axes::splat(false));
@@ -307,7 +303,7 @@ impl<'a> GridLayouter<'a> {
}
let frame =
- node.layout_block(self.world, &pod, self.styles)?.remove(0);
+ cell.layout_block(self.world, &pod, self.styles)?.remove(0);
resolved.set_max(frame.width());
}
}
@@ -366,7 +362,7 @@ impl<'a> GridLayouter<'a> {
// Determine the size for each region of the row.
for (x, &rcol) in self.rcols.iter().enumerate() {
- if let Some(node) = self.cell(x, y) {
+ if let Some(cell) = self.cell(x, y) {
let mut pod = self.regions.clone();
pod.first.x = rcol;
pod.base.x = rcol;
@@ -376,7 +372,7 @@ impl<'a> GridLayouter<'a> {
pod.base.x = self.regions.base.x;
}
- let mut sizes = node
+ let mut sizes = cell
.layout_block(self.world, &pod, self.styles)?
.into_iter()
.map(|frame| frame.height());
@@ -456,7 +452,7 @@ impl<'a> GridLayouter<'a> {
let mut pos = Point::zero();
for (x, &rcol) in self.rcols.iter().enumerate() {
- if let Some(node) = self.cell(x, y) {
+ if let Some(cell) = self.cell(x, y) {
let size = Size::new(rcol, height);
// Set the base to the region's base for auto rows and to the
@@ -466,7 +462,7 @@ impl<'a> GridLayouter<'a> {
.select(self.regions.base, size);
let pod = Regions::one(size, base, Axes::splat(true));
- let frame = node.layout_block(self.world, &pod, self.styles)?.remove(0);
+ let frame = cell.layout_block(self.world, &pod, self.styles)?.remove(0);
match frame.role() {
Some(Role::ListLabel | Role::ListItemBody) => {
output.apply_role(Role::ListItem)
@@ -504,7 +500,7 @@ impl<'a> GridLayouter<'a> {
// Layout the row.
let mut pos = Point::zero();
for (x, &rcol) in self.rcols.iter().enumerate() {
- if let Some(node) = self.cell(x, y) {
+ if let Some(cell) = self.cell(x, y) {
pod.first.x = rcol;
pod.base.x = rcol;
@@ -514,7 +510,7 @@ impl<'a> GridLayouter<'a> {
}
// Push the layouted frames into the individual output frames.
- let frames = node.layout_block(self.world, &pod, self.styles)?;
+ let frames = cell.layout_block(self.world, &pod, self.styles)?;
for (output, frame) in outputs.iter_mut().zip(frames) {
match frame.role() {
Some(Role::ListLabel | Role::ListItemBody) => {
@@ -578,7 +574,7 @@ impl<'a> GridLayouter<'a> {
Ok(())
}
- /// Get the node in the cell in column `x` and row `y`.
+ /// Get the content of the cell in column `x` and row `y`.
///
/// Returns `None` if it's a gutter cell.
#[track_caller]
diff --git a/src/library/layout/mod.rs b/src/library/layout/mod.rs
index 02276f22..000cb212 100644
--- a/src/library/layout/mod.rs
+++ b/src/library/layout/mod.rs
@@ -23,3 +23,792 @@ pub use place::*;
pub use spacing::*;
pub use stack::*;
pub use transform::*;
+
+use std::mem;
+
+use comemo::Tracked;
+use typed_arena::Arena;
+
+use crate::diag::SourceResult;
+use crate::frame::Frame;
+use crate::geom::*;
+use crate::library::structure::{DocNode, ListItem, ListNode, DESC, ENUM, LIST};
+use crate::library::text::{
+ LinebreakNode, ParChild, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode,
+};
+use crate::model::{
+ capability, Barrier, Content, Interruption, Node, SequenceNode, Show, StyleChain,
+ StyleEntry, StyleMap, StyleVec, StyleVecBuilder, StyledNode, Target,
+};
+use crate::World;
+
+/// The root-level layout.
+#[capability]
+pub trait Layout: 'static + Sync + Send {
+ /// Layout into one frame per page.
+ fn layout(&self, world: Tracked<dyn World>) -> SourceResult<Vec<Frame>>;
+}
+
+impl Layout for Content {
+ #[comemo::memoize]
+ fn layout(&self, world: Tracked<dyn World>) -> SourceResult<Vec<Frame>> {
+ let styles = StyleChain::with_root(&world.config().styles);
+ let scratch = Scratch::default();
+
+ let mut builder = Builder::new(world, &scratch, true);
+ builder.accept(self, styles)?;
+
+ let (doc, shared) = builder.into_doc(styles)?;
+ doc.layout(world, shared)
+ }
+}
+
+/// Block-level layout.
+#[capability]
+pub trait LayoutBlock: 'static + Sync + Send {
+ /// Layout into one frame per region.
+ fn layout_block(
+ &self,
+ world: Tracked<dyn World>,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> SourceResult<Vec<Frame>>;
+}
+
+impl LayoutBlock for Content {
+ #[comemo::memoize]
+ fn layout_block(
+ &self,
+ world: Tracked<dyn World>,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> SourceResult<Vec<Frame>> {
+ if let Some(node) = self.to::<dyn LayoutBlock>() {
+ let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
+ let styles = barrier.chain(&styles);
+ return node.layout_block(world, regions, styles);
+ }
+
+ let scratch = Scratch::default();
+ let mut builder = Builder::new(world, &scratch, false);
+ builder.accept(self, styles)?;
+ let (flow, shared) = builder.into_flow(styles)?;
+ flow.layout_block(world, regions, shared)
+ }
+}
+
+/// Inline-level layout.
+#[capability]
+pub trait LayoutInline: 'static + Sync + Send {
+ /// Layout into a single frame.
+ fn layout_inline(
+ &self,
+ world: Tracked<dyn World>,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> SourceResult<Vec<Frame>>;
+}
+
+impl LayoutInline for Content {
+ #[comemo::memoize]
+ fn layout_inline(
+ &self,
+ world: Tracked<dyn World>,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> SourceResult<Vec<Frame>> {
+ if let Some(node) = self.to::<dyn LayoutInline>() {
+ let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
+ let styles = barrier.chain(&styles);
+ return node.layout_inline(world, regions, styles);
+ }
+
+ if let Some(node) = self.to::<dyn LayoutBlock>() {
+ let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
+ let styles = barrier.chain(&styles);
+ return node.layout_block(world, regions, styles);
+ }
+
+ let scratch = Scratch::default();
+ let mut builder = Builder::new(world, &scratch, false);
+ builder.accept(self, styles)?;
+ let (flow, shared) = builder.into_flow(styles)?;
+ flow.layout_block(world, regions, shared)
+ }
+}
+
+/// A sequence of regions to layout into.
+#[derive(Debug, Clone, Hash)]
+pub struct Regions {
+ /// The (remaining) size of the first region.
+ pub first: Size,
+ /// The base size for relative sizing.
+ pub base: Size,
+ /// The height of followup regions. The width is the same for all regions.
+ pub backlog: Vec<Abs>,
+ /// The height of the final region that is repeated once the backlog is
+ /// drained. The width is the same for all regions.
+ pub last: Option<Abs>,
+ /// Whether nodes should expand to fill the regions instead of shrinking to
+ /// fit the content.
+ pub expand: Axes<bool>,
+}
+
+impl Regions {
+ /// Create a new region sequence with exactly one region.
+ pub fn one(size: Size, base: Size, expand: Axes<bool>) -> Self {
+ Self {
+ first: size,
+ base,
+ backlog: vec![],
+ last: None,
+ expand,
+ }
+ }
+
+ /// Create a new sequence of same-size regions that repeats indefinitely.
+ pub fn repeat(size: Size, base: Size, expand: Axes<bool>) -> Self {
+ Self {
+ first: size,
+ base,
+ backlog: vec![],
+ last: Some(size.y),
+ expand,
+ }
+ }
+
+ /// Create new regions where all sizes are mapped with `f`.
+ ///
+ /// Note that since all regions must have the same width, the width returned
+ /// by `f` is ignored for the backlog and the final region.
+ pub fn map<F>(&self, mut f: F) -> Self
+ where
+ F: FnMut(Size) -> Size,
+ {
+ let x = self.first.x;
+ Self {
+ first: f(self.first),
+ base: f(self.base),
+ backlog: self.backlog.iter().map(|&y| f(Size::new(x, y)).y).collect(),
+ last: self.last.map(|y| f(Size::new(x, y)).y),
+ expand: self.expand,
+ }
+ }
+
+ /// Whether the first region is full and a region break is called for.
+ pub fn is_full(&self) -> bool {
+ Abs::zero().fits(self.first.y) && !self.in_last()
+ }
+
+ /// Whether the first region is the last usable region.
+ ///
+ /// If this is true, calling `next()` will have no effect.
+ pub fn in_last(&self) -> bool {
+ self.backlog.len() == 0 && self.last.map_or(true, |height| self.first.y == height)
+ }
+
+ /// Advance to the next region if there is any.
+ pub fn next(&mut self) {
+ if let Some(height) = (!self.backlog.is_empty())
+ .then(|| self.backlog.remove(0))
+ .or(self.last)
+ {
+ self.first.y = height;
+ self.base.y = height;
+ }
+ }
+
+ /// An iterator that returns the sizes of the first and all following
+ /// regions, equivalently to what would be produced by calling
+ /// [`next()`](Self::next) repeatedly until all regions are exhausted.
+ /// This iterater may be infinite.
+ pub fn iter(&self) -> impl Iterator<Item = Size> + '_ {
+ let first = std::iter::once(self.first);
+ let backlog = self.backlog.iter();
+ let last = self.last.iter().cycle();
+ first.chain(backlog.chain(last).map(|&h| Size::new(self.first.x, h)))
+ }
+}
+
+/// Builds a document or a flow node from content.
+struct Builder<'a> {
+ /// The core context.
+ world: Tracked<'a, dyn World>,
+ /// Scratch arenas for building.
+ scratch: &'a Scratch<'a>,
+ /// The current document building state.
+ doc: Option<DocBuilder<'a>>,
+ /// The current flow building state.
+ flow: FlowBuilder<'a>,
+ /// The current paragraph building state.
+ par: ParBuilder<'a>,
+ /// The current list building state.
+ list: ListBuilder<'a>,
+}
+
+/// Temporary storage arenas for building.
+#[derive(Default)]
+struct Scratch<'a> {
+ /// An arena where intermediate style chains are stored.
+ styles: Arena<StyleChain<'a>>,
+ /// An arena where intermediate content resulting from show rules is stored.
+ templates: Arena<Content>,
+}
+
+impl<'a> Builder<'a> {
+ pub fn new(
+ world: Tracked<'a, dyn World>,
+ scratch: &'a Scratch<'a>,
+ top: bool,
+ ) -> Self {
+ Self {
+ world,
+ scratch,
+ doc: top.then(|| DocBuilder::default()),
+ flow: FlowBuilder::default(),
+ par: ParBuilder::default(),
+ list: ListBuilder::default(),
+ }
+ }
+
+ pub fn into_doc(
+ mut self,
+ styles: StyleChain<'a>,
+ ) -> SourceResult<(DocNode, StyleChain<'a>)> {
+ self.interrupt(Interruption::Page, styles, true)?;
+ let (pages, shared) = self.doc.unwrap().pages.finish();
+ Ok((DocNode(pages), shared))
+ }
+
+ pub fn into_flow(
+ mut self,
+ styles: StyleChain<'a>,
+ ) -> SourceResult<(FlowNode, StyleChain<'a>)> {
+ self.interrupt(Interruption::Par, styles, false)?;
+ let (children, shared) = self.flow.0.finish();
+ Ok((FlowNode(children), shared))
+ }
+
+ pub fn accept(
+ &mut self,
+ content: &'a Content,
+ styles: StyleChain<'a>,
+ ) -> SourceResult<()> {
+ if let Some(text) = content.downcast::<TextNode>() {
+ if let Some(realized) = styles.apply(self.world, Target::Text(&text.0))? {
+ let stored = self.scratch.templates.alloc(realized);
+ return self.accept(stored, styles);
+ }
+ } else if let Some(styled) = content.downcast::<StyledNode>() {
+ return self.styled(styled, styles);
+ } else if let Some(seq) = content.downcast::<SequenceNode>() {
+ return self.sequence(seq, styles);
+ } else if content.has::<dyn Show>() {
+ if self.show(&content, styles)? {
+ return Ok(());
+ }
+ }
+
+ if self.list.accept(content, styles) {
+ return Ok(());
+ }
+
+ self.interrupt(Interruption::List, styles, false)?;
+
+ if content.is::<ListItem>() {
+ self.list.accept(content, styles);
+ return Ok(());
+ }
+
+ if self.par.accept(content, styles) {
+ return Ok(());
+ }
+
+ self.interrupt(Interruption::Par, styles, false)?;
+
+ if self.flow.accept(content, styles) {
+ return Ok(());
+ }
+
+ let keep = content
+ .downcast::<PagebreakNode>()
+ .map_or(false, |pagebreak| !pagebreak.weak);
+ self.interrupt(Interruption::Page, styles, keep)?;
+
+ if let Some(doc) = &mut self.doc {
+ doc.accept(content, styles);
+ }
+
+ // We might want to issue a warning or error for content that wasn't
+ // handled (e.g. a pagebreak in a flow building process). However, we
+ // don't have the spans here at the moment.
+ Ok(())
+ }
+
+ fn show(
+ &mut self,
+ content: &'a Content,
+ styles: StyleChain<'a>,
+ ) -> SourceResult<bool> {
+ if let Some(mut realized) = styles.apply(self.world, Target::Node(content))? {
+ let mut map = StyleMap::new();
+ let barrier = Barrier::new(content.id());
+ map.push(StyleEntry::Barrier(barrier));
+ map.push(StyleEntry::Barrier(barrier));
+ realized = realized.styled_with_map(map);
+ let stored = self.scratch.templates.alloc(realized);
+ self.accept(stored, styles)?;
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+ }
+
+ fn styled(
+ &mut self,
+ styled: &'a StyledNode,
+ styles: StyleChain<'a>,
+ ) -> SourceResult<()> {
+ let stored = self.scratch.styles.alloc(styles);
+ let styles = styled.map.chain(stored);
+ let intr = styled.map.interruption();
+
+ if let Some(intr) = intr {
+ self.interrupt(intr, styles, false)?;
+ }
+
+ self.accept(&styled.sub, styles)?;
+
+ if let Some(intr) = intr {
+ self.interrupt(intr, styles, true)?;
+ }
+
+ Ok(())
+ }
+
+ fn interrupt(
+ &mut self,
+ intr: Interruption,
+ styles: StyleChain<'a>,
+ keep: bool,
+ ) -> SourceResult<()> {
+ if intr >= Interruption::List && !self.list.is_empty() {
+ mem::take(&mut self.list).finish(self)?;
+ }
+
+ if intr >= Interruption::Par {
+ if !self.par.is_empty() {
+ mem::take(&mut self.par).finish(self);
+ }
+ }
+
+ if intr >= Interruption::Page {
+ if let Some(doc) = &mut self.doc {
+ if !self.flow.is_empty() || (doc.keep_next && keep) {
+ mem::take(&mut self.flow).finish(doc, styles);
+ }
+ doc.keep_next = !keep;
+ }
+ }
+
+ Ok(())
+ }
+
+ fn sequence(
+ &mut self,
+ seq: &'a SequenceNode,
+ styles: StyleChain<'a>,
+ ) -> SourceResult<()> {
+ for content in &seq.0 {
+ self.accept(content, styles)?;
+ }
+ Ok(())
+ }
+}
+
+/// Accepts pagebreaks and pages.
+struct DocBuilder<'a> {
+ /// The page runs built so far.
+ pages: StyleVecBuilder<'a, PageNode>,
+ /// Whether to keep a following page even if it is empty.
+ keep_next: bool,
+}
+
+impl<'a> DocBuilder<'a> {
+ fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) {
+ if let Some(pagebreak) = content.downcast::<PagebreakNode>() {
+ self.keep_next = !pagebreak.weak;
+ }
+
+ if let Some(page) = content.downcast::<PageNode>() {
+ self.pages.push(page.clone(), styles);
+ self.keep_next = false;
+ }
+ }
+}
+
+impl Default for DocBuilder<'_> {
+ fn default() -> Self {
+ Self {
+ pages: StyleVecBuilder::new(),
+ keep_next: true,
+ }
+ }
+}
+
+/// Accepts flow content.
+#[derive(Default)]
+struct FlowBuilder<'a>(CollapsingBuilder<'a, FlowChild>);
+
+impl<'a> FlowBuilder<'a> {
+ fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
+ // Weak flow elements:
+ // Weakness | Element
+ // 0 | weak colbreak
+ // 1 | weak fractional spacing
+ // 2 | weak spacing
+ // 3 | generated weak spacing
+ // 4 | generated weak fractional spacing
+ // 5 | par spacing
+
+ if let Some(_) = content.downcast::<ParbreakNode>() {
+ /* Nothing to do */
+ } else if let Some(colbreak) = content.downcast::<ColbreakNode>() {
+ if colbreak.weak {
+ self.0.weak(FlowChild::Colbreak, styles, 0);
+ } else {
+ self.0.destructive(FlowChild::Colbreak, styles);
+ }
+ } else if let Some(vertical) = content.downcast::<VNode>() {
+ let child = FlowChild::Spacing(vertical.amount);
+ let frac = vertical.amount.is_fractional();
+ if vertical.weak {
+ let weakness = 1 + u8::from(frac) + 2 * u8::from(vertical.generated);
+ self.0.weak(child, styles, weakness);
+ } else if frac {
+ self.0.destructive(child, styles);
+ } else {
+ self.0.ignorant(child, styles);
+ }
+ } else if content.has::<dyn LayoutBlock>() {
+ let child = FlowChild::Block(content.clone());
+ if content.is::<PlaceNode>() {
+ self.0.ignorant(child, styles);
+ } else {
+ self.0.supportive(child, styles);
+ }
+ } else {
+ return false;
+ }
+
+ true
+ }
+
+ fn par(&mut self, par: ParNode, styles: StyleChain<'a>, indent: bool) {
+ let amount = if indent && !styles.get(ParNode::SPACING_AND_INDENT) {
+ styles.get(ParNode::LEADING).into()
+ } else {
+ styles.get(ParNode::SPACING).into()
+ };
+
+ self.0.weak(FlowChild::Spacing(amount), styles, 5);
+ self.0.supportive(FlowChild::Block(par.pack()), styles);
+ self.0.weak(FlowChild::Spacing(amount), styles, 5);
+ }
+
+ fn finish(self, doc: &mut DocBuilder<'a>, styles: StyleChain<'a>) {
+ let (flow, shared) = self.0.finish();
+ let styles = if flow.is_empty() { styles } else { shared };
+ let node = PageNode(FlowNode(flow).pack());
+ doc.pages.push(node, styles);
+ }
+
+ fn is_empty(&self) -> bool {
+ self.0.is_empty()
+ }
+}
+
+/// Accepts paragraph content.
+#[derive(Default)]
+struct ParBuilder<'a>(CollapsingBuilder<'a, ParChild>);
+
+impl<'a> ParBuilder<'a> {
+ fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
+ // Weak par elements:
+ // Weakness | Element
+ // 0 | weak fractional spacing
+ // 1 | weak spacing
+ // 2 | space
+
+ if content.is::<SpaceNode>() {
+ self.0.weak(ParChild::Text(' '.into()), styles, 2);
+ } else if let Some(linebreak) = content.downcast::<LinebreakNode>() {
+ let c = if linebreak.justify { '\u{2028}' } else { '\n' };
+ self.0.destructive(ParChild::Text(c.into()), styles);
+ } else if let Some(horizontal) = content.downcast::<HNode>() {
+ let child = ParChild::Spacing(horizontal.amount);
+ let frac = horizontal.amount.is_fractional();
+ if horizontal.weak {
+ let weakness = u8::from(!frac);
+ self.0.weak(child, styles, weakness);
+ } else if frac {
+ self.0.destructive(child, styles);
+ } else {
+ self.0.ignorant(child, styles);
+ }
+ } else if let Some(quote) = content.downcast::<SmartQuoteNode>() {
+ self.0.supportive(ParChild::Quote { double: quote.double }, styles);
+ } else if let Some(text) = content.downcast::<TextNode>() {
+ self.0.supportive(ParChild::Text(text.0.clone()), styles);
+ } else if content.has::<dyn LayoutInline>() {
+ self.0.supportive(ParChild::Inline(content.clone()), styles);
+ } else {
+ return false;
+ }
+
+ true
+ }
+
+ fn finish(self, parent: &mut Builder<'a>) {
+ let (mut children, shared) = self.0.finish();
+ if children.is_empty() {
+ return;
+ }
+
+ // Paragraph indent should only apply if the paragraph starts with
+ // text and follows directly after another paragraph.
+ let indent = shared.get(ParNode::INDENT);
+ if !indent.is_zero()
+ && children
+ .items()
+ .find_map(|child| match child {
+ ParChild::Spacing(_) => None,
+ ParChild::Text(_) | ParChild::Quote { .. } => Some(true),
+ ParChild::Inline(_) => Some(false),
+ })
+ .unwrap_or_default()
+ && parent
+ .flow
+ .0
+ .items()
+ .rev()
+ .find_map(|child| match child {
+ FlowChild::Spacing(_) => None,
+ FlowChild::Block(content) => Some(content.is::<ParNode>()),
+ FlowChild::Colbreak => Some(false),
+ })
+ .unwrap_or_default()
+ {
+ children.push_front(ParChild::Spacing(indent.into()));
+ }
+
+ parent.flow.par(ParNode(children), shared, !indent.is_zero());
+ }
+
+ fn is_empty(&self) -> bool {
+ self.0.is_empty()
+ }
+}
+
+/// Accepts list / enum items, spaces, paragraph breaks.
+struct ListBuilder<'a> {
+ /// The list items collected so far.
+ items: StyleVecBuilder<'a, ListItem>,
+ /// Whether the list contains no paragraph breaks.
+ tight: bool,
+ /// Whether the list can be attached.
+ attachable: bool,
+ /// Trailing content for which it is unclear whether it is part of the list.
+ staged: Vec<(&'a Content, StyleChain<'a>)>,
+}
+
+impl<'a> ListBuilder<'a> {
+ fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
+ if self.items.is_empty() {
+ if content.is::<ParbreakNode>() {
+ self.attachable = false;
+ } else if !content.is::<SpaceNode>() && !content.is::<ListItem>() {
+ self.attachable = true;
+ }
+ }
+
+ if let Some(item) = content.downcast::<ListItem>() {
+ if self
+ .items
+ .items()
+ .next()
+ .map_or(true, |first| item.kind() == first.kind())
+ {
+ self.items.push(item.clone(), styles);
+ self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakNode>());
+ } else {
+ return false;
+ }
+ } else if !self.items.is_empty()
+ && (content.is::<SpaceNode>() || content.is::<ParbreakNode>())
+ {
+ self.staged.push((content, styles));
+ } else {
+ return false;
+ }
+
+ true
+ }
+
+ fn finish(self, parent: &mut Builder<'a>) -> SourceResult<()> {
+ let (items, shared) = self.items.finish();
+ let kind = match items.items().next() {
+ Some(item) => item.kind(),
+ None => return Ok(()),
+ };
+
+ let tight = self.tight;
+ let attached = tight && self.attachable;
+ let content = match kind {
+ LIST => ListNode::<LIST> { tight, attached, items }.pack(),
+ ENUM => ListNode::<ENUM> { tight, attached, items }.pack(),
+ DESC | _ => ListNode::<DESC> { tight, attached, items }.pack(),
+ };
+
+ let stored = parent.scratch.templates.alloc(content);
+ parent.accept(stored, shared)?;
+
+ for (content, styles) in self.staged {
+ parent.accept(content, styles)?;
+ }
+
+ parent.list.attachable = true;
+
+ Ok(())
+ }
+
+ fn is_empty(&self) -> bool {
+ self.items.is_empty()
+ }
+}
+
+impl Default for ListBuilder<'_> {
+ fn default() -> Self {
+ Self {
+ items: StyleVecBuilder::default(),
+ tight: true,
+ attachable: true,
+ staged: vec![],
+ }
+ }
+}
+
+/// A wrapper around a [`StyleVecBuilder`] that allows to collapse items.
+struct CollapsingBuilder<'a, T> {
+ /// The internal builder.
+ builder: StyleVecBuilder<'a, T>,
+ /// Staged weak and ignorant items that we can't yet commit to the builder.
+ /// The option is `Some(_)` for weak items and `None` for ignorant items.
+ staged: Vec<(T, StyleChain<'a>, Option<u8>)>,
+ /// What the last non-ignorant item was.
+ last: Last,
+}
+
+/// What the last non-ignorant item was.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+enum Last {
+ Weak,
+ Destructive,
+ Supportive,
+}
+
+impl<'a, T> CollapsingBuilder<'a, T> {
+ /// Create a new style-vec builder.
+ pub fn new() -> Self {
+ Self {
+ builder: StyleVecBuilder::new(),
+ staged: vec![],
+ last: Last::Destructive,
+ }
+ }
+
+ /// Whether the builder is empty.
+ pub fn is_empty(&self) -> bool {
+ self.builder.is_empty() && self.staged.is_empty()
+ }
+
+ /// Can only exist when there is at least one supportive item to its left
+ /// and to its right, with no destructive items in between. There may be
+ /// ignorant items in between in both directions.
+ ///
+ /// Between weak items, there may be at least one per layer and among the
+ /// candidates the strongest one (smallest `weakness`) wins. When tied,
+ /// the one that compares larger through `PartialOrd` wins.
+ pub fn weak(&mut self, item: T, styles: StyleChain<'a>, weakness: u8)
+ where
+ T: PartialOrd,
+ {
+ if self.last == Last::Destructive {
+ return;
+ }
+
+ if self.last == Last::Weak {
+ if let Some(i) =
+ self.staged.iter().position(|(prev_item, _, prev_weakness)| {
+ prev_weakness.map_or(false, |prev_weakness| {
+ weakness < prev_weakness
+ || (weakness == prev_weakness && item > *prev_item)
+ })
+ })
+ {
+ self.staged.remove(i);
+ } else {
+ return;
+ }
+ }
+
+ self.staged.push((item, styles, Some(weakness)));
+ self.last = Last::Weak;
+ }
+
+ /// Forces nearby weak items to collapse.
+ pub fn destructive(&mut self, item: T, styles: StyleChain<'a>) {
+ self.flush(false);
+ self.builder.push(item, styles);
+ self.last = Last::Destructive;
+ }
+
+ /// Allows nearby weak items to exist.
+ pub fn supportive(&mut self, item: T, styles: StyleChain<'a>) {
+ self.flush(true);
+ self.builder.push(item, styles);
+ self.last = Last::Supportive;
+ }
+
+ /// Has no influence on other items.
+ pub fn ignorant(&mut self, item: T, styles: StyleChain<'a>) {
+ self.staged.push((item, styles, None));
+ }
+
+ /// Iterate over the contained items.
+ pub fn items(&self) -> impl DoubleEndedIterator<Item = &T> {
+ self.builder.items().chain(self.staged.iter().map(|(item, ..)| item))
+ }
+
+ /// Return the finish style vec and the common prefix chain.
+ pub fn finish(mut self) -> (StyleVec<T>, StyleChain<'a>) {
+ self.flush(false);
+ self.builder.finish()
+ }
+
+ /// Push the staged items, filtering out weak items if `supportive` is
+ /// false.
+ fn flush(&mut self, supportive: bool) {
+ for (item, styles, meta) in self.staged.drain(..) {
+ if supportive || meta.is_none() {
+ self.builder.push(item, styles);
+ }
+ }
+ }
+}
+
+impl<'a, T> Default for CollapsingBuilder<'a, T> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
diff --git a/src/library/layout/pad.rs b/src/library/layout/pad.rs
index effdd5f8..920660d6 100644
--- a/src/library/layout/pad.rs
+++ b/src/library/layout/pad.rs
@@ -1,15 +1,15 @@
use crate::library::prelude::*;
-/// Pad a node at the sides.
+/// Pad content at the sides.
#[derive(Debug, Hash)]
pub struct PadNode {
/// The amount of padding.
pub padding: Sides<Rel<Length>>,
- /// The child node whose sides to pad.
+ /// The content whose sides to pad.
pub child: Content,
}
-#[node(Layout)]
+#[node(LayoutBlock)]
impl PadNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let all = args.named("rest")?.or(args.find()?);
@@ -25,8 +25,8 @@ impl PadNode {
}
}
-impl Layout for PadNode {
- fn layout(
+impl LayoutBlock for PadNode {
+ fn layout_block(
&self,
world: Tracked<dyn World>,
regions: &Regions,
@@ -51,10 +51,6 @@ impl Layout for PadNode {
Ok(frames)
}
-
- fn level(&self) -> Level {
- Level::Block
- }
}
/// Shrink a size by padding relative to the size itself.
diff --git a/src/library/layout/page.rs b/src/library/layout/page.rs
index f5821ae6..8d081749 100644
--- a/src/library/layout/page.rs
+++ b/src/library/layout/page.rs
@@ -80,7 +80,7 @@ impl PageNode {
let mut child = self.0.clone();
- // Realize columns with columns node.
+ // Realize columns.
let columns = styles.get(Self::COLUMNS);
if columns.get() > 1 {
child = ColumnsNode { columns, child: self.0.clone() }.pack();
diff --git a/src/library/layout/place.rs b/src/library/layout/place.rs
index 42ab0fba..ee38ebe6 100644
--- a/src/library/layout/place.rs
+++ b/src/library/layout/place.rs
@@ -1,11 +1,11 @@
use super::AlignNode;
use crate::library::prelude::*;
-/// Place a node at an absolute position.
+/// Place content at an absolute position.
#[derive(Debug, Hash)]
pub struct PlaceNode(pub Content);
-#[node(Layout)]
+#[node(LayoutBlock)]
impl PlaceNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let aligns = args.find()?.unwrap_or(Axes::with_x(Some(RawAlign::Start)));
@@ -16,8 +16,8 @@ impl PlaceNode {
}
}
-impl Layout for PlaceNode {
- fn layout(
+impl LayoutBlock for PlaceNode {
+ fn layout_block(
&self,
world: Tracked<dyn World>,
regions: &Regions,
@@ -42,10 +42,6 @@ impl Layout for PlaceNode {
Ok(frames)
}
-
- fn level(&self) -> Level {
- Level::Block
- }
}
impl PlaceNode {
diff --git a/src/library/layout/spacing.rs b/src/library/layout/spacing.rs
index 6df5cde5..c410eee7 100644
--- a/src/library/layout/spacing.rs
+++ b/src/library/layout/spacing.rs
@@ -78,7 +78,7 @@ castable! {
Value::Fraction(v) => Self::Fractional(v),
}
-/// Spacing around and between block-level nodes, relative to paragraph spacing.
+/// Spacing around and between blocks, relative to paragraph spacing.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct BlockSpacing(Rel<Length>);
diff --git a/src/library/layout/stack.rs b/src/library/layout/stack.rs
index 9ea7965d..e1e70de9 100644
--- a/src/library/layout/stack.rs
+++ b/src/library/layout/stack.rs
@@ -3,7 +3,7 @@ use crate::library::prelude::*;
use crate::library::text::ParNode;
use crate::model::StyledNode;
-/// Arrange nodes and spacing along an axis.
+/// Arrange content and spacing along an axis.
#[derive(Debug, Hash)]
pub struct StackNode {
/// The stacking direction.
@@ -14,7 +14,7 @@ pub struct StackNode {
pub children: Vec<StackChild>,
}
-#[node(Layout)]
+#[node(LayoutBlock)]
impl StackNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self {
@@ -26,8 +26,8 @@ impl StackNode {
}
}
-impl Layout for StackNode {
- fn layout(
+impl LayoutBlock for StackNode {
+ fn layout_block(
&self,
world: Tracked<dyn World>,
regions: &Regions,
@@ -35,7 +35,7 @@ impl Layout for StackNode {
) -> SourceResult<Vec<Frame>> {
let mut layouter = StackLayouter::new(self.dir, regions, styles);
- // Spacing to insert before the next node.
+ // Spacing to insert before the next block.
let mut deferred = None;
for child in &self.children {
@@ -44,12 +44,12 @@ impl Layout for StackNode {
layouter.layout_spacing(*kind);
deferred = None;
}
- StackChild::Node(node) => {
+ StackChild::Block(block) => {
if let Some(kind) = deferred {
layouter.layout_spacing(kind);
}
- layouter.layout_node(world, node, styles)?;
+ layouter.layout_block(world, block, styles)?;
deferred = self.spacing;
}
}
@@ -57,26 +57,22 @@ impl Layout for StackNode {
Ok(layouter.finish())
}
-
- fn level(&self) -> Level {
- Level::Block
- }
}
/// A child of a stack node.
#[derive(Hash)]
pub enum StackChild {
- /// Spacing between other nodes.
+ /// Spacing between other children.
Spacing(Spacing),
- /// An arbitrary node.
- Node(Content),
+ /// Arbitrary block-level content.
+ Block(Content),
}
impl Debug for StackChild {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Spacing(kind) => kind.fmt(f),
- Self::Node(node) => node.fmt(f),
+ Self::Block(block) => block.fmt(f),
}
}
}
@@ -88,7 +84,7 @@ castable! {
Value::Ratio(v) => Self::Spacing(Spacing::Relative(v.into())),
Value::Relative(v) => Self::Spacing(Spacing::Relative(v)),
Value::Fraction(v) => Self::Spacing(Spacing::Fractional(v)),
- Value::Content(v) => Self::Node(v),
+ Value::Content(v) => Self::Block(v),
}
/// Performs stack layout.
@@ -122,7 +118,7 @@ enum StackItem {
Absolute(Abs),
/// Fractional spacing between other items.
Fractional(Fr),
- /// A frame for a layouted child node.
+ /// A frame for a layouted block.
Frame(Frame, Align),
}
@@ -171,11 +167,11 @@ impl<'a> StackLayouter<'a> {
}
}
- /// Layout an arbitrary node.
- pub fn layout_node(
+ /// Layout an arbitrary block.
+ pub fn layout_block(
&mut self,
world: Tracked<dyn World>,
- node: &Content,
+ block: &Content,
styles: StyleChain,
) -> SourceResult<()> {
if self.regions.is_full() {
@@ -184,12 +180,12 @@ impl<'a> StackLayouter<'a> {
// Block-axis alignment of the `AlignNode` is respected
// by the stack node.
- let align = node
+ let align = block
.downcast::<AlignNode>()
.and_then(|node| node.aligns.get(self.axis))
.map(|align| align.resolve(styles))
.unwrap_or_else(|| {
- if let Some(styled) = node.downcast::<StyledNode>() {
+ if let Some(styled) = block.downcast::<StyledNode>() {
let map = &styled.map;
if map.contains(ParNode::ALIGN) {
return StyleChain::with_root(map).get(ParNode::ALIGN);
@@ -199,7 +195,7 @@ impl<'a> StackLayouter<'a> {
self.dir.start().into()
});
- let frames = node.layout_block(world, &self.regions, styles)?;
+ let frames = block.layout_block(world, &self.regions, styles)?;
let len = frames.len();
for (i, mut frame) in frames.into_iter().enumerate() {
// Set the generic block role.
diff --git a/src/library/layout/transform.rs b/src/library/layout/transform.rs
index 061efa6b..a73a1827 100644
--- a/src/library/layout/transform.rs
+++ b/src/library/layout/transform.rs
@@ -1,16 +1,16 @@
use crate::geom::Transform;
use crate::library::prelude::*;
-/// Move a node without affecting layout.
+/// Move content without affecting layout.
#[derive(Debug, Hash)]
pub struct MoveNode {
- /// The offset by which to move the node.
+ /// The offset by which to move the content.
pub delta: Axes<Rel<Length>>,
- /// The node whose contents should be moved.
+ /// The content that should be moved.
pub child: Content,
}
-#[node(Layout)]
+#[node(LayoutInline)]
impl MoveNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let dx = args.named("dx")?.unwrap_or_default();
@@ -23,8 +23,8 @@ impl MoveNode {
}
}
-impl Layout for MoveNode {
- fn layout(
+impl LayoutInline for MoveNode {
+ fn layout_inline(
&self,
world: Tracked<dyn World>,
regions: &Regions,
@@ -40,28 +40,24 @@ impl Layout for MoveNode {
Ok(frames)
}
-
- fn level(&self) -> Level {
- Level::Inline
- }
}
-/// Transform a node without affecting layout.
+/// Transform content without affecting layout.
#[derive(Debug, Hash)]
pub struct TransformNode<const T: TransformKind> {
- /// Transformation to apply to the contents.
+ /// Transformation to apply to the content.
pub transform: Transform,
- /// The node whose contents should be transformed.
+ /// The content that should be transformed.
pub child: Content,
}
-/// Rotate a node without affecting layout.
+/// Rotate content without affecting layout.
pub type RotateNode = TransformNode<ROTATE>;
-/// Scale a node without affecting layout.
+/// Scale content without affecting layout.
pub type ScaleNode = TransformNode<SCALE>;
-#[node(Layout)]
+#[node(LayoutInline)]
impl<const T: TransformKind> TransformNode<T> {
/// The origin of the transformation.
#[property(resolve)]
@@ -85,8 +81,8 @@ impl<const T: TransformKind> TransformNode<T> {
}
}
-impl<const T: TransformKind> Layout for TransformNode<T> {
- fn layout(
+impl<const T: TransformKind> LayoutInline for TransformNode<T> {
+ fn layout_inline(
&self,
world: Tracked<dyn World>,
regions: &Regions,
@@ -106,10 +102,6 @@ impl<const T: TransformKind> Layout for TransformNode<T> {
Ok(frames)
}
-
- fn level(&self) -> Level {
- Level::Inline
- }
}
/// Kinds of transformations.