From 56342bd972a13ffe21beaf2b87ab7eb1597704b4 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 2 Nov 2022 14:48:51 +0100 Subject: [PATCH] Move layout traits into library --- benches/oneshot.rs | 2 +- macros/src/lib.rs | 20 +- src/lib.rs | 5 +- src/library/ext.rs | 179 ++++++++ src/library/graphics/hide.rs | 12 +- src/library/graphics/image.rs | 10 +- src/library/graphics/line.rs | 10 +- src/library/graphics/shape.rs | 20 +- src/library/layout/align.rs | 21 +- src/library/layout/columns.rs | 12 +- src/library/layout/container.rs | 26 +- src/library/layout/flow.rs | 45 +- src/library/layout/grid.rs | 32 +- src/library/layout/mod.rs | 789 ++++++++++++++++++++++++++++++++ src/library/layout/pad.rs | 14 +- src/library/layout/page.rs | 2 +- src/library/layout/place.rs | 12 +- src/library/layout/spacing.rs | 2 +- src/library/layout/stack.rs | 42 +- src/library/layout/transform.rs | 36 +- src/library/math/mod.rs | 49 +- src/library/mod.rs | 200 +------- src/library/prelude.rs | 13 +- src/{model => library}/raw.rs | 2 +- src/library/structure/table.rs | 2 +- src/library/text/par.rs | 58 +-- src/model/capture.rs | 186 -------- src/model/collapse.rs | 116 ----- src/model/content.rs | 70 +-- src/model/fold.rs | 81 ---- src/model/func.rs | 187 +++++++- src/model/layout.rs | 142 ------ src/model/mod.rs | 33 +- src/model/ops.rs | 3 +- src/model/property.rs | 195 -------- src/model/realize.rs | 486 -------------------- src/model/recipe.rs | 185 -------- src/model/resolve.rs | 84 ---- src/model/str.rs | 3 +- src/model/styles.rs | 525 ++++++++++++++++++++- src/model/value.rs | 2 +- 41 files changed, 1905 insertions(+), 2008 deletions(-) create mode 100644 src/library/ext.rs rename src/{model => library}/raw.rs (98%) delete mode 100644 src/model/capture.rs delete mode 100644 src/model/collapse.rs delete mode 100644 src/model/fold.rs delete mode 100644 src/model/layout.rs delete mode 100644 src/model/property.rs delete mode 100644 src/model/realize.rs delete mode 100644 src/model/recipe.rs delete mode 100644 src/model/resolve.rs diff --git a/benches/oneshot.rs b/benches/oneshot.rs index 1d82f44d9..2437b7236 100644 --- a/benches/oneshot.rs +++ b/benches/oneshot.rs @@ -85,7 +85,7 @@ fn bench_layout(iai: &mut Iai) { let id = world.source.id(); let route = typst::model::Route::default(); let module = typst::model::eval(world.track(), route.track(), id).unwrap(); - iai.run(|| typst::model::layout(world.track(), &module.content)); + iai.run(|| typst::library::layout::Layout::layout(&module.content, world.track())); } fn bench_render(iai: &mut Iai) { diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 5c3676046..823dbdc8d 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -8,17 +8,31 @@ use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::{Error, Ident, Result}; -/// Turn a struct into a node / a function with settable properties. +/// Implement `Capability` for a trait. +#[proc_macro_attribute] +pub fn capability(_: TokenStream, item: TokenStream) -> TokenStream { + let item_trait = syn::parse_macro_input!(item as syn::ItemTrait); + let name = &item_trait.ident; + quote! { + #item_trait + impl crate::model::Capability for dyn #name {} + }.into() +} + +/// Implement `Node` for a struct. #[proc_macro_attribute] pub fn node(stream: TokenStream, item: TokenStream) -> TokenStream { let impl_block = syn::parse_macro_input!(item as syn::ItemImpl); - expand(stream.into(), impl_block) + expand_node(stream.into(), impl_block) .unwrap_or_else(|err| err.to_compile_error()) .into() } /// Expand an impl block for a node. -fn expand(stream: TokenStream2, mut impl_block: syn::ItemImpl) -> Result { +fn expand_node( + stream: TokenStream2, + mut impl_block: syn::ItemImpl, +) -> Result { // Split the node type into name and generic type arguments. let params = &impl_block.generics.params; let self_ty = &*impl_block.self_ty; diff --git a/src/lib.rs b/src/lib.rs index caba97072..051a58f3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,7 @@ //! structure, layouts, etc. of the module. The nodes of the content tree are //! well structured and order-independent and thus much better suited for //! layouting than the raw markup. -//! - **Layouting:** Next, the content is [layouted] into a portable version of +//! - **Layouting:** Next, the content is layouted into a portable version of //! the typeset document. The output of this is a collection of [`Frame`]s //! (one per page), ready for exporting. //! - **Exporting:** The finished layout can be exported into a supported @@ -24,7 +24,6 @@ //! [evaluate]: model::eval //! [module]: model::Module //! [content]: model::Content -//! [layouted]: model::layout //! [PDF]: export::pdf #![allow(clippy::len_without_is_empty)] @@ -69,7 +68,7 @@ pub fn typeset( ) -> SourceResult> { let route = Route::default(); let module = model::eval(world.track(), route.track(), main)?; - model::layout(world.track(), &module.content) + library::layout::Layout::layout(&module.content, world.track()) } /// The environment in which typesetting occurs. diff --git a/src/library/ext.rs b/src/library/ext.rs new file mode 100644 index 000000000..07b55a7bb --- /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, below: Option) -> Self; + + /// Force a size for this content. + fn boxed(self, sizing: Axes>>) -> Self; + + /// Set alignments for this content. + fn aligned(self, aligns: Axes>) -> Self; + + /// Pad this content at the sides. + fn padded(self, padding: Sides>) -> Self; + + /// Transform this content's contents without affecting layout. + fn moved(self, delta: Axes>) -> 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, below: Option) -> 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>>) -> Self { + layout::BoxNode { sizing, child: self }.pack() + } + + fn aligned(self, aligns: Axes>) -> Self { + layout::AlignNode { aligns, child: self }.pack() + } + + fn padded(self, padding: Sides>) -> Self { + layout::PadNode { padding, child: self }.pack() + } + + fn moved(self, delta: Axes>) -> 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, + regions: &Regions, + styles: StyleChain, + ) -> SourceResult> { + 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, + regions: &Regions, + styles: StyleChain, + ) -> SourceResult> { + 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 fafd74215..d320b06c5 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 { Ok(Self(args.expect("body")?).pack()) } } -impl Layout for HideNode { - fn layout( +impl LayoutInline for HideNode { + fn layout_inline( &self, world: Tracked, 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 9c3a775ad..e27ea4884 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, 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 c2f894049..ee7813a51 100644 --- a/src/library/graphics/line.rs +++ b/src/library/graphics/line.rs @@ -9,7 +9,7 @@ pub struct LineNode { delta: Axes>, } -#[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, 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 608c9842d..4804cd681 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(pub Option); -/// Place a node into a square. +/// A square with optional content. pub type SquareNode = ShapeNode; -/// Place a node into a rectangle. +/// A rectangle with optional content. pub type RectNode = ShapeNode; -/// Place a node into a circle. +/// A circle with optional content. pub type CircleNode = ShapeNode; -/// Place a node into an ellipse. +/// A ellipse with optional content. pub type EllipseNode = ShapeNode; -#[node(Layout)] +#[node(LayoutInline)] impl ShapeNode { /// How to fill the shape. pub const FILL: Option = None; @@ -72,8 +72,8 @@ impl ShapeNode { } } -impl Layout for ShapeNode { - fn layout( +impl LayoutInline for ShapeNode { + fn layout_inline( &self, world: Tracked, regions: &Regions, @@ -173,10 +173,6 @@ impl Layout for ShapeNode { 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 f49763b5e..2ee565cc5 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>, - /// 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 { let aligns: Axes> = args.find()?.unwrap_or_default(); let body: Content = args.expect("body")?; if let Axes { x: Some(x), y: None } = aligns { - if body - .to::() - .map_or(true, |node| node.level() == Level::Inline) - { + if !body.has::() { 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, 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 79d98e110..df259eab2 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, 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 60f9139b4..023809d04 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>>, - /// 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 { 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, 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 { Ok(Self(args.eat()?.unwrap_or_default()).pack()) } } -impl Layout for BlockNode { - fn layout( +impl LayoutBlock for BlockNode { + fn layout_block( &self, world: Tracked, regions: &Regions, @@ -81,8 +77,4 @@ impl Layout for BlockNode { ) -> SourceResult> { 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 01ee9dc90..f4d18699f 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); 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, 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, /// Finished frames for previous regions. finished: Vec, @@ -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), /// 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, - 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::() { + if let Some(placed) = block.downcast::() { 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::() + // Vertical align node alignment is respected by the flow. + block + .downcast::() .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 7e5cbbd52..1bb676918 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>, /// Defines sizing of gutter rows and columns between content. pub gutter: Axes>, - /// The nodes to be arranged in a grid. + /// The content to be arranged in a grid. pub cells: Vec, } -#[node(Layout)] +#[node(LayoutBlock)] impl GridNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { 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, 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 02276f228..000cb2127 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) -> SourceResult>; +} + +impl Layout for Content { + #[comemo::memoize] + fn layout(&self, world: Tracked) -> SourceResult> { + 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, + regions: &Regions, + styles: StyleChain, + ) -> SourceResult>; +} + +impl LayoutBlock for Content { + #[comemo::memoize] + fn layout_block( + &self, + world: Tracked, + regions: &Regions, + styles: StyleChain, + ) -> SourceResult> { + if let Some(node) = self.to::() { + 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, + regions: &Regions, + styles: StyleChain, + ) -> SourceResult>; +} + +impl LayoutInline for Content { + #[comemo::memoize] + fn layout_inline( + &self, + world: Tracked, + regions: &Regions, + styles: StyleChain, + ) -> SourceResult> { + if let Some(node) = self.to::() { + 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::() { + 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, + /// 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, + /// Whether nodes should expand to fill the regions instead of shrinking to + /// fit the content. + pub expand: Axes, +} + +impl Regions { + /// Create a new region sequence with exactly one region. + pub fn one(size: Size, base: Size, expand: Axes) -> 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) -> 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(&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 + '_ { + 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>, + /// 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>, + /// An arena where intermediate content resulting from show rules is stored. + templates: Arena, +} + +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::() { + 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::() { + return self.styled(styled, styles); + } else if let Some(seq) = content.downcast::() { + return self.sequence(seq, styles); + } else if content.has::() { + if self.show(&content, styles)? { + return Ok(()); + } + } + + if self.list.accept(content, styles) { + return Ok(()); + } + + self.interrupt(Interruption::List, styles, false)?; + + if content.is::() { + 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::() + .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 { + 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::() { + self.keep_next = !pagebreak.weak; + } + + if let Some(page) = content.downcast::() { + 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::() { + /* Nothing to do */ + } else if let Some(colbreak) = content.downcast::() { + if colbreak.weak { + self.0.weak(FlowChild::Colbreak, styles, 0); + } else { + self.0.destructive(FlowChild::Colbreak, styles); + } + } else if let Some(vertical) = content.downcast::() { + 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::() { + let child = FlowChild::Block(content.clone()); + if content.is::() { + 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::() { + self.0.weak(ParChild::Text(' '.into()), styles, 2); + } else if let Some(linebreak) = content.downcast::() { + let c = if linebreak.justify { '\u{2028}' } else { '\n' }; + self.0.destructive(ParChild::Text(c.into()), styles); + } else if let Some(horizontal) = content.downcast::() { + 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::() { + self.0.supportive(ParChild::Quote { double: quote.double }, styles); + } else if let Some(text) = content.downcast::() { + self.0.supportive(ParChild::Text(text.0.clone()), styles); + } else if content.has::() { + 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::()), + 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::() { + self.attachable = false; + } else if !content.is::() && !content.is::() { + self.attachable = true; + } + } + + if let Some(item) = content.downcast::() { + 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::()); + } else { + return false; + } + } else if !self.items.is_empty() + && (content.is::() || content.is::()) + { + 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:: { tight, attached, items }.pack(), + ENUM => ListNode:: { tight, attached, items }.pack(), + DESC | _ => ListNode:: { 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)>, + /// 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 { + 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, 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 effdd5f84..920660d68 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>, - /// 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 { 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, 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 f5821ae6e..8d0817499 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 42ab0fbab..ee38ebe60 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 { 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, 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 6df5cde55..c410eee73 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); diff --git a/src/library/layout/stack.rs b/src/library/layout/stack.rs index 9ea7965da..e1e70de97 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, } -#[node(Layout)] +#[node(LayoutBlock)] impl StackNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Self { @@ -26,8 +26,8 @@ impl StackNode { } } -impl Layout for StackNode { - fn layout( +impl LayoutBlock for StackNode { + fn layout_block( &self, world: Tracked, regions: &Regions, @@ -35,7 +35,7 @@ impl Layout for StackNode { ) -> SourceResult> { 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, - 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::() .and_then(|node| node.aligns.get(self.axis)) .map(|align| align.resolve(styles)) .unwrap_or_else(|| { - if let Some(styled) = node.downcast::() { + if let Some(styled) = block.downcast::() { 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 061efa6b3..a73a1827e 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>, - /// 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 { 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, 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 { - /// 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; -/// Scale a node without affecting layout. +/// Scale content without affecting layout. pub type ScaleNode = TransformNode; -#[node(Layout)] +#[node(LayoutInline)] impl TransformNode { /// The origin of the transformation. #[property(resolve)] @@ -85,8 +81,8 @@ impl TransformNode { } } -impl Layout for TransformNode { - fn layout( +impl LayoutInline for TransformNode { + fn layout_inline( &self, world: Tracked, regions: &Regions, @@ -106,10 +102,6 @@ impl Layout for TransformNode { 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 84d4b6ee3..5bb5054da 100644 --- a/src/library/math/mod.rs +++ b/src/library/math/mod.rs @@ -36,7 +36,7 @@ pub enum MathNode { Row(Arc>, 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, _: StyleChain) -> SourceResult { - 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 { - 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, _: &Regions, styles: StyleChain, ) -> SourceResult> { - 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 d75e24590..184c515e2 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, below: Option) -> Self; - - /// Force a size for this node. - fn boxed(self, sizing: Axes>>) -> Self; - - /// Set alignments for this node. - fn aligned(self, aligns: Axes>) -> Self; - - /// Pad this node at the sides. - fn padded(self, padding: Sides>) -> Self; - - /// Transform this node's contents without affecting layout. - fn moved(self, delta: Axes>) -> 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, below: Option) -> 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>>) -> Self { - layout::BoxNode { sizing, child: self }.pack() - } - - fn aligned(self, aligns: Axes>) -> Self { - layout::AlignNode { aligns, child: self }.pack() - } - - fn padded(self, padding: Sides>) -> Self { - layout::PadNode { padding, child: self }.pack() - } - - fn moved(self, delta: Axes>) -> 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, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult> { - 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, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult> { - 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 756904f07..66e35e68d 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/model/raw.rs b/src/library/raw.rs similarity index 98% rename from src/model/raw.rs rename to src/library/raw.rs index b9242b51e..67aa651d7 100644 --- a/src/model/raw.rs +++ b/src/library/raw.rs @@ -1,8 +1,8 @@ use std::fmt::{self, Debug, Formatter}; -use super::{Fold, Resolve, Smart, StyleChain, Value}; 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)] diff --git a/src/library/structure/table.rs b/src/library/structure/table.rs index d5e8920e8..8a4eb3021 100644 --- a/src/library/structure/table.rs +++ b/src/library/structure/table.rs @@ -8,7 +8,7 @@ pub struct TableNode { pub tracks: Axes>, /// Defines sizing of gutter rows and columns between content. pub gutter: Axes>, - /// The nodes to be arranged in the table. + /// The content to be arranged in the table. pub cells: Vec, } diff --git a/src/library/text/par.rs b/src/library/text/par.rs index 7c8623669..50089b20f 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, 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 { Ok(Self(args.expect("body")?).pack()) } } -impl Layout for RepeatNode { - fn layout( +impl LayoutInline for RepeatNode { + fn layout_inline( &self, world: Tracked, regions: &Regions, @@ -200,16 +196,12 @@ impl Layout for RepeatNode { ) -> SourceResult> { self.0.layout_inline(world, regions, styles) } - - fn level(&self) -> Level { - Level::Inline - } } /// Range of a substring of text. type Range = std::ops::Range; -// 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, par: &'a ParNode, @@ -548,13 +540,13 @@ fn prepare<'a>( items.push(Item::Fractional(v)); } }, - Segment::Node(node) => { - if let Some(repeat) = node.downcast::() { + Segment::Inline(inline) => { + if let Some(repeat) = inline.downcast::() { 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; diff --git a/src/model/capture.rs b/src/model/capture.rs deleted file mode 100644 index c4c107b2e..000000000 --- a/src/model/capture.rs +++ /dev/null @@ -1,186 +0,0 @@ -use super::{Scope, Scopes, Value}; -use crate::syntax::ast::TypedNode; -use crate::syntax::{ast, SyntaxNode}; - -/// A visitor that captures variable slots. -pub struct CapturesVisitor<'a> { - external: &'a Scopes<'a>, - internal: Scopes<'a>, - captures: Scope, -} - -impl<'a> CapturesVisitor<'a> { - /// Create a new visitor for the given external scopes. - pub fn new(external: &'a Scopes) -> Self { - Self { - external, - internal: Scopes::new(None), - captures: Scope::new(), - } - } - - /// Return the scope of captured variables. - pub fn finish(self) -> Scope { - self.captures - } - - /// Bind a new internal variable. - pub fn bind(&mut self, ident: ast::Ident) { - self.internal.top.define(ident.take(), Value::None); - } - - /// Capture a variable if it isn't internal. - pub fn capture(&mut self, ident: ast::Ident) { - if self.internal.get(&ident).is_err() { - if let Ok(value) = self.external.get(&ident) { - self.captures.define_captured(ident.take(), value.clone()); - } - } - } - - /// Visit any node and collect all captured variables. - pub fn visit(&mut self, node: &SyntaxNode) { - match node.cast() { - // Every identifier is a potential variable that we need to capture. - // Identifiers that shouldn't count as captures because they - // actually bind a new name are handled below (individually through - // the expressions that contain them). - Some(ast::Expr::Ident(ident)) => self.capture(ident), - - // Code and content blocks create a scope. - Some(ast::Expr::Code(_) | ast::Expr::Content(_)) => { - self.internal.enter(); - for child in node.children() { - self.visit(child); - } - self.internal.exit(); - } - - // A closure contains parameter bindings, which are bound before the - // body is evaluated. Care must be taken so that the default values - // of named parameters cannot access previous parameter bindings. - Some(ast::Expr::Closure(expr)) => { - for param in expr.params() { - if let ast::Param::Named(named) = param { - self.visit(named.expr().as_untyped()); - } - } - - for param in expr.params() { - match param { - ast::Param::Pos(ident) => self.bind(ident), - ast::Param::Named(named) => self.bind(named.name()), - ast::Param::Sink(ident) => self.bind(ident), - } - } - - self.visit(expr.body().as_untyped()); - } - - // A let expression contains a binding, but that binding is only - // active after the body is evaluated. - Some(ast::Expr::Let(expr)) => { - if let Some(init) = expr.init() { - self.visit(init.as_untyped()); - } - self.bind(expr.binding()); - } - - // A show rule contains a binding, but that binding is only active - // after the target has been evaluated. - Some(ast::Expr::Show(show)) => { - self.visit(show.pattern().as_untyped()); - if let Some(binding) = show.binding() { - self.bind(binding); - } - self.visit(show.body().as_untyped()); - } - - // A for loop contains one or two bindings in its pattern. These are - // active after the iterable is evaluated but before the body is - // evaluated. - Some(ast::Expr::For(expr)) => { - self.visit(expr.iter().as_untyped()); - let pattern = expr.pattern(); - if let Some(key) = pattern.key() { - self.bind(key); - } - self.bind(pattern.value()); - self.visit(expr.body().as_untyped()); - } - - // An import contains items, but these are active only after the - // path is evaluated. - Some(ast::Expr::Import(expr)) => { - self.visit(expr.path().as_untyped()); - if let ast::Imports::Items(items) = expr.imports() { - for item in items { - self.bind(item); - } - } - } - - // Everything else is traversed from left to right. - _ => { - for child in node.children() { - self.visit(child); - } - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::syntax::parse; - - #[track_caller] - fn test(text: &str, result: &[&str]) { - let mut scopes = Scopes::new(None); - scopes.top.define("x", 0); - scopes.top.define("y", 0); - scopes.top.define("z", 0); - - let mut visitor = CapturesVisitor::new(&scopes); - let root = parse(text); - visitor.visit(&root); - - let captures = visitor.finish(); - let mut names: Vec<_> = captures.iter().map(|(k, _)| k).collect(); - names.sort(); - - assert_eq!(names, result); - } - - #[test] - fn test_captures() { - // Let binding and function definition. - test("#let x = x", &["x"]); - test("#let x; {x + y}", &["y"]); - test("#let f(x, y) = x + y", &[]); - - // Closure with different kinds of params. - test("{(x, y) => x + z}", &["z"]); - test("{(x: y, z) => x + z}", &["y"]); - test("{(..x) => x + y}", &["y"]); - test("{(x, y: x + z) => x + y}", &["x", "z"]); - - // Show rule. - test("#show x: y as x", &["y"]); - test("#show x: y as x + z", &["y", "z"]); - test("#show x: x as x", &["x"]); - - // For loop. - test("#for x in y { x + z }", &["y", "z"]); - test("#for x, y in y { x + y }", &["y"]); - - // Import. - test("#import x, y from z", &["z"]); - test("#import x, y, z from x + y", &["x", "y"]); - - // Blocks. - test("{ let x = 1; { let y = 2; y }; x + y }", &["y"]); - test("[#let x = 1]#x", &["x"]); - } -} diff --git a/src/model/collapse.rs b/src/model/collapse.rs deleted file mode 100644 index f57a3a42a..000000000 --- a/src/model/collapse.rs +++ /dev/null @@ -1,116 +0,0 @@ -use super::{StyleChain, StyleVec, StyleVecBuilder}; - -/// A wrapper around a [`StyleVecBuilder`] that allows to collapse items. -pub(super) 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)>, - /// 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 { - 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, 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/model/content.rs b/src/model/content.rs index 170d47a1f..1cffa7731 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -5,18 +5,12 @@ use std::iter::{self, Sum}; use std::ops::{Add, AddAssign}; use std::sync::Arc; -use comemo::Tracked; use siphasher::sip128::{Hasher128, SipHasher}; use typst_macros::node; -use super::{ - Args, Barrier, Builder, Key, Layout, Level, Property, Regions, Scratch, Selector, - StyleChain, StyleEntry, StyleMap, Vm, -}; +use super::{Args, Key, Property, Selector, StyleEntry, StyleMap, Vm}; use crate::diag::{SourceResult, StrResult}; -use crate::frame::Frame; use crate::util::ReadableTypeId; -use crate::World; /// Composable representation of styled content. /// @@ -40,22 +34,27 @@ impl Content { } } + /// Whether the content is empty. pub fn is_empty(&self) -> bool { self.downcast::().map_or(false, |seq| seq.0.is_empty()) } + /// The id of the contained node. pub fn id(&self) -> NodeId { (*self.0).id() } + /// Whether the contained node is of type `T`. pub fn is(&self) -> bool { (*self.0).as_any().is::() } + /// Cast to `T` if the contained node is of type `T`. pub fn downcast(&self) -> Option<&T> { (*self.0).as_any().downcast_ref::() } + /// Try to cast to a mutable instance of `T`. fn try_downcast_mut(&mut self) -> Option<&mut T> { Arc::get_mut(&mut self.0)?.as_any_mut().downcast_mut::() } @@ -120,51 +119,6 @@ impl Content { pub fn unguard(&self, sel: Selector) -> Self { self.clone().styled_with_entry(StyleEntry::Unguard(sel)) } - - #[comemo::memoize] - pub fn layout_block( - &self, - world: Tracked, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult> { - let barrier = StyleEntry::Barrier(Barrier::new(self.id())); - let styles = barrier.chain(&styles); - - if let Some(node) = self.to::() { - if node.level() == Level::Block { - return node.layout(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(world, regions, shared) - } - - - #[comemo::memoize] - pub fn layout_inline( - &self, - world: Tracked, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult> { - let barrier = StyleEntry::Barrier(Barrier::new(self.id())); - let styles = barrier.chain(&styles); - - if let Some(node) = self.to::() { - return node.layout(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(world, regions, shared) - } } impl Default for Content { @@ -293,6 +247,11 @@ pub trait Node: 'static { fn vtable(&self, id: TypeId) -> Option<*const ()>; } +/// A capability a node can have. +/// +/// This is implemented by trait objects. +pub trait Capability: 'static + Send + Sync {} + /// A unique identifier for a node. #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct NodeId(ReadableTypeId); @@ -310,15 +269,12 @@ impl Debug for NodeId { } } -/// A capability a node can have. -/// -/// This is implemented by trait objects. -pub trait Capability: 'static + Send + Sync {} - /// A node with applied styles. #[derive(Clone, Hash)] pub struct StyledNode { + /// The styled content. pub sub: Content, + /// The styles. pub map: StyleMap, } diff --git a/src/model/fold.rs b/src/model/fold.rs deleted file mode 100644 index bc85be019..000000000 --- a/src/model/fold.rs +++ /dev/null @@ -1,81 +0,0 @@ -use super::Smart; -use crate::geom::{Abs, Corners, Length, Rel, Sides}; - -/// A property that is folded to determine its final value. -pub trait Fold { - /// The type of the folded output. - type Output; - - /// Fold this inner value with an outer folded value. - fn fold(self, outer: Self::Output) -> Self::Output; -} - -impl Fold for Option -where - T: Fold, - T::Output: Default, -{ - type Output = Option; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.map(|inner| inner.fold(outer.unwrap_or_default())) - } -} - -impl Fold for Smart -where - T: Fold, - T::Output: Default, -{ - type Output = Smart; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.map(|inner| inner.fold(outer.unwrap_or_default())) - } -} - -impl Fold for Sides -where - T: Fold, -{ - type Output = Sides; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.zip(outer, |inner, outer| inner.fold(outer)) - } -} - -impl Fold for Sides>> { - type Output = Sides>; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.zip(outer, |inner, outer| inner.unwrap_or(outer)) - } -} - -impl Fold for Sides>>> { - type Output = Sides>>; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.zip(outer, |inner, outer| inner.unwrap_or(outer)) - } -} - -impl Fold for Corners -where - T: Fold, -{ - type Output = Corners; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.zip(outer, |inner, outer| inner.fold(outer)) - } -} - -impl Fold for Corners>> { - type Output = Corners>; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.zip(outer, |inner, outer| inner.unwrap_or(outer)) - } -} diff --git a/src/model/func.rs b/src/model/func.rs index 47ad44364..dff58233e 100644 --- a/src/model/func.rs +++ b/src/model/func.rs @@ -6,8 +6,8 @@ use comemo::{Track, Tracked}; use super::{Args, Eval, Flow, Node, NodeId, Route, Scope, Scopes, StyleMap, Value, Vm}; use crate::diag::{SourceResult, StrResult}; -use crate::syntax::ast::Expr; -use crate::syntax::SourceId; +use crate::syntax::ast::{self, Expr, TypedNode}; +use crate::syntax::{SourceId, SyntaxNode}; use crate::util::EcoString; use crate::World; @@ -227,3 +227,186 @@ impl Closure { result } } + +/// A visitor that determines which variables to capture for a closure. +pub struct CapturesVisitor<'a> { + external: &'a Scopes<'a>, + internal: Scopes<'a>, + captures: Scope, +} + +impl<'a> CapturesVisitor<'a> { + /// Create a new visitor for the given external scopes. + pub fn new(external: &'a Scopes) -> Self { + Self { + external, + internal: Scopes::new(None), + captures: Scope::new(), + } + } + + /// Return the scope of captured variables. + pub fn finish(self) -> Scope { + self.captures + } + + /// Bind a new internal variable. + pub fn bind(&mut self, ident: ast::Ident) { + self.internal.top.define(ident.take(), Value::None); + } + + /// Capture a variable if it isn't internal. + pub fn capture(&mut self, ident: ast::Ident) { + if self.internal.get(&ident).is_err() { + if let Ok(value) = self.external.get(&ident) { + self.captures.define_captured(ident.take(), value.clone()); + } + } + } + + /// Visit any node and collect all captured variables. + pub fn visit(&mut self, node: &SyntaxNode) { + match node.cast() { + // Every identifier is a potential variable that we need to capture. + // Identifiers that shouldn't count as captures because they + // actually bind a new name are handled below (individually through + // the expressions that contain them). + Some(ast::Expr::Ident(ident)) => self.capture(ident), + + // Code and content blocks create a scope. + Some(ast::Expr::Code(_) | ast::Expr::Content(_)) => { + self.internal.enter(); + for child in node.children() { + self.visit(child); + } + self.internal.exit(); + } + + // A closure contains parameter bindings, which are bound before the + // body is evaluated. Care must be taken so that the default values + // of named parameters cannot access previous parameter bindings. + Some(ast::Expr::Closure(expr)) => { + for param in expr.params() { + if let ast::Param::Named(named) = param { + self.visit(named.expr().as_untyped()); + } + } + + for param in expr.params() { + match param { + ast::Param::Pos(ident) => self.bind(ident), + ast::Param::Named(named) => self.bind(named.name()), + ast::Param::Sink(ident) => self.bind(ident), + } + } + + self.visit(expr.body().as_untyped()); + } + + // A let expression contains a binding, but that binding is only + // active after the body is evaluated. + Some(ast::Expr::Let(expr)) => { + if let Some(init) = expr.init() { + self.visit(init.as_untyped()); + } + self.bind(expr.binding()); + } + + // A show rule contains a binding, but that binding is only active + // after the target has been evaluated. + Some(ast::Expr::Show(show)) => { + self.visit(show.pattern().as_untyped()); + if let Some(binding) = show.binding() { + self.bind(binding); + } + self.visit(show.body().as_untyped()); + } + + // A for loop contains one or two bindings in its pattern. These are + // active after the iterable is evaluated but before the body is + // evaluated. + Some(ast::Expr::For(expr)) => { + self.visit(expr.iter().as_untyped()); + let pattern = expr.pattern(); + if let Some(key) = pattern.key() { + self.bind(key); + } + self.bind(pattern.value()); + self.visit(expr.body().as_untyped()); + } + + // An import contains items, but these are active only after the + // path is evaluated. + Some(ast::Expr::Import(expr)) => { + self.visit(expr.path().as_untyped()); + if let ast::Imports::Items(items) = expr.imports() { + for item in items { + self.bind(item); + } + } + } + + // Everything else is traversed from left to right. + _ => { + for child in node.children() { + self.visit(child); + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::syntax::parse; + + #[track_caller] + fn test(text: &str, result: &[&str]) { + let mut scopes = Scopes::new(None); + scopes.top.define("x", 0); + scopes.top.define("y", 0); + scopes.top.define("z", 0); + + let mut visitor = CapturesVisitor::new(&scopes); + let root = parse(text); + visitor.visit(&root); + + let captures = visitor.finish(); + let mut names: Vec<_> = captures.iter().map(|(k, _)| k).collect(); + names.sort(); + + assert_eq!(names, result); + } + + #[test] + fn test_captures() { + // Let binding and function definition. + test("#let x = x", &["x"]); + test("#let x; {x + y}", &["y"]); + test("#let f(x, y) = x + y", &[]); + + // Closure with different kinds of params. + test("{(x, y) => x + z}", &["z"]); + test("{(x: y, z) => x + z}", &["y"]); + test("{(..x) => x + y}", &["y"]); + test("{(x, y: x + z) => x + y}", &["x", "z"]); + + // Show rule. + test("#show x: y as x", &["y"]); + test("#show x: y as x + z", &["y", "z"]); + test("#show x: x as x", &["x"]); + + // For loop. + test("#for x in y { x + z }", &["y", "z"]); + test("#for x, y in y { x + y }", &["y"]); + + // Import. + test("#import x, y from z", &["z"]); + test("#import x, y, z from x + y", &["x", "y"]); + + // Blocks. + test("{ let x = 1; { let y = 2; y }; x + y }", &["y"]); + test("[#let x = 1]#x", &["x"]); + } +} diff --git a/src/model/layout.rs b/src/model/layout.rs deleted file mode 100644 index e8aa0e968..000000000 --- a/src/model/layout.rs +++ /dev/null @@ -1,142 +0,0 @@ -//! Layouting infrastructure. - -use std::hash::Hash; - -use comemo::Tracked; - -use super::{Builder, Capability, Content, Scratch, StyleChain}; -use crate::diag::SourceResult; -use crate::frame::Frame; -use crate::geom::{Abs, Axes, Size}; -use crate::World; - -/// Layout content into a collection of pages. -#[comemo::memoize] -pub fn layout(world: Tracked, content: &Content) -> SourceResult> { - let styles = StyleChain::with_root(&world.config().styles); - let scratch = Scratch::default(); - - let mut builder = Builder::new(world, &scratch, true); - builder.accept(content, styles)?; - - let (doc, shared) = builder.into_doc(styles)?; - doc.layout(world, shared) -} - -/// A node that can be layouted into a sequence of regions. -/// -/// Layouting returns one frame per used region. -pub trait Layout: 'static + Sync + Send { - /// Layout this node into the given regions, producing frames. - fn layout( - &self, - world: Tracked, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult>; - - /// Whether this is an inline-level or block-level node. - fn level(&self) -> Level; -} - -impl Capability for dyn Layout {} - -/// At which level a node operates. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum Level { - Inline, - Block, -} - -/// 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, - /// 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, - /// Whether nodes should expand to fill the regions instead of shrinking to - /// fit the content. - pub expand: Axes, -} - -impl Regions { - /// Create a new region sequence with exactly one region. - pub fn one(size: Size, base: Size, expand: Axes) -> 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) -> 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(&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 + '_ { - 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))) - } -} diff --git a/src/model/mod.rs b/src/model/mod.rs index aba9514ce..b4f8f6534 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,13 +1,5 @@ //! Layout and computation model. -#[macro_use] -mod styles; -mod collapse; -mod content; -mod eval; -mod layout; -mod property; -mod recipe; #[macro_use] mod cast; #[macro_use] @@ -18,38 +10,29 @@ mod dict; mod str; #[macro_use] mod value; +#[macro_use] +mod styles; mod args; -mod capture; -mod fold; +mod content; +mod eval; mod func; -pub mod methods; -pub mod ops; -mod raw; -mod realize; -mod resolve; mod scope; mod vm; +pub mod methods; +pub mod ops; + pub use self::str::*; pub use args::*; pub use array::*; -pub use capture::*; pub use cast::*; pub use content::*; pub use dict::*; pub use eval::*; -pub use fold::*; pub use func::*; -pub use layout::*; -pub use property::*; -pub use raw::*; -pub use recipe::*; -pub use resolve::*; pub use scope::*; pub use styles::*; -pub use typst_macros::node; pub use value::*; pub use vm::*; -// use collapse::*; -use realize::*; +pub use typst_macros::{capability, node}; diff --git a/src/model/ops.rs b/src/model/ops.rs index 7a8c6950b..7eb814c15 100644 --- a/src/model/ops.rs +++ b/src/model/ops.rs @@ -2,10 +2,11 @@ use std::cmp::Ordering; -use super::{Node, RawAlign, RawStroke, Regex, Smart, Value}; +use super::{Node, Regex, Smart, Value}; use crate::diag::StrResult; use crate::geom::{Axes, Axis, Length, Numeric, Rel}; use crate::library::text::TextNode; +use crate::library::{RawAlign, RawStroke}; use Value::*; /// Bail with a type mismatch error. diff --git a/src/model/property.rs b/src/model/property.rs deleted file mode 100644 index 3a498d2cc..000000000 --- a/src/model/property.rs +++ /dev/null @@ -1,195 +0,0 @@ -use std::any::Any; -use std::fmt::{self, Debug, Formatter}; -use std::hash::Hash; -use std::sync::Arc; - -use comemo::Prehashed; - -use super::{Interruption, NodeId, StyleChain}; -use crate::library::layout::PageNode; -use crate::library::structure::{DescNode, EnumNode, ListNode}; -use crate::library::text::ParNode; -use crate::util::ReadableTypeId; - -/// A style property originating from a set rule or constructor. -#[derive(Clone, Hash)] -pub struct Property { - /// The id of the property's [key](Key). - key: KeyId, - /// The id of the node the property belongs to. - node: NodeId, - /// Whether the property should only affect the first node down the - /// hierarchy. Used by constructors. - scoped: bool, - /// The property's value. - value: Arc>, - /// The name of the property. - #[cfg(debug_assertions)] - name: &'static str, -} - -impl Property { - /// Create a new property from a key-value pair. - pub fn new<'a, K: Key<'a>>(_: K, value: K::Value) -> Self { - Self { - key: KeyId::of::(), - node: K::node(), - value: Arc::new(Prehashed::new(value)), - scoped: false, - #[cfg(debug_assertions)] - name: K::NAME, - } - } - - /// Whether this property has the given key. - pub fn is<'a, K: Key<'a>>(&self) -> bool { - self.key == KeyId::of::() - } - - /// Whether this property belongs to the node `T`. - pub fn is_of(&self) -> bool { - self.node == NodeId::of::() - } - - /// Access the property's value if it is of the given key. - pub fn downcast<'a, K: Key<'a>>(&'a self) -> Option<&'a K::Value> { - if self.key == KeyId::of::() { - (**self.value).as_any().downcast_ref() - } else { - None - } - } - - /// The node this property is for. - pub fn node(&self) -> NodeId { - self.node - } - - /// Whether the property is scoped. - pub fn scoped(&self) -> bool { - self.scoped - } - - /// Make the property scoped. - pub fn make_scoped(&mut self) { - self.scoped = true; - } - - /// What kind of structure the property interrupts. - pub fn interruption(&self) -> Option { - if self.is_of::() { - Some(Interruption::Page) - } else if self.is_of::() { - Some(Interruption::Par) - } else if self.is_of::() - || self.is_of::() - || self.is_of::() - { - Some(Interruption::List) - } else { - None - } - } -} - -impl Debug for Property { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - #[cfg(debug_assertions)] - write!(f, "{} = ", self.name)?; - write!(f, "{:?}", self.value)?; - if self.scoped { - write!(f, " [scoped]")?; - } - Ok(()) - } -} - -impl PartialEq for Property { - fn eq(&self, other: &Self) -> bool { - self.key == other.key - && self.value.eq(&other.value) - && self.scoped == other.scoped - } -} - -trait Bounds: Debug + Sync + Send + 'static { - fn as_any(&self) -> &dyn Any; -} - -impl Bounds for T -where - T: Debug + Sync + Send + 'static, -{ - fn as_any(&self) -> &dyn Any { - self - } -} - -/// A style property key. -/// -/// This trait is not intended to be implemented manually, but rather through -/// the `#[node]` proc-macro. -pub trait Key<'a>: Copy + 'static { - /// The unfolded type which this property is stored as in a style map. - type Value: Debug + Clone + Hash + Sync + Send + 'static; - - /// The folded type of value that is returned when reading this property - /// from a style chain. - type Output; - - /// The name of the property, used for debug printing. - const NAME: &'static str; - - /// The id of the node the key belongs to. - fn node() -> NodeId; - - /// Compute an output value from a sequence of values belonging to this key, - /// folding if necessary. - fn get( - chain: StyleChain<'a>, - values: impl Iterator, - ) -> Self::Output; -} - -/// A unique identifier for a property key. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -struct KeyId(ReadableTypeId); - -impl KeyId { - /// The id of the given key. - pub fn of<'a, T: Key<'a>>() -> Self { - Self(ReadableTypeId::of::()) - } -} - -impl Debug for KeyId { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -/// A scoped property barrier. -/// -/// Barriers interact with [scoped](super::StyleMap::scoped) styles: A scoped -/// style can still be read through a single barrier (the one of the node it -/// _should_ apply to), but a second barrier will make it invisible. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct Barrier(NodeId); - -impl Barrier { - /// Create a new barrier for the given node. - pub fn new(node: NodeId) -> Self { - Self(node) - } - - /// Whether this barrier is for the node `T`. - pub fn is_for(&self, node: NodeId) -> bool { - self.0 == node - } -} - -impl Debug for Barrier { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Barrier for {:?}", self.0) - } -} diff --git a/src/model/realize.rs b/src/model/realize.rs deleted file mode 100644 index a99abdd35..000000000 --- a/src/model/realize.rs +++ /dev/null @@ -1,486 +0,0 @@ -use std::mem; - -use comemo::Tracked; -use typed_arena::Arena; - -use super::collapse::CollapsingBuilder; -use super::{ - Barrier, Content, Interruption, Layout, Level, Node, SequenceNode, Show, StyleChain, - StyleEntry, StyleMap, StyleVecBuilder, StyledNode, Target, -}; -use crate::diag::SourceResult; -use crate::geom::Numeric; -use crate::library::layout::{ - ColbreakNode, FlowChild, FlowNode, HNode, PageNode, PagebreakNode, PlaceNode, VNode, -}; -use crate::library::structure::{DocNode, ListItem, ListNode, DESC, ENUM, LIST}; -use crate::library::text::{ - LinebreakNode, ParChild, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode, -}; -use crate::World; - -/// Builds a document or a flow node from content. -pub(super) 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>, - /// 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)] -pub(super) struct Scratch<'a> { - /// An arena where intermediate style chains are stored. - styles: Arena>, - /// An arena where intermediate content resulting from show rules is stored. - templates: Arena, -} - -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(node) = content.downcast::() { - if let Some(realized) = styles.apply(self.world, Target::Text(&node.0))? { - let stored = self.scratch.templates.alloc(realized); - return self.accept(stored, styles); - } - } else if let Some(styled) = content.downcast::() { - return self.styled(styled, styles); - } else if let Some(seq) = content.downcast::() { - return self.sequence(seq, styles); - } else if content.has::() { - if self.show(&content, styles)? { - return Ok(()); - } - } - - if self.list.accept(content, styles) { - return Ok(()); - } - - self.interrupt(Interruption::List, styles, false)?; - - if content.is::() { - 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::().map_or(false, |node| !node.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, node: &'a Content, styles: StyleChain<'a>) -> SourceResult { - if let Some(mut realized) = styles.apply(self.world, Target::Node(node))? { - let mut map = StyleMap::new(); - let barrier = Barrier::new(node.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::() { - self.keep_next = !pagebreak.weak; - } - - if let Some(page) = content.downcast::() { - 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::() { - /* Nothing to do */ - } else if let Some(colbreak) = content.downcast::() { - if colbreak.weak { - self.0.weak(FlowChild::Colbreak, styles, 0); - } else { - self.0.destructive(FlowChild::Colbreak, styles); - } - } else if let Some(vertical) = content.downcast::() { - 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::() { - let child = FlowChild::Node(content.clone()); - if content.is::() { - 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::Node(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::() { - self.0.weak(ParChild::Text(' '.into()), styles, 2); - } else if let Some(linebreak) = content.downcast::() { - let c = if linebreak.justify { '\u{2028}' } else { '\n' }; - self.0.destructive(ParChild::Text(c.into()), styles); - } else if let Some(horizontal) = content.downcast::() { - 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::() { - self.0.supportive(ParChild::Quote { double: quote.double }, styles); - } else if let Some(node) = content.downcast::() { - self.0.supportive(ParChild::Text(node.0.clone()), styles); - } else if let Some(node) = content.to::() { - if node.level() == Level::Inline { - self.0.supportive(ParChild::Node(content.clone()), styles); - } else { - return false; - } - } 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::Node(_) => Some(false), - }) - .unwrap_or_default() - && parent - .flow - .0 - .items() - .rev() - .find_map(|child| match child { - FlowChild::Spacing(_) => None, - FlowChild::Node(node) => Some(node.is::()), - 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::() { - self.attachable = false; - } else if !content.is::() && !content.is::() { - self.attachable = true; - } - } - - if let Some(item) = content.downcast::() { - 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::()); - } else { - return false; - } - } else if !self.items.is_empty() - && (content.is::() || content.is::()) - { - 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:: { tight, attached, items }.pack(), - ENUM => ListNode:: { tight, attached, items }.pack(), - DESC | _ => ListNode:: { 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![], - } - } -} diff --git a/src/model/recipe.rs b/src/model/recipe.rs deleted file mode 100644 index c687f4fb4..000000000 --- a/src/model/recipe.rs +++ /dev/null @@ -1,185 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; -use std::hash::Hash; - -use comemo::Tracked; - -use super::{ - Args, Capability, Content, Func, Interruption, Node, NodeId, Regex, StyleChain, - StyleEntry, Value, -}; -use crate::diag::SourceResult; -use crate::library::structure::{DescNode, EnumNode, ListNode}; -use crate::library::text::TextNode; -use crate::syntax::Spanned; -use crate::World; - -/// A show rule recipe. -#[derive(Clone, PartialEq, Hash)] -pub struct Recipe { - /// The patterns to customize. - pub pattern: Pattern, - /// The function that defines the recipe. - pub func: Spanned, -} - -impl Recipe { - /// Whether the recipe is applicable to the target. - pub fn applicable(&self, target: Target) -> bool { - match (&self.pattern, target) { - (Pattern::Node(id), Target::Node(node)) => *id == node.id(), - (Pattern::Regex(_), Target::Text(_)) => true, - _ => false, - } - } - - /// Try to apply the recipe to the target. - pub fn apply( - &self, - world: Tracked, - sel: Selector, - target: Target, - ) -> SourceResult> { - let content = match (target, &self.pattern) { - (Target::Node(node), &Pattern::Node(id)) if node.id() == id => { - let node = node.to::().unwrap().unguard_parts(sel); - self.call(world, || Value::Content(node))? - } - - (Target::Text(text), Pattern::Regex(regex)) => { - let mut result = vec![]; - let mut cursor = 0; - - for mat in regex.find_iter(text) { - let start = mat.start(); - if cursor < start { - result.push(TextNode(text[cursor .. start].into()).pack()); - } - - result.push(self.call(world, || Value::Str(mat.as_str().into()))?); - cursor = mat.end(); - } - - if result.is_empty() { - return Ok(None); - } - - if cursor < text.len() { - result.push(TextNode(text[cursor ..].into()).pack()); - } - - Content::sequence(result) - } - - _ => return Ok(None), - }; - - Ok(Some(content.styled_with_entry(StyleEntry::Guard(sel)))) - } - - /// Call the recipe function, with the argument if desired. - fn call(&self, world: Tracked, arg: F) -> SourceResult - where - F: FnOnce() -> Value, - { - let args = if self.func.v.argc() == Some(0) { - Args::new(self.func.span, []) - } else { - Args::new(self.func.span, [arg()]) - }; - - Ok(self.func.v.call_detached(world, args)?.display(world)) - } - - /// What kind of structure the property interrupts. - pub fn interruption(&self) -> Option { - if let Pattern::Node(id) = self.pattern { - if id == NodeId::of::() - || id == NodeId::of::() - || id == NodeId::of::() - { - return Some(Interruption::List); - } - } - - None - } -} - -impl Debug for Recipe { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!( - f, - "Recipe matching {:?} from {:?}", - self.pattern, self.func.span - ) - } -} - -/// A show rule pattern that may match a target. -#[derive(Debug, Clone, PartialEq, Hash)] -pub enum Pattern { - /// Defines the appearence of some node. - Node(NodeId), - /// Defines text to be replaced. - Regex(Regex), -} - -impl Pattern { - /// Define a simple text replacement pattern. - pub fn text(text: &str) -> Self { - Self::Regex(Regex::new(®ex::escape(text)).unwrap()) - } -} - -/// A target for a show rule recipe. -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum Target<'a> { - /// A showable node. - Node(&'a Content), - /// A slice of text. - Text(&'a str), -} - -/// Identifies a show rule recipe. -#[derive(Debug, Copy, Clone, PartialEq, Hash)] -pub enum Selector { - /// The nth recipe from the top of the chain. - Nth(usize), - /// The base recipe for a kind of node. - Base(NodeId), -} - -/// A node that can be realized given some styles. -pub trait Show: 'static + Sync + Send { - /// Unguard nested content against recursive show rules. - fn unguard_parts(&self, sel: Selector) -> Content; - - /// Access a field on this node. - fn field(&self, name: &str) -> Option; - - /// The base recipe for this node that is executed if there is no - /// user-defined show rule. - fn realize( - &self, - world: Tracked, - styles: StyleChain, - ) -> SourceResult; - - /// Finalize this node given the realization of a base or user recipe. Use - /// this for effects that should work even in the face of a user-defined - /// show rule, for example: - /// - Application of general settable properties - /// - /// Defaults to just the realized content. - #[allow(unused_variables)] - fn finalize( - &self, - world: Tracked, - styles: StyleChain, - realized: Content, - ) -> SourceResult { - Ok(realized) - } -} - -impl Capability for dyn Show {} diff --git a/src/model/resolve.rs b/src/model/resolve.rs deleted file mode 100644 index 1ca4be69a..000000000 --- a/src/model/resolve.rs +++ /dev/null @@ -1,84 +0,0 @@ -use super::{Smart, StyleChain}; -use crate::geom::{Abs, Axes, Corners, Em, Length, Numeric, Rel, Sides}; -use crate::library::text::TextNode; - -/// A property that is resolved with other properties from the style chain. -pub trait Resolve { - /// The type of the resolved output. - type Output; - - /// Resolve the value using the style chain. - fn resolve(self, styles: StyleChain) -> Self::Output; -} - -impl Resolve for Em { - type Output = Abs; - - fn resolve(self, styles: StyleChain) -> Self::Output { - if self.is_zero() { - Abs::zero() - } else { - self.at(styles.get(TextNode::SIZE)) - } - } -} - -impl Resolve for Length { - type Output = Abs; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.abs + self.em.resolve(styles) - } -} - -impl Resolve for Option { - type Output = Option; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl Resolve for Smart { - type Output = Smart; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl Resolve for Axes { - type Output = Axes; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl Resolve for Sides { - type Output = Sides; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl Resolve for Corners { - type Output = Corners; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl Resolve for Rel -where - T: Resolve + Numeric, - ::Output: Numeric, -{ - type Output = Rel<::Output>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|abs| abs.resolve(styles)) - } -} diff --git a/src/model/str.rs b/src/model/str.rs index 62b378451..843da9a86 100644 --- a/src/model/str.rs +++ b/src/model/str.rs @@ -5,8 +5,9 @@ use std::ops::{Add, AddAssign, Deref}; use unicode_segmentation::UnicodeSegmentation; -use super::{Array, Dict, RawAlign, Value}; +use super::{Array, Dict, Value}; use crate::diag::StrResult; +use crate::library::RawAlign; use crate::util::EcoString; /// Create a new [`Str`] from a format string. diff --git a/src/model/styles.rs b/src/model/styles.rs index d160a4a6c..c58a1beb6 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -1,12 +1,20 @@ +use std::any::Any; use std::fmt::{self, Debug, Formatter}; use std::hash::Hash; use std::iter; use std::marker::PhantomData; +use std::sync::Arc; -use comemo::Tracked; +use comemo::{Prehashed, Tracked}; -use super::{Barrier, Content, Key, Property, Recipe, Selector, Show, Target}; +use super::{capability, Args, Content, Func, Node, NodeId, Regex, Smart, Value}; use crate::diag::SourceResult; +use crate::geom::{Abs, Axes, Corners, Em, Length, Numeric, Rel, Sides}; +use crate::library::layout::PageNode; +use crate::library::structure::{DescNode, EnumNode, ListNode}; +use crate::library::text::{ParNode, TextNode}; +use crate::syntax::Spanned; +use crate::util::ReadableTypeId; use crate::World; /// A map of style properties. @@ -618,3 +626,516 @@ impl<'a, T> Default for StyleVecBuilder<'a, T> { Self::new() } } + +/// A style property originating from a set rule or constructor. +#[derive(Clone, Hash)] +pub struct Property { + /// The id of the property's [key](Key). + key: KeyId, + /// The id of the node the property belongs to. + node: NodeId, + /// Whether the property should only affect the first node down the + /// hierarchy. Used by constructors. + scoped: bool, + /// The property's value. + value: Arc>, + /// The name of the property. + #[cfg(debug_assertions)] + name: &'static str, +} + +impl Property { + /// Create a new property from a key-value pair. + pub fn new<'a, K: Key<'a>>(_: K, value: K::Value) -> Self { + Self { + key: KeyId::of::(), + node: K::node(), + value: Arc::new(Prehashed::new(value)), + scoped: false, + #[cfg(debug_assertions)] + name: K::NAME, + } + } + + /// Whether this property has the given key. + pub fn is<'a, K: Key<'a>>(&self) -> bool { + self.key == KeyId::of::() + } + + /// Whether this property belongs to the node `T`. + pub fn is_of(&self) -> bool { + self.node == NodeId::of::() + } + + /// Access the property's value if it is of the given key. + pub fn downcast<'a, K: Key<'a>>(&'a self) -> Option<&'a K::Value> { + if self.key == KeyId::of::() { + (**self.value).as_any().downcast_ref() + } else { + None + } + } + + /// The node this property is for. + pub fn node(&self) -> NodeId { + self.node + } + + /// Whether the property is scoped. + pub fn scoped(&self) -> bool { + self.scoped + } + + /// Make the property scoped. + pub fn make_scoped(&mut self) { + self.scoped = true; + } + + /// What kind of structure the property interrupts. + pub fn interruption(&self) -> Option { + if self.is_of::() { + Some(Interruption::Page) + } else if self.is_of::() { + Some(Interruption::Par) + } else if self.is_of::() + || self.is_of::() + || self.is_of::() + { + Some(Interruption::List) + } else { + None + } + } +} + +impl Debug for Property { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + #[cfg(debug_assertions)] + write!(f, "{} = ", self.name)?; + write!(f, "{:?}", self.value)?; + if self.scoped { + write!(f, " [scoped]")?; + } + Ok(()) + } +} + +impl PartialEq for Property { + fn eq(&self, other: &Self) -> bool { + self.key == other.key + && self.value.eq(&other.value) + && self.scoped == other.scoped + } +} + +trait Bounds: Debug + Sync + Send + 'static { + fn as_any(&self) -> &dyn Any; +} + +impl Bounds for T +where + T: Debug + Sync + Send + 'static, +{ + fn as_any(&self) -> &dyn Any { + self + } +} + +/// A style property key. +/// +/// This trait is not intended to be implemented manually, but rather through +/// the `#[node]` proc-macro. +pub trait Key<'a>: Copy + 'static { + /// The unfolded type which this property is stored as in a style map. + type Value: Debug + Clone + Hash + Sync + Send + 'static; + + /// The folded type of value that is returned when reading this property + /// from a style chain. + type Output; + + /// The name of the property, used for debug printing. + const NAME: &'static str; + + /// The id of the node the key belongs to. + fn node() -> NodeId; + + /// Compute an output value from a sequence of values belonging to this key, + /// folding if necessary. + fn get( + chain: StyleChain<'a>, + values: impl Iterator, + ) -> Self::Output; +} + +/// A unique identifier for a property key. +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +struct KeyId(ReadableTypeId); + +impl KeyId { + /// The id of the given key. + pub fn of<'a, T: Key<'a>>() -> Self { + Self(ReadableTypeId::of::()) + } +} + +impl Debug for KeyId { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +/// A scoped property barrier. +/// +/// Barriers interact with [scoped](super::StyleMap::scoped) styles: A scoped +/// style can still be read through a single barrier (the one of the node it +/// _should_ apply to), but a second barrier will make it invisible. +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct Barrier(NodeId); + +impl Barrier { + /// Create a new barrier for the given node. + pub fn new(node: NodeId) -> Self { + Self(node) + } + + /// Whether this barrier is for the node `T`. + pub fn is_for(&self, node: NodeId) -> bool { + self.0 == node + } +} + +impl Debug for Barrier { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "Barrier for {:?}", self.0) + } +} + +/// A property that is resolved with other properties from the style chain. +pub trait Resolve { + /// The type of the resolved output. + type Output; + + /// Resolve the value using the style chain. + fn resolve(self, styles: StyleChain) -> Self::Output; +} + +impl Resolve for Em { + type Output = Abs; + + fn resolve(self, styles: StyleChain) -> Self::Output { + if self.is_zero() { + Abs::zero() + } else { + self.at(styles.get(TextNode::SIZE)) + } + } +} + +impl Resolve for Length { + type Output = Abs; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.abs + self.em.resolve(styles) + } +} + +impl Resolve for Option { + type Output = Option; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|v| v.resolve(styles)) + } +} + +impl Resolve for Smart { + type Output = Smart; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|v| v.resolve(styles)) + } +} + +impl Resolve for Axes { + type Output = Axes; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|v| v.resolve(styles)) + } +} + +impl Resolve for Sides { + type Output = Sides; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|v| v.resolve(styles)) + } +} + +impl Resolve for Corners { + type Output = Corners; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|v| v.resolve(styles)) + } +} + +impl Resolve for Rel +where + T: Resolve + Numeric, + ::Output: Numeric, +{ + type Output = Rel<::Output>; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|abs| abs.resolve(styles)) + } +} + +/// A property that is folded to determine its final value. +pub trait Fold { + /// The type of the folded output. + type Output; + + /// Fold this inner value with an outer folded value. + fn fold(self, outer: Self::Output) -> Self::Output; +} + +impl Fold for Option +where + T: Fold, + T::Output: Default, +{ + type Output = Option; + + fn fold(self, outer: Self::Output) -> Self::Output { + self.map(|inner| inner.fold(outer.unwrap_or_default())) + } +} + +impl Fold for Smart +where + T: Fold, + T::Output: Default, +{ + type Output = Smart; + + fn fold(self, outer: Self::Output) -> Self::Output { + self.map(|inner| inner.fold(outer.unwrap_or_default())) + } +} + +impl Fold for Sides +where + T: Fold, +{ + type Output = Sides; + + fn fold(self, outer: Self::Output) -> Self::Output { + self.zip(outer, |inner, outer| inner.fold(outer)) + } +} + +impl Fold for Sides>> { + type Output = Sides>; + + fn fold(self, outer: Self::Output) -> Self::Output { + self.zip(outer, |inner, outer| inner.unwrap_or(outer)) + } +} + +impl Fold for Sides>>> { + type Output = Sides>>; + + fn fold(self, outer: Self::Output) -> Self::Output { + self.zip(outer, |inner, outer| inner.unwrap_or(outer)) + } +} + +impl Fold for Corners +where + T: Fold, +{ + type Output = Corners; + + fn fold(self, outer: Self::Output) -> Self::Output { + self.zip(outer, |inner, outer| inner.fold(outer)) + } +} + +impl Fold for Corners>> { + type Output = Corners>; + + fn fold(self, outer: Self::Output) -> Self::Output { + self.zip(outer, |inner, outer| inner.unwrap_or(outer)) + } +} + +/// A show rule recipe. +#[derive(Clone, PartialEq, Hash)] +pub struct Recipe { + /// The patterns to customize. + pub pattern: Pattern, + /// The function that defines the recipe. + pub func: Spanned, +} + +impl Recipe { + /// Whether the recipe is applicable to the target. + pub fn applicable(&self, target: Target) -> bool { + match (&self.pattern, target) { + (Pattern::Node(id), Target::Node(node)) => *id == node.id(), + (Pattern::Regex(_), Target::Text(_)) => true, + _ => false, + } + } + + /// Try to apply the recipe to the target. + pub fn apply( + &self, + world: Tracked, + sel: Selector, + target: Target, + ) -> SourceResult> { + let content = match (target, &self.pattern) { + (Target::Node(node), &Pattern::Node(id)) if node.id() == id => { + let node = node.to::().unwrap().unguard_parts(sel); + self.call(world, || Value::Content(node))? + } + + (Target::Text(text), Pattern::Regex(regex)) => { + let mut result = vec![]; + let mut cursor = 0; + + for mat in regex.find_iter(text) { + let start = mat.start(); + if cursor < start { + result.push(TextNode(text[cursor .. start].into()).pack()); + } + + result.push(self.call(world, || Value::Str(mat.as_str().into()))?); + cursor = mat.end(); + } + + if result.is_empty() { + return Ok(None); + } + + if cursor < text.len() { + result.push(TextNode(text[cursor ..].into()).pack()); + } + + Content::sequence(result) + } + + _ => return Ok(None), + }; + + Ok(Some(content.styled_with_entry(StyleEntry::Guard(sel)))) + } + + /// Call the recipe function, with the argument if desired. + fn call(&self, world: Tracked, arg: F) -> SourceResult + where + F: FnOnce() -> Value, + { + let args = if self.func.v.argc() == Some(0) { + Args::new(self.func.span, []) + } else { + Args::new(self.func.span, [arg()]) + }; + + Ok(self.func.v.call_detached(world, args)?.display(world)) + } + + /// What kind of structure the property interrupts. + pub fn interruption(&self) -> Option { + if let Pattern::Node(id) = self.pattern { + if id == NodeId::of::() + || id == NodeId::of::() + || id == NodeId::of::() + { + return Some(Interruption::List); + } + } + + None + } +} + +impl Debug for Recipe { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!( + f, + "Recipe matching {:?} from {:?}", + self.pattern, self.func.span + ) + } +} + +/// A show rule pattern that may match a target. +#[derive(Debug, Clone, PartialEq, Hash)] +pub enum Pattern { + /// Defines the appearence of some node. + Node(NodeId), + /// Defines text to be replaced. + Regex(Regex), +} + +impl Pattern { + /// Define a simple text replacement pattern. + pub fn text(text: &str) -> Self { + Self::Regex(Regex::new(®ex::escape(text)).unwrap()) + } +} + +/// A target for a show rule recipe. +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum Target<'a> { + /// A showable node. + Node(&'a Content), + /// A slice of text. + Text(&'a str), +} + +/// Identifies a show rule recipe. +#[derive(Debug, Copy, Clone, PartialEq, Hash)] +pub enum Selector { + /// The nth recipe from the top of the chain. + Nth(usize), + /// The base recipe for a kind of node. + Base(NodeId), +} + +/// A node that can be realized given some styles. +#[capability] +pub trait Show: 'static + Sync + Send { + /// Unguard nested content against recursive show rules. + fn unguard_parts(&self, sel: Selector) -> Content; + + /// Access a field on this node. + fn field(&self, name: &str) -> Option; + + /// The base recipe for this node that is executed if there is no + /// user-defined show rule. + fn realize( + &self, + world: Tracked, + styles: StyleChain, + ) -> SourceResult; + + /// Finalize this node given the realization of a base or user recipe. Use + /// this for effects that should work even in the face of a user-defined + /// show rule, for example: + /// - Application of general settable properties + /// + /// Defaults to just the realized content. + #[allow(unused_variables)] + fn finalize( + &self, + world: Tracked, + styles: StyleChain, + realized: Content, + ) -> SourceResult { + Ok(realized) + } +} diff --git a/src/model/value.rs b/src/model/value.rs index 4741401c5..d68f42a08 100644 --- a/src/model/value.rs +++ b/src/model/value.rs @@ -307,7 +307,7 @@ where } fn hash128(&self) -> u128 { - // Also hash the TypeId since nodes with different types but + // Also hash the TypeId since values with different types but // equal data should be different. let mut state = SipHasher::new(); self.type_id().hash(&mut state);