diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-11-02 14:48:51 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-11-02 14:48:51 +0100 |
| commit | 56342bd972a13ffe21beaf2b87ab7eb1597704b4 (patch) | |
| tree | 78f9549141e753dde4a938670c54f3fe8695a058 /src/library | |
| parent | 37ac5d966ebaf97ac79c507028cd5b742b510b89 (diff) | |
Move layout traits into library
Diffstat (limited to 'src/library')
| -rw-r--r-- | src/library/ext.rs | 179 | ||||
| -rw-r--r-- | src/library/graphics/hide.rs | 12 | ||||
| -rw-r--r-- | src/library/graphics/image.rs | 10 | ||||
| -rw-r--r-- | src/library/graphics/line.rs | 10 | ||||
| -rw-r--r-- | src/library/graphics/shape.rs | 20 | ||||
| -rw-r--r-- | src/library/layout/align.rs | 21 | ||||
| -rw-r--r-- | src/library/layout/columns.rs | 12 | ||||
| -rw-r--r-- | src/library/layout/container.rs | 26 | ||||
| -rw-r--r-- | src/library/layout/flow.rs | 45 | ||||
| -rw-r--r-- | src/library/layout/grid.rs | 32 | ||||
| -rw-r--r-- | src/library/layout/mod.rs | 789 | ||||
| -rw-r--r-- | src/library/layout/pad.rs | 14 | ||||
| -rw-r--r-- | src/library/layout/page.rs | 2 | ||||
| -rw-r--r-- | src/library/layout/place.rs | 12 | ||||
| -rw-r--r-- | src/library/layout/spacing.rs | 2 | ||||
| -rw-r--r-- | src/library/layout/stack.rs | 42 | ||||
| -rw-r--r-- | src/library/layout/transform.rs | 36 | ||||
| -rw-r--r-- | src/library/math/mod.rs | 49 | ||||
| -rw-r--r-- | src/library/mod.rs | 200 | ||||
| -rw-r--r-- | src/library/prelude.rs | 13 | ||||
| -rw-r--r-- | src/library/raw.rs | 149 | ||||
| -rw-r--r-- | src/library/structure/table.rs | 2 | ||||
| -rw-r--r-- | src/library/text/par.rs | 58 |
23 files changed, 1299 insertions, 436 deletions
diff --git a/src/library/ext.rs b/src/library/ext.rs new file mode 100644 index 00000000..07b55a7b --- /dev/null +++ b/src/library/ext.rs @@ -0,0 +1,179 @@ +use super::*; +use crate::library::prelude::*; + +/// Additional methods on content. +pub trait ContentExt { + /// Make this content strong. + fn strong(self) -> Self; + + /// Make this content emphasized. + fn emph(self) -> Self; + + /// Underline this content. + fn underlined(self) -> Self; + + /// Add weak vertical spacing above and below the content. + fn spaced(self, above: Option<Abs>, below: Option<Abs>) -> Self; + + /// Force a size for this content. + fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self; + + /// Set alignments for this content. + fn aligned(self, aligns: Axes<Option<RawAlign>>) -> Self; + + /// Pad this content at the sides. + fn padded(self, padding: Sides<Rel<Length>>) -> Self; + + /// Transform this content's contents without affecting layout. + fn moved(self, delta: Axes<Rel<Length>>) -> Self; + + /// Fill the frames resulting from a content. + fn filled(self, fill: Paint) -> Self; + + /// Stroke the frames resulting from a content. + fn stroked(self, stroke: Stroke) -> Self; +} + +impl ContentExt for Content { + fn strong(self) -> Self { + text::StrongNode(self).pack() + } + + fn emph(self) -> Self { + text::EmphNode(self).pack() + } + + fn underlined(self) -> Self { + text::DecoNode::<{ text::UNDERLINE }>(self).pack() + } + + fn spaced(self, above: Option<Abs>, below: Option<Abs>) -> Self { + if above.is_none() && below.is_none() { + return self; + } + + let mut seq = vec![]; + if let Some(above) = above { + seq.push( + layout::VNode { + amount: above.into(), + weak: true, + generated: true, + } + .pack(), + ); + } + + seq.push(self); + if let Some(below) = below { + seq.push( + layout::VNode { + amount: below.into(), + weak: true, + generated: true, + } + .pack(), + ); + } + + Content::sequence(seq) + } + + fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self { + layout::BoxNode { sizing, child: self }.pack() + } + + fn aligned(self, aligns: Axes<Option<RawAlign>>) -> Self { + layout::AlignNode { aligns, child: self }.pack() + } + + fn padded(self, padding: Sides<Rel<Length>>) -> Self { + layout::PadNode { padding, child: self }.pack() + } + + fn moved(self, delta: Axes<Rel<Length>>) -> Self { + layout::MoveNode { delta, child: self }.pack() + } + + fn filled(self, fill: Paint) -> Self { + FillNode { fill, child: self }.pack() + } + + fn stroked(self, stroke: Stroke) -> Self { + StrokeNode { stroke, child: self }.pack() + } +} + +/// Additional methods for the style chain. +pub trait StyleMapExt { + /// Set a font family composed of a preferred family and existing families + /// from a style chain. + fn set_family(&mut self, preferred: text::FontFamily, existing: StyleChain); +} + +impl StyleMapExt for StyleMap { + fn set_family(&mut self, preferred: text::FontFamily, existing: StyleChain) { + self.set( + text::TextNode::FAMILY, + std::iter::once(preferred) + .chain(existing.get(text::TextNode::FAMILY).iter().cloned()) + .collect(), + ); + } +} + +/// Fill the frames resulting from content. +#[derive(Debug, Hash)] +struct FillNode { + /// How to fill the frames resulting from the `child`. + fill: Paint, + /// The content whose frames should be filled. + child: Content, +} + +#[node(LayoutBlock)] +impl FillNode {} + +impl LayoutBlock for FillNode { + fn layout_block( + &self, + world: Tracked<dyn World>, + regions: &Regions, + styles: StyleChain, + ) -> SourceResult<Vec<Frame>> { + let mut frames = self.child.layout_block(world, regions, styles)?; + for frame in &mut frames { + let shape = Geometry::Rect(frame.size()).filled(self.fill); + frame.prepend(Point::zero(), Element::Shape(shape)); + } + Ok(frames) + } +} + +/// Stroke the frames resulting from content. +#[derive(Debug, Hash)] +struct StrokeNode { + /// How to stroke the frames resulting from the `child`. + stroke: Stroke, + /// The content whose frames should be stroked. + child: Content, +} + +#[node(LayoutBlock)] +impl StrokeNode {} + +impl LayoutBlock for StrokeNode { + fn layout_block( + &self, + world: Tracked<dyn World>, + regions: &Regions, + styles: StyleChain, + ) -> SourceResult<Vec<Frame>> { + let mut frames = self.child.layout_block(world, regions, styles)?; + for frame in &mut frames { + let shape = Geometry::Rect(frame.size()).stroked(self.stroke); + frame.prepend(Point::zero(), Element::Shape(shape)); + } + Ok(frames) + } +} diff --git a/src/library/graphics/hide.rs b/src/library/graphics/hide.rs index fafd7421..d320b06c 100644 --- a/src/library/graphics/hide.rs +++ b/src/library/graphics/hide.rs @@ -1,18 +1,18 @@ use crate::library::prelude::*; -/// Hide a node without affecting layout. +/// Hide content without affecting layout. #[derive(Debug, Hash)] pub struct HideNode(pub Content); -#[node(Layout)] +#[node(LayoutInline)] impl HideNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { Ok(Self(args.expect("body")?).pack()) } } -impl Layout for HideNode { - fn layout( +impl LayoutInline for HideNode { + fn layout_inline( &self, world: Tracked<dyn World>, regions: &Regions, @@ -24,8 +24,4 @@ impl Layout for HideNode { } Ok(frames) } - - fn level(&self) -> Level { - Level::Inline - } } diff --git a/src/library/graphics/image.rs b/src/library/graphics/image.rs index 9c3a775a..e27ea488 100644 --- a/src/library/graphics/image.rs +++ b/src/library/graphics/image.rs @@ -8,7 +8,7 @@ use crate::library::text::TextNode; #[derive(Debug, Hash)] pub struct ImageNode(pub Image); -#[node(Layout)] +#[node(LayoutInline)] impl ImageNode { /// How the image should adjust itself to a given area. pub const FIT: ImageFit = ImageFit::Cover; @@ -36,8 +36,8 @@ impl ImageNode { } } -impl Layout for ImageNode { - fn layout( +impl LayoutInline for ImageNode { + fn layout_inline( &self, _: Tracked<dyn World>, regions: &Regions, @@ -95,10 +95,6 @@ impl Layout for ImageNode { Ok(vec![frame]) } - - fn level(&self) -> Level { - Level::Inline - } } /// How an image should adjust itself to a given area. diff --git a/src/library/graphics/line.rs b/src/library/graphics/line.rs index c2f89404..ee7813a5 100644 --- a/src/library/graphics/line.rs +++ b/src/library/graphics/line.rs @@ -9,7 +9,7 @@ pub struct LineNode { delta: Axes<Rel<Length>>, } -#[node(Layout)] +#[node(LayoutInline)] impl LineNode { /// How to stroke the line. #[property(resolve, fold)] @@ -36,8 +36,8 @@ impl LineNode { } } -impl Layout for LineNode { - fn layout( +impl LayoutInline for LineNode { + fn layout_inline( &self, _: Tracked<dyn World>, regions: &Regions, @@ -65,10 +65,6 @@ impl Layout for LineNode { Ok(vec![frame]) } - - fn level(&self) -> Level { - Level::Inline - } } castable! { diff --git a/src/library/graphics/shape.rs b/src/library/graphics/shape.rs index 608c9842..4804cd68 100644 --- a/src/library/graphics/shape.rs +++ b/src/library/graphics/shape.rs @@ -3,23 +3,23 @@ use std::f64::consts::SQRT_2; use crate::library::prelude::*; use crate::library::text::TextNode; -/// Place a node into a sizable and fillable shape. +/// A sizable and fillable shape with optional content. #[derive(Debug, Hash)] pub struct ShapeNode<const S: ShapeKind>(pub Option<Content>); -/// Place a node into a square. +/// A square with optional content. pub type SquareNode = ShapeNode<SQUARE>; -/// Place a node into a rectangle. +/// A rectangle with optional content. pub type RectNode = ShapeNode<RECT>; -/// Place a node into a circle. +/// A circle with optional content. pub type CircleNode = ShapeNode<CIRCLE>; -/// Place a node into an ellipse. +/// A ellipse with optional content. pub type EllipseNode = ShapeNode<ELLIPSE>; -#[node(Layout)] +#[node(LayoutInline)] impl<const S: ShapeKind> ShapeNode<S> { /// How to fill the shape. pub const FILL: Option<Paint> = None; @@ -72,8 +72,8 @@ impl<const S: ShapeKind> ShapeNode<S> { } } -impl<const S: ShapeKind> Layout for ShapeNode<S> { - fn layout( +impl<const S: ShapeKind> LayoutInline for ShapeNode<S> { + fn layout_inline( &self, world: Tracked<dyn World>, regions: &Regions, @@ -173,10 +173,6 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> { Ok(frames) } - - fn level(&self) -> Level { - Level::Inline - } } /// A category of shape. 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. diff --git a/src/library/math/mod.rs b/src/library/math/mod.rs index 84d4b6ee..5bb5054d 100644 --- a/src/library/math/mod.rs +++ b/src/library/math/mod.rs @@ -36,7 +36,7 @@ pub enum MathNode { Row(Arc<Vec<MathNode>>, Span), } -#[node(Show, Layout)] +#[node(Show, LayoutInline)] impl MathNode { /// The math font family. #[property(referenced)] @@ -67,6 +67,15 @@ impl MathNode { self } + + /// Whether the formula is display level. + pub fn display(&self) -> bool { + if let Self::Row(row, _) = self { + matches!(row.as_slice(), [MathNode::Space, .., MathNode::Space]) + } else { + false + } + } } impl Show for MathNode { @@ -79,11 +88,10 @@ impl Show for MathNode { } fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> { - Ok(match self.level() { - Level::Inline => self.clone().pack(), - Level::Block => { - self.clone().pack().aligned(Axes::with_x(Some(Align::Center.into()))) - } + Ok(if self.display() { + self.clone().pack().aligned(Axes::with_x(Some(Align::Center.into()))) + } else { + self.clone().pack() }) } @@ -93,27 +101,22 @@ impl Show for MathNode { styles: StyleChain, realized: Content, ) -> SourceResult<Content> { - Ok(match self.level() { - Level::Inline => realized, - Level::Block => { - realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW)) - } + Ok(if self.display() { + realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW)) + } else { + realized }) } } -impl Layout for MathNode { - fn layout( +impl LayoutInline for MathNode { + fn layout_inline( &self, world: Tracked<dyn World>, _: &Regions, styles: StyleChain, ) -> SourceResult<Vec<Frame>> { - let style = match self.level() { - Level::Inline => Style::Text, - Level::Block => Style::Display, - }; - + let style = if self.display() { Style::Display } else { Style::Text }; let span = match self { &Self::Row(_, span) => span, _ => Span::detached(), @@ -121,16 +124,6 @@ impl Layout for MathNode { Ok(vec![layout_tex(world, self, span, style, styles)?]) } - - fn level(&self) -> Level { - if let Self::Row(row, _) = self { - if matches!(row.as_slice(), [MathNode::Space, .., MathNode::Space]) { - return Level::Block; - } - } - - Level::Inline - } } /// Layout a TeX formula into a frame. diff --git a/src/library/mod.rs b/src/library/mod.rs index d75e2459..184c515e 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -11,7 +11,14 @@ pub mod structure; pub mod text; pub mod utility; -use prelude::*; +mod ext; +mod raw; + +pub use raw::*; + +use crate::geom::{Align, Color, Dir}; +use crate::model::{Node, Scope}; +use crate::LangItems; /// Construct a scope containing all standard library definitions. pub fn scope() -> Scope { @@ -156,10 +163,10 @@ pub fn items() -> LangItems { strong: |body| text::StrongNode(body).pack(), emph: |body| text::EmphNode(body).pack(), raw: |text, lang, block| { - let node = text::RawNode { text, block }.pack(); + let content = text::RawNode { text, block }.pack(); match lang { - Some(_) => node.styled(text::RawNode::LANG, lang), - None => node, + Some(_) => content.styled(text::RawNode::LANG, lang), + None => content, } }, link: |url| text::LinkNode::from_url(url).pack(), @@ -174,188 +181,3 @@ pub fn items() -> LangItems { }, } } - -/// Additional methods on content. -pub trait ContentExt { - /// Make this content strong. - fn strong(self) -> Self; - - /// Make this content emphasized. - fn emph(self) -> Self; - - /// Underline this content. - fn underlined(self) -> Self; - - /// Add weak vertical spacing above and below the content. - fn spaced(self, above: Option<Abs>, below: Option<Abs>) -> Self; - - /// Force a size for this node. - fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self; - - /// Set alignments for this node. - fn aligned(self, aligns: Axes<Option<RawAlign>>) -> Self; - - /// Pad this node at the sides. - fn padded(self, padding: Sides<Rel<Length>>) -> Self; - - /// Transform this node's contents without affecting layout. - fn moved(self, delta: Axes<Rel<Length>>) -> Self; - - /// Fill the frames resulting from a node. - fn filled(self, fill: Paint) -> Self; - - /// Stroke the frames resulting from a node. - fn stroked(self, stroke: Stroke) -> Self; -} - -impl ContentExt for Content { - fn strong(self) -> Self { - text::StrongNode(self).pack() - } - - fn emph(self) -> Self { - text::EmphNode(self).pack() - } - - fn underlined(self) -> Self { - text::DecoNode::<{ text::UNDERLINE }>(self).pack() - } - - fn spaced(self, above: Option<Abs>, below: Option<Abs>) -> Self { - if above.is_none() && below.is_none() { - return self; - } - - let mut seq = vec![]; - if let Some(above) = above { - seq.push( - layout::VNode { - amount: above.into(), - weak: true, - generated: true, - } - .pack(), - ); - } - - seq.push(self); - if let Some(below) = below { - seq.push( - layout::VNode { - amount: below.into(), - weak: true, - generated: true, - } - .pack(), - ); - } - - Self::sequence(seq) - } - - fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self { - layout::BoxNode { sizing, child: self }.pack() - } - - fn aligned(self, aligns: Axes<Option<RawAlign>>) -> Self { - layout::AlignNode { aligns, child: self }.pack() - } - - fn padded(self, padding: Sides<Rel<Length>>) -> Self { - layout::PadNode { padding, child: self }.pack() - } - - fn moved(self, delta: Axes<Rel<Length>>) -> Self { - layout::MoveNode { delta, child: self }.pack() - } - - fn filled(self, fill: Paint) -> Self { - FillNode { fill, child: self }.pack() - } - - fn stroked(self, stroke: Stroke) -> Self { - StrokeNode { stroke, child: self }.pack() - } -} - -/// Additional methods for the style chain. -pub trait StyleMapExt { - /// Set a font family composed of a preferred family and existing families - /// from a style chain. - fn set_family(&mut self, preferred: text::FontFamily, existing: StyleChain); -} - -impl StyleMapExt for StyleMap { - fn set_family(&mut self, preferred: text::FontFamily, existing: StyleChain) { - self.set( - text::TextNode::FAMILY, - std::iter::once(preferred) - .chain(existing.get(text::TextNode::FAMILY).iter().cloned()) - .collect(), - ); - } -} - -/// Fill the frames resulting from a node. -#[derive(Debug, Hash)] -struct FillNode { - /// How to fill the frames resulting from the `child`. - fill: Paint, - /// The node whose frames should be filled. - child: Content, -} - -#[node(Layout)] -impl FillNode {} - -impl Layout for FillNode { - fn layout( - &self, - world: Tracked<dyn World>, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult<Vec<Frame>> { - let mut frames = self.child.layout_block(world, regions, styles)?; - for frame in &mut frames { - let shape = Geometry::Rect(frame.size()).filled(self.fill); - frame.prepend(Point::zero(), Element::Shape(shape)); - } - Ok(frames) - } - - fn level(&self) -> Level { - Level::Block - } -} - -/// Stroke the frames resulting from a node. -#[derive(Debug, Hash)] -struct StrokeNode { - /// How to stroke the frames resulting from the `child`. - stroke: Stroke, - /// The node whose frames should be stroked. - child: Content, -} - -#[node(Layout)] -impl StrokeNode {} - -impl Layout for StrokeNode { - fn layout( - &self, - world: Tracked<dyn World>, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult<Vec<Frame>> { - let mut frames = self.child.layout_block(world, regions, styles)?; - for frame in &mut frames { - let shape = Geometry::Rect(frame.size()).stroked(self.stroke); - frame.prepend(Point::zero(), Element::Shape(shape)); - } - Ok(frames) - } - - fn level(&self) -> Level { - Level::Block - } -} diff --git a/src/library/prelude.rs b/src/library/prelude.rs index 756904f0..66e35e68 100644 --- a/src/library/prelude.rs +++ b/src/library/prelude.rs @@ -7,19 +7,20 @@ pub use std::num::NonZeroUsize; pub use std::sync::Arc; pub use comemo::Tracked; -pub use typst_macros::node; -pub use super::{ContentExt, StyleMapExt}; +pub use super::ext::{ContentExt, StyleMapExt}; +pub use super::layout::{Layout, LayoutBlock, LayoutInline, Regions}; +pub use super::text::TextNode; +pub use super::{RawAlign, RawStroke}; pub use crate::diag::{ with_alternative, At, FileError, FileResult, SourceError, SourceResult, StrResult, }; pub use crate::frame::*; pub use crate::geom::*; -pub use crate::library::text::TextNode; pub use crate::model::{ - Arg, Args, Array, Cast, Content, Dict, Dynamic, Fold, Func, Key, Layout, Level, Node, - RawAlign, RawStroke, Regions, Resolve, Scope, Selector, Show, Smart, Str, StyleChain, - StyleMap, StyleVec, Value, Vm, + capability, node, Arg, Args, Array, Capability, Cast, Content, Dict, Dynamic, Fold, + Func, Key, Node, Resolve, Scope, Selector, Show, Smart, Str, StyleChain, StyleMap, + StyleVec, Value, Vm, }; pub use crate::syntax::{Span, Spanned}; pub use crate::util::EcoString; diff --git a/src/library/raw.rs b/src/library/raw.rs new file mode 100644 index 00000000..67aa651d --- /dev/null +++ b/src/library/raw.rs @@ -0,0 +1,149 @@ +use std::fmt::{self, Debug, Formatter}; + +use crate::geom::{Abs, Align, Axes, Axis, Get, Length, Paint, Stroke}; +use crate::library::text::TextNode; +use crate::model::{Fold, Resolve, Smart, StyleChain, Value}; + +/// The unresolved alignment representation. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum RawAlign { + /// Align at the start side of the text direction. + Start, + /// Align at the end side of the text direction. + End, + /// Align at a specific alignment. + Specific(Align), +} + +impl Resolve for RawAlign { + type Output = Align; + + fn resolve(self, styles: StyleChain) -> Self::Output { + let dir = styles.get(TextNode::DIR); + match self { + Self::Start => dir.start().into(), + Self::End => dir.end().into(), + Self::Specific(align) => align, + } + } +} + +impl RawAlign { + /// The axis this alignment belongs to. + pub const fn axis(self) -> Axis { + match self { + Self::Start | Self::End => Axis::X, + Self::Specific(align) => align.axis(), + } + } +} + +impl From<Align> for RawAlign { + fn from(align: Align) -> Self { + Self::Specific(align) + } +} + +impl Debug for RawAlign { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::Start => f.pad("start"), + Self::End => f.pad("end"), + Self::Specific(align) => align.fmt(f), + } + } +} + +dynamic! { + RawAlign: "alignment", +} + +dynamic! { + Axes<RawAlign>: "2d alignment", +} + +castable! { + Axes<Option<RawAlign>>, + Expected: "1d or 2d alignment", + @align: RawAlign => { + let mut aligns = Axes::default(); + aligns.set(align.axis(), Some(*align)); + aligns + }, + @aligns: Axes<RawAlign> => aligns.map(Some), +} + +/// The unresolved stroke representation. +/// +/// In this representation, both fields are optional so that you can pass either +/// just a paint (`red`), just a thickness (`0.1em`) or both (`2pt + red`) where +/// this is expected. +#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] +pub struct RawStroke<T = Length> { + /// The stroke's paint. + pub paint: Smart<Paint>, + /// The stroke's thickness. + pub thickness: Smart<T>, +} + +impl RawStroke<Abs> { + /// Unpack the stroke, filling missing fields from the `default`. + pub fn unwrap_or(self, default: Stroke) -> Stroke { + Stroke { + paint: self.paint.unwrap_or(default.paint), + thickness: self.thickness.unwrap_or(default.thickness), + } + } + + /// Unpack the stroke, filling missing fields with the default values. + pub fn unwrap_or_default(self) -> Stroke { + self.unwrap_or(Stroke::default()) + } +} + +impl Resolve for RawStroke { + type Output = RawStroke<Abs>; + + fn resolve(self, styles: StyleChain) -> Self::Output { + RawStroke { + paint: self.paint, + thickness: self.thickness.resolve(styles), + } + } +} + +impl Fold for RawStroke<Abs> { + type Output = Self; + + fn fold(self, outer: Self::Output) -> Self::Output { + Self { + paint: self.paint.or(outer.paint), + thickness: self.thickness.or(outer.thickness), + } + } +} + +impl<T: Debug> Debug for RawStroke<T> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match (self.paint, &self.thickness) { + (Smart::Custom(paint), Smart::Custom(thickness)) => { + write!(f, "{thickness:?} + {paint:?}") + } + (Smart::Custom(paint), Smart::Auto) => paint.fmt(f), + (Smart::Auto, Smart::Custom(thickness)) => thickness.fmt(f), + (Smart::Auto, Smart::Auto) => f.pad("<stroke>"), + } + } +} + +dynamic! { + RawStroke: "stroke", + Value::Length(thickness) => Self { + paint: Smart::Auto, + thickness: Smart::Custom(thickness), + }, + Value::Color(color) => Self { + paint: Smart::Custom(color.into()), + thickness: Smart::Auto, + }, +} diff --git a/src/library/structure/table.rs b/src/library/structure/table.rs index d5e8920e..8a4eb302 100644 --- a/src/library/structure/table.rs +++ b/src/library/structure/table.rs @@ -8,7 +8,7 @@ pub struct TableNode { 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 the table. + /// The content to be arranged in the table. pub cells: Vec<Content>, } diff --git a/src/library/text/par.rs b/src/library/text/par.rs index 7c862366..50089b20 100644 --- a/src/library/text/par.rs +++ b/src/library/text/par.rs @@ -22,11 +22,11 @@ pub enum ParChild { Quote { double: bool }, /// Horizontal spacing between other children. Spacing(Spacing), - /// An arbitrary inline-level node. - Node(Content), + /// Arbitrary inline-level content. + Inline(Content), } -#[node(Layout)] +#[node(LayoutBlock)] impl ParNode { /// The spacing between lines. #[property(resolve)] @@ -61,8 +61,8 @@ impl ParNode { } } -impl Layout for ParNode { - fn layout( +impl LayoutBlock for ParNode { + fn layout_block( &self, world: Tracked<dyn World>, regions: &Regions, @@ -82,10 +82,6 @@ impl Layout for ParNode { // Stack the lines into one frame per region. stack(&p, world, &lines, regions) } - - fn level(&self) -> Level { - Level::Block - } } impl Debug for ParNode { @@ -101,7 +97,7 @@ impl Debug for ParChild { Self::Text(text) => write!(f, "Text({:?})", text), Self::Quote { double } => write!(f, "Quote({double})"), Self::Spacing(kind) => write!(f, "{:?}", kind), - Self::Node(node) => node.fmt(f), + Self::Inline(inline) => inline.fmt(f), } } } @@ -180,19 +176,19 @@ impl ParbreakNode { } } -/// A node that should be repeated to fill up a line. +/// Repeats content to fill a line. #[derive(Debug, Hash)] pub struct RepeatNode(pub Content); -#[node(Layout)] +#[node(LayoutInline)] impl RepeatNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { Ok(Self(args.expect("body")?).pack()) } } -impl Layout for RepeatNode { - fn layout( +impl LayoutInline for RepeatNode { + fn layout_inline( &self, world: Tracked<dyn World>, regions: &Regions, @@ -200,16 +196,12 @@ impl Layout for RepeatNode { ) -> SourceResult<Vec<Frame>> { self.0.layout_inline(world, regions, styles) } - - fn level(&self) -> Level { - Level::Inline - } } /// Range of a substring of text. type Range = std::ops::Range<usize>; -// The characters by which spacing, nodes and pins are replaced in the +// The characters by which spacing, inline content and pins are replaced in the // paragraph's full text. const SPACING_REPLACE: char = ' '; // Space const NODE_REPLACE: char = '\u{FFFC}'; // Object Replacement Character @@ -291,8 +283,8 @@ enum Segment<'a> { Text(usize), /// Horizontal spacing between other segments. Spacing(Spacing), - /// An arbitrary inline-level layout node. - Node(&'a Content), + /// Arbitrary inline-level content. + Inline(&'a Content), } impl Segment<'_> { @@ -301,7 +293,7 @@ impl Segment<'_> { match *self { Self::Text(len) => len, Self::Spacing(_) => SPACING_REPLACE.len_utf8(), - Self::Node(_) => NODE_REPLACE.len_utf8(), + Self::Inline(_) => NODE_REPLACE.len_utf8(), } } } @@ -315,9 +307,9 @@ enum Item<'a> { Absolute(Abs), /// Fractional spacing between other items. Fractional(Fr), - /// A layouted child node. + /// Layouted inline-level content. Frame(Frame), - /// A repeating node that fills the remaining space. + /// A repeating node that fills the remaining space in a line. Repeat(&'a RepeatNode, StyleChain<'a>), } @@ -475,7 +467,7 @@ fn collect<'a>( ParChild::Text(text) => text.chars().next(), ParChild::Quote { .. } => Some('"'), ParChild::Spacing(_) => Some(SPACING_REPLACE), - ParChild::Node(_) => Some(NODE_REPLACE), + ParChild::Inline(_) => Some(NODE_REPLACE), }); full.push_str(quoter.quote("es, double, peeked)); @@ -488,9 +480,9 @@ fn collect<'a>( full.push(SPACING_REPLACE); Segment::Spacing(spacing) } - ParChild::Node(node) => { + ParChild::Inline(inline) => { full.push(NODE_REPLACE); - Segment::Node(node) + Segment::Inline(inline) } }; @@ -514,7 +506,7 @@ fn collect<'a>( } /// Prepare paragraph layout by shaping the whole paragraph and layouting all -/// contained inline-level nodes. +/// contained inline-level content. fn prepare<'a>( world: Tracked<dyn World>, par: &'a ParNode, @@ -548,13 +540,13 @@ fn prepare<'a>( items.push(Item::Fractional(v)); } }, - Segment::Node(node) => { - if let Some(repeat) = node.downcast::<RepeatNode>() { + Segment::Inline(inline) => { + if let Some(repeat) = inline.downcast::<RepeatNode>() { items.push(Item::Repeat(repeat, styles)); } else { let size = Size::new(regions.first.x, regions.base.y); let pod = Regions::one(size, regions.base, Axes::splat(false)); - let mut frame = node.layout_inline(world, &pod, styles)?.remove(0); + let mut frame = inline.layout_inline(world, &pod, styles)?.remove(0); frame.translate(Point::with_y(styles.get(TextNode::BASELINE))); frame.apply_role(Role::GenericInline); items.push(Item::Frame(frame)); @@ -1169,12 +1161,12 @@ fn commit( Item::Frame(frame) => { push(&mut offset, frame.clone()); } - Item::Repeat(node, styles) => { + Item::Repeat(repeat, styles) => { let before = offset; let fill = Fr::one().share(fr, remaining); let size = Size::new(fill, regions.base.y); let pod = Regions::one(size, regions.base, Axes::new(false, false)); - let frame = node.layout(world, &pod, *styles)?.remove(0); + let frame = repeat.layout_inline(world, &pod, *styles)?.remove(0); let width = frame.width(); let count = (fill / width).floor(); let remaining = fill % width; |
