From 37ac5d966ebaf97ac79c507028cd5b742b510b89 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 1 Nov 2022 16:56:35 +0100 Subject: [PATCH] More dynamic content representation --- macros/src/lib.rs | 50 +++- src/lib.rs | 26 +- src/library/graphics/hide.rs | 12 +- src/library/graphics/image.rs | 10 +- src/library/graphics/line.rs | 8 +- src/library/graphics/shape.rs | 16 +- src/library/layout/align.rs | 26 +- src/library/layout/columns.rs | 24 +- src/library/layout/container.rs | 78 +++++- src/library/layout/flow.rs | 15 +- src/library/layout/grid.rs | 28 +- src/library/layout/pad.rs | 14 +- src/library/layout/page.rs | 17 +- src/library/layout/place.rs | 16 +- src/library/layout/spacing.rs | 17 +- src/library/layout/stack.rs | 26 +- src/library/layout/transform.rs | 30 +- src/library/math/mod.rs | 54 ++-- src/library/mod.rs | 203 ++++++++++---- src/library/prelude.rs | 9 +- src/library/structure/heading.rs | 13 +- src/library/structure/list.rs | 48 ++-- src/library/structure/reference.rs | 8 +- src/library/structure/table.rs | 16 +- src/library/text/deco.rs | 6 +- src/library/text/link.rs | 22 +- src/library/text/mod.rs | 159 +++++++---- src/library/text/par.rs | 60 ++-- src/library/text/raw.rs | 21 +- src/library/text/repeat.rs | 24 -- src/library/text/shift.rs | 47 ++-- src/model/cast.rs | 10 +- src/model/collapse.rs | 2 +- src/model/content.rs | 431 ++++++++++++++++------------- src/model/eval.rs | 113 ++++---- src/model/func.rs | 24 +- src/model/layout.rs | 255 ++--------------- src/model/methods.rs | 1 - src/model/mod.rs | 4 +- src/model/ops.rs | 11 +- src/model/property.rs | 55 ++-- src/model/realize.rs | 224 +++++++-------- src/model/recipe.rs | 50 +++- src/model/show.rs | 127 --------- src/model/styles.rs | 36 +-- src/model/value.rs | 35 +-- src/model/vm.rs | 11 +- src/util/fat.rs | 64 +++++ src/util/mod.rs | 8 +- 49 files changed, 1328 insertions(+), 1236 deletions(-) delete mode 100644 src/library/text/repeat.rs delete mode 100644 src/model/show.rs create mode 100644 src/util/fat.rs diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 62e27a092..5c3676046 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -12,7 +12,7 @@ use syn::{Error, Ident, Result}; #[proc_macro_attribute] pub fn node(stream: TokenStream, item: TokenStream) -> TokenStream { let impl_block = syn::parse_macro_input!(item as syn::ItemImpl); - expand(TokenStream2::from(stream), impl_block) + expand(stream.into(), impl_block) .unwrap_or_else(|err| err.to_compile_error()) .into() } @@ -51,14 +51,35 @@ fn expand(stream: TokenStream2, mut impl_block: syn::ItemImpl) -> Result crate::diag::SourceResult { + unimplemented!() + } + } + }); let set = generate_set(&properties, set); - let showable = match stream.to_string().as_str() { - "" => false, - "showable" => true, - _ => return Err(Error::new(stream.span(), "unrecognized argument")), + + let items: syn::punctuated::Punctuated = + parse_quote! { #stream }; + + let checks = items.iter().map(|cap| { + quote! { + if id == TypeId::of::() { + return Some(unsafe { crate::util::fat::vtable(self as &dyn #cap) }); + } + } + }); + + let vtable = quote! { + fn vtable(&self, id: TypeId) -> Option<*const ()> { + #(#checks)* + None + } }; // Put everything into a module with a hopefully unique type to isolate @@ -75,9 +96,13 @@ fn expand(stream: TokenStream2, mut impl_block: syn::ItemImpl) -> Result model::Node for #self_ty { - const SHOWABLE: bool = #showable; #construct #set + #vtable + + fn id(&self) -> model::NodeId { + model::NodeId::of::() + } } #(#key_modules)* @@ -331,7 +356,7 @@ fn generate_set( ) -> syn::ImplItemMethod { let user = user.map(|method| { let block = &method.block; - quote! { (|| -> SourceResult<()> { #block; Ok(()) } )()?; } + quote! { (|| -> crate::diag::SourceResult<()> { #block; Ok(()) } )()?; } }); let mut shorthands = vec![]; @@ -367,8 +392,11 @@ fn generate_set( }); parse_quote! { - fn set(args: &mut Args, constructor: bool) -> SourceResult { - let mut styles = StyleMap::new(); + fn set( + args: &mut model::Args, + constructor: bool, + ) -> crate::diag::SourceResult { + let mut styles = model::StyleMap::new(); #user #(#bindings)* #(#sets)* diff --git a/src/lib.rs b/src/lib.rs index c7e47ffac..caba97072 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,8 +54,7 @@ use comemo::{Prehashed, Track}; use crate::diag::{FileResult, SourceResult}; use crate::font::{Font, FontBook}; use crate::frame::Frame; -use crate::model::StyleMap; -use crate::model::{Content, Route, Scope}; +use crate::model::{Content, Route, Scope, StyleMap}; use crate::syntax::{Source, SourceId}; use crate::util::{Buffer, EcoString}; @@ -130,13 +129,18 @@ impl Default for Config { /// Definition of certain standard library items the language is aware of. #[derive(Debug, Clone, Hash)] pub struct LangItems { - pub strong: fn(Content) -> Content, - pub emph: fn(Content) -> Content, - pub raw: fn(EcoString, Option, bool) -> Content, - pub link: fn(EcoString) -> Content, - pub ref_: fn(EcoString) -> Content, - pub heading: fn(NonZeroUsize, Content) -> Content, - pub list_item: fn(Content) -> Content, - pub enum_item: fn(Option, Content) -> Content, - pub desc_item: fn(Content, Content) -> Content, + pub space: fn() -> Content, + pub linebreak: fn(justify: bool) -> Content, + pub text: fn(text: EcoString) -> Content, + pub smart_quote: fn(double: bool) -> Content, + pub parbreak: fn() -> Content, + pub strong: fn(body: Content) -> Content, + pub emph: fn(body: Content) -> Content, + pub raw: fn(text: EcoString, tag: Option, block: bool) -> Content, + pub link: fn(label: EcoString) -> Content, + pub ref_: fn(target: EcoString) -> Content, + pub heading: fn(level: NonZeroUsize, body: Content) -> Content, + pub list_item: fn(body: Content) -> Content, + pub enum_item: fn(number: Option, body: Content) -> Content, + pub desc_item: fn(term: Content, body: Content) -> Content, } diff --git a/src/library/graphics/hide.rs b/src/library/graphics/hide.rs index 656842722..fafd74215 100644 --- a/src/library/graphics/hide.rs +++ b/src/library/graphics/hide.rs @@ -2,12 +2,12 @@ use crate::library::prelude::*; /// Hide a node without affecting layout. #[derive(Debug, Hash)] -pub struct HideNode(pub LayoutNode); +pub struct HideNode(pub Content); -#[node] +#[node(Layout)] impl HideNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { - Ok(Content::inline(Self(args.expect("body")?))) + Ok(Self(args.expect("body")?).pack()) } } @@ -18,10 +18,14 @@ impl Layout for HideNode { regions: &Regions, styles: StyleChain, ) -> SourceResult> { - let mut frames = self.0.layout(world, regions, styles)?; + let mut frames = self.0.layout_inline(world, regions, styles)?; for frame in &mut frames { frame.clear(); } Ok(frames) } + + fn level(&self) -> Level { + Level::Inline + } } diff --git a/src/library/graphics/image.rs b/src/library/graphics/image.rs index 343b4788c..9c3a775ad 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] +#[node(Layout)] impl ImageNode { /// How the image should adjust itself to a given area. pub const FIT: ImageFit = ImageFit::Cover; @@ -32,9 +32,7 @@ impl ImageNode { let width = args.named("width")?; let height = args.named("height")?; - Ok(Content::inline( - ImageNode(image).pack().sized(Axes::new(width, height)), - )) + Ok(ImageNode(image).pack().boxed(Axes::new(width, height))) } } @@ -97,6 +95,10 @@ 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 78878014e..c2f894049 100644 --- a/src/library/graphics/line.rs +++ b/src/library/graphics/line.rs @@ -9,7 +9,7 @@ pub struct LineNode { delta: Axes>, } -#[node] +#[node(Layout)] impl LineNode { /// How to stroke the line. #[property(resolve, fold)] @@ -32,7 +32,7 @@ impl LineNode { } }; - Ok(Content::inline(Self { origin, delta })) + Ok(Self { origin, delta }.pack()) } } @@ -65,6 +65,10 @@ 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 7a7421093..608c9842d 100644 --- a/src/library/graphics/shape.rs +++ b/src/library/graphics/shape.rs @@ -5,7 +5,7 @@ use crate::library::text::TextNode; /// Place a node into a sizable and fillable shape. #[derive(Debug, Hash)] -pub struct ShapeNode(pub Option); +pub struct ShapeNode(pub Option); /// Place a node into a square. pub type SquareNode = ShapeNode; @@ -19,7 +19,7 @@ pub type CircleNode = ShapeNode; /// Place a node into an ellipse. pub type EllipseNode = ShapeNode; -#[node] +#[node(Layout)] impl ShapeNode { /// How to fill the shape. pub const FILL: Option = None; @@ -55,9 +55,7 @@ impl ShapeNode { size => size, }; - Ok(Content::inline( - Self(args.eat()?).pack().sized(Axes::new(width, height)), - )) + Ok(Self(args.eat()?).pack().boxed(Axes::new(width, height))) } fn set(...) { @@ -92,7 +90,7 @@ impl Layout for ShapeNode { let child = child.clone().padded(inset.map(|side| side.map(Length::from))); let mut pod = Regions::one(regions.first, regions.base, regions.expand); - frames = child.layout(world, &pod, styles)?; + frames = child.layout_inline(world, &pod, styles)?; for frame in frames.iter_mut() { frame.apply_role(Role::GenericBlock); @@ -112,7 +110,7 @@ impl Layout for ShapeNode { pod.first = Size::splat(length); pod.expand = Axes::splat(true); - frames = child.layout(world, &pod, styles)?; + frames = child.layout_inline(world, &pod, styles)?; } } else { // The default size that a shape takes on if it has no child and @@ -175,6 +173,10 @@ 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 95f5c01f9..f49763b5e 100644 --- a/src/library/layout/align.rs +++ b/src/library/layout/align.rs @@ -7,21 +7,25 @@ pub struct AlignNode { /// How to align the node horizontally and vertically. pub aligns: Axes>, /// The node to be aligned. - pub child: LayoutNode, + pub child: Content, } -#[node] +#[node(Layout)] impl AlignNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let aligns: Axes> = args.find()?.unwrap_or_default(); let body: Content = args.expect("body")?; - Ok(match (body, aligns) { - (Content::Block(node), _) => Content::Block(node.aligned(aligns)), - (other, Axes { x: Some(x), y: None }) => { - other.styled(ParNode::ALIGN, HorizontalAlign(x)) + + if let Axes { x: Some(x), y: None } = aligns { + if body + .to::() + .map_or(true, |node| node.level() == Level::Inline) + { + return Ok(body.styled(ParNode::ALIGN, HorizontalAlign(x))); } - (other, _) => Content::Block(other.pack().aligned(aligns)), - }) + } + + Ok(body.aligned(aligns)) } } @@ -43,7 +47,7 @@ impl Layout for AlignNode { } // Layout the child. - let mut frames = self.child.layout(world, &pod, passed.chain(&styles))?; + let mut frames = self.child.layout_block(world, &pod, passed.chain(&styles))?; for (region, frame) in regions.iter().zip(&mut frames) { // Align in the target size. The target size depends on whether we // should expand. @@ -58,4 +62,8 @@ 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 3ba3598ca..79d98e110 100644 --- a/src/library/layout/columns.rs +++ b/src/library/layout/columns.rs @@ -8,20 +8,21 @@ pub struct ColumnsNode { pub columns: NonZeroUsize, /// The child to be layouted into the columns. Most likely, this should be a /// flow or stack node. - pub child: LayoutNode, + pub child: Content, } -#[node] +#[node(Layout)] impl ColumnsNode { /// The size of the gutter space between each column. #[property(resolve)] pub const GUTTER: Rel = Ratio::new(0.04).into(); fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { - Ok(Content::block(Self { + Ok(Self { columns: args.expect("column count")?, child: args.expect("body")?, - })) + } + .pack()) } } @@ -35,7 +36,7 @@ impl Layout for ColumnsNode { // Separating the infinite space into infinite columns does not make // much sense. if !regions.first.x.is_finite() { - return self.child.layout(world, regions, styles); + return self.child.layout_block(world, regions, styles); } // Determine the width of the gutter and each column. @@ -57,7 +58,7 @@ impl Layout for ColumnsNode { }; // Layout the children. - let mut frames = self.child.layout(world, &pod, styles)?.into_iter(); + let mut frames = self.child.layout_block(world, &pod, styles)?.into_iter(); let mut finished = vec![]; let dir = styles.get(TextNode::DIR); @@ -99,15 +100,22 @@ impl Layout for ColumnsNode { Ok(finished) } + + fn level(&self) -> Level { + Level::Block + } } /// A column break. -pub struct ColbreakNode; +#[derive(Debug, Clone, Hash)] +pub struct ColbreakNode { + pub weak: bool, +} #[node] impl ColbreakNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let weak = args.named("weak")?.unwrap_or(false); - Ok(Content::Colbreak { weak }) + Ok(Self { weak }.pack()) } } diff --git a/src/library/layout/container.rs b/src/library/layout/container.rs index 9b1f8f568..60f9139b4 100644 --- a/src/library/layout/container.rs +++ b/src/library/layout/container.rs @@ -1,24 +1,88 @@ use crate::library::prelude::*; /// An inline-level container that sizes content and places it into a paragraph. -pub struct BoxNode; +#[derive(Debug, Clone, Hash)] +pub struct BoxNode { + /// How to size the node horizontally and vertically. + pub sizing: Axes>>, + /// The node to be sized. + pub child: Content, +} -#[node] +#[node(Layout)] impl BoxNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let width = args.named("width")?; let height = args.named("height")?; - let body: LayoutNode = args.eat()?.unwrap_or_default(); - Ok(Content::inline(body.sized(Axes::new(width, height)))) + let body = args.eat::()?.unwrap_or_default(); + Ok(body.boxed(Axes::new(width, height))) + } +} + +impl Layout for BoxNode { + fn layout( + &self, + world: Tracked, + regions: &Regions, + styles: StyleChain, + ) -> SourceResult> { + // The "pod" is the region into which the child will be layouted. + let pod = { + // Resolve the sizing to a concrete size. + let size = self + .sizing + .resolve(styles) + .zip(regions.base) + .map(|(s, b)| s.map(|v| v.relative_to(b))) + .unwrap_or(regions.first); + + // Select the appropriate base and expansion for the child depending + // on whether it is automatically or relatively sized. + let is_auto = self.sizing.as_ref().map(Option::is_none); + let base = is_auto.select(regions.base, size); + let expand = regions.expand | !is_auto; + + Regions::one(size, base, expand) + }; + + // Layout the child. + let mut frames = self.child.layout_inline(world, &pod, styles)?; + + // Ensure frame size matches regions size if expansion is on. + let frame = &mut frames[0]; + let target = regions.expand.select(regions.first, frame.size()); + frame.resize(target, Align::LEFT_TOP); + + Ok(frames) + } + + fn level(&self) -> Level { + Level::Inline } } /// A block-level container that places content into a separate flow. -pub struct BlockNode; +#[derive(Debug, Clone, Hash)] +pub struct BlockNode(pub Content); -#[node] +#[node(Layout)] impl BlockNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { - Ok(Content::Block(args.eat()?.unwrap_or_default())) + Ok(Self(args.eat()?.unwrap_or_default()).pack()) + } +} + +impl Layout for BlockNode { + fn layout( + &self, + world: Tracked, + regions: &Regions, + styles: StyleChain, + ) -> 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 1f0a2b4ae..01ee9dc90 100644 --- a/src/library/layout/flow.rs +++ b/src/library/layout/flow.rs @@ -17,11 +17,14 @@ pub enum FlowChild { /// Vertical spacing between other children. Spacing(Spacing), /// An arbitrary block-level node. - Node(LayoutNode), + Node(Content), /// A column / region break. Colbreak, } +#[node(Layout)] +impl FlowNode {} + impl Layout for FlowNode { fn layout( &self, @@ -48,6 +51,10 @@ impl Layout for FlowNode { Ok(layouter.finish()) } + + fn level(&self) -> Level { + Level::Block + } } impl Debug for FlowNode { @@ -150,7 +157,7 @@ impl FlowLayouter { pub fn layout_node( &mut self, world: Tracked, - node: &LayoutNode, + node: &Content, styles: StyleChain, ) -> SourceResult<()> { // Don't even try layouting into a full region. @@ -162,7 +169,7 @@ impl FlowLayouter { // aligned later. if let Some(placed) = node.downcast::() { if placed.out_of_flow() { - let frame = node.layout(world, &self.regions, styles)?.remove(0); + let frame = node.layout_block(world, &self.regions, styles)?.remove(0); self.items.push(FlowItem::Placed(frame)); return Ok(()); } @@ -180,7 +187,7 @@ impl FlowLayouter { .unwrap_or(Align::Top), ); - let frames = node.layout(world, &self.regions, styles)?; + let frames = node.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 a1098c6d1..7e5cbbd52 100644 --- a/src/library/layout/grid.rs +++ b/src/library/layout/grid.rs @@ -8,10 +8,10 @@ pub struct GridNode { /// Defines sizing of gutter rows and columns between content. pub gutter: Axes>, /// The nodes to be arranged in a grid. - pub cells: Vec, + pub cells: Vec, } -#[node] +#[node(Layout)] impl GridNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let columns = args.named("columns")?.unwrap_or_default(); @@ -19,14 +19,15 @@ impl GridNode { let base_gutter: Vec = args.named("gutter")?.unwrap_or_default(); let column_gutter = args.named("column-gutter")?; let row_gutter = args.named("row-gutter")?; - Ok(Content::block(Self { + Ok(Self { tracks: Axes::new(columns, rows), gutter: Axes::new( column_gutter.unwrap_or_else(|| base_gutter.clone()), row_gutter.unwrap_or(base_gutter), ), cells: args.all()?, - })) + } + .pack()) } } @@ -50,6 +51,10 @@ 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. @@ -95,7 +100,7 @@ pub struct GridLayouter<'a> { /// The core context. world: Tracked<'a, dyn World>, /// The grid cells. - cells: &'a [LayoutNode], + cells: &'a [Content], /// The column tracks including gutter tracks. cols: Vec, /// The row tracks including gutter tracks. @@ -136,7 +141,7 @@ impl<'a> GridLayouter<'a> { world: Tracked<'a, dyn World>, tracks: Axes<&[TrackSizing]>, gutter: Axes<&[TrackSizing]>, - cells: &'a [LayoutNode], + cells: &'a [Content], regions: &Regions, styles: StyleChain<'a>, ) -> Self { @@ -301,7 +306,8 @@ impl<'a> GridLayouter<'a> { v.resolve(self.styles).relative_to(self.regions.base.y); } - let frame = node.layout(self.world, &pod, self.styles)?.remove(0); + let frame = + node.layout_block(self.world, &pod, self.styles)?.remove(0); resolved.set_max(frame.width()); } } @@ -371,7 +377,7 @@ impl<'a> GridLayouter<'a> { } let mut sizes = node - .layout(self.world, &pod, self.styles)? + .layout_block(self.world, &pod, self.styles)? .into_iter() .map(|frame| frame.height()); @@ -460,7 +466,7 @@ impl<'a> GridLayouter<'a> { .select(self.regions.base, size); let pod = Regions::one(size, base, Axes::splat(true)); - let frame = node.layout(self.world, &pod, self.styles)?.remove(0); + let frame = node.layout_block(self.world, &pod, self.styles)?.remove(0); match frame.role() { Some(Role::ListLabel | Role::ListItemBody) => { output.apply_role(Role::ListItem) @@ -508,7 +514,7 @@ impl<'a> GridLayouter<'a> { } // Push the layouted frames into the individual output frames. - let frames = node.layout(self.world, &pod, self.styles)?; + let frames = node.layout_block(self.world, &pod, self.styles)?; for (output, frame) in outputs.iter_mut().zip(frames) { match frame.role() { Some(Role::ListLabel | Role::ListItemBody) => { @@ -576,7 +582,7 @@ impl<'a> GridLayouter<'a> { /// /// Returns `None` if it's a gutter cell. #[track_caller] - fn cell(&self, x: usize, y: usize) -> Option<&'a LayoutNode> { + fn cell(&self, x: usize, y: usize) -> Option<&'a Content> { assert!(x < self.cols.len()); assert!(y < self.rows.len()); diff --git a/src/library/layout/pad.rs b/src/library/layout/pad.rs index b0238d403..effdd5f84 100644 --- a/src/library/layout/pad.rs +++ b/src/library/layout/pad.rs @@ -6,10 +6,10 @@ pub struct PadNode { /// The amount of padding. pub padding: Sides>, /// The child node whose sides to pad. - pub child: LayoutNode, + pub child: Content, } -#[node] +#[node(Layout)] impl PadNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let all = args.named("rest")?.or(args.find()?); @@ -19,9 +19,9 @@ impl PadNode { let top = args.named("top")?.or(y).or(all).unwrap_or_default(); let right = args.named("right")?.or(x).or(all).unwrap_or_default(); let bottom = args.named("bottom")?.or(y).or(all).unwrap_or_default(); - let body: LayoutNode = args.expect("body")?; + let body = args.expect::("body")?; let padding = Sides::new(left, top, right, bottom); - Ok(Content::block(body.padded(padding))) + Ok(body.padded(padding)) } } @@ -35,7 +35,7 @@ impl Layout for PadNode { // Layout child into padded regions. let padding = self.padding.resolve(styles); let pod = regions.map(|size| shrink(size, padding)); - let mut frames = self.child.layout(world, &pod, styles)?; + let mut frames = self.child.layout_block(world, &pod, styles)?; for frame in &mut frames { // Apply the padding inversely such that the grown size padded @@ -51,6 +51,10 @@ 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 2e5cf2f97..f5821ae6e 100644 --- a/src/library/layout/page.rs +++ b/src/library/layout/page.rs @@ -5,7 +5,7 @@ use crate::library::prelude::*; /// Layouts its child onto one or multiple pages. #[derive(PartialEq, Clone, Hash)] -pub struct PageNode(pub LayoutNode); +pub struct PageNode(pub Content); #[node] impl PageNode { @@ -41,7 +41,7 @@ impl PageNode { pub const FOREGROUND: Marginal = Marginal::None; fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { - Ok(Content::Page(Self(args.expect("body")?))) + Ok(Self(args.expect("body")?).pack()) } fn set(...) { @@ -96,7 +96,7 @@ impl PageNode { // Layout the child. let regions = Regions::repeat(size, size, size.map(Abs::is_finite)); - let mut frames = child.layout(world, ®ions, styles)?; + let mut frames = child.layout_block(world, ®ions, styles)?; let header = styles.get(Self::HEADER); let footer = styles.get(Self::FOOTER); @@ -127,7 +127,7 @@ impl PageNode { ] { if let Some(content) = marginal.resolve(world, page)? { let pod = Regions::one(area, area, Axes::splat(true)); - let mut sub = content.layout(world, &pod, styles)?.remove(0); + let mut sub = content.layout_block(world, &pod, styles)?.remove(0); sub.apply_role(role); if role == Role::Background { @@ -154,13 +154,16 @@ impl Debug for PageNode { } /// A page break. -pub struct PagebreakNode; +#[derive(Debug, Copy, Clone, Hash)] +pub struct PagebreakNode { + pub weak: bool, +} #[node] impl PagebreakNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let weak = args.named("weak")?.unwrap_or(false); - Ok(Content::Pagebreak { weak }) + Ok(Self { weak }.pack()) } } @@ -201,7 +204,7 @@ impl Cast> for Marginal { fn cast(value: Spanned) -> StrResult { match value.v { Value::None => Ok(Self::None), - Value::Str(v) => Ok(Self::Content(Content::Text(v.into()))), + Value::Str(v) => Ok(Self::Content(TextNode(v.into()).pack())), Value::Content(v) => Ok(Self::Content(v)), Value::Func(v) => Ok(Self::Func(v, value.span)), v => Err(format!( diff --git a/src/library/layout/place.rs b/src/library/layout/place.rs index 8b68c0875..42ab0fbab 100644 --- a/src/library/layout/place.rs +++ b/src/library/layout/place.rs @@ -3,18 +3,16 @@ use crate::library::prelude::*; /// Place a node at an absolute position. #[derive(Debug, Hash)] -pub struct PlaceNode(pub LayoutNode); +pub struct PlaceNode(pub Content); -#[node] +#[node(Layout)] impl PlaceNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let aligns = args.find()?.unwrap_or(Axes::with_x(Some(RawAlign::Start))); let dx = args.named("dx")?.unwrap_or_default(); let dy = args.named("dy")?.unwrap_or_default(); - let body: LayoutNode = args.expect("body")?; - Ok(Content::block(Self( - body.moved(Axes::new(dx, dy)).aligned(aligns), - ))) + let body = args.expect::("body")?; + Ok(Self(body.moved(Axes::new(dx, dy)).aligned(aligns)).pack()) } } @@ -35,7 +33,7 @@ impl Layout for PlaceNode { Regions::one(regions.base, regions.base, expand) }; - let mut frames = self.0.layout(world, &pod, styles)?; + let mut frames = self.0.layout_block(world, &pod, styles)?; // If expansion is off, zero all sizes so that we don't take up any // space in our parent. Otherwise, respect the expand settings. @@ -44,6 +42,10 @@ 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 28e52d73b..6df5cde55 100644 --- a/src/library/layout/spacing.rs +++ b/src/library/layout/spacing.rs @@ -4,26 +4,35 @@ use crate::library::prelude::*; use crate::library::text::ParNode; /// Horizontal spacing. -pub struct HNode; +#[derive(Debug, Clone, Hash)] +pub struct HNode { + pub amount: Spacing, + pub weak: bool, +} #[node] impl HNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let amount = args.expect("spacing")?; let weak = args.named("weak")?.unwrap_or(false); - Ok(Content::Horizontal { amount, weak }) + Ok(Self { amount, weak }.pack()) } } /// Vertical spacing. -pub struct VNode; +#[derive(Debug, Clone, Hash)] +pub struct VNode { + pub amount: Spacing, + pub weak: bool, + pub generated: bool, +} #[node] impl VNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let amount = args.expect("spacing")?; let weak = args.named("weak")?.unwrap_or(false); - Ok(Content::Vertical { amount, weak, generated: false }) + Ok(Self { amount, weak, generated: false }.pack()) } } diff --git a/src/library/layout/stack.rs b/src/library/layout/stack.rs index b9663dd66..9ea7965da 100644 --- a/src/library/layout/stack.rs +++ b/src/library/layout/stack.rs @@ -1,6 +1,7 @@ use super::{AlignNode, Spacing}; use crate::library::prelude::*; use crate::library::text::ParNode; +use crate::model::StyledNode; /// Arrange nodes and spacing along an axis. #[derive(Debug, Hash)] @@ -13,14 +14,15 @@ pub struct StackNode { pub children: Vec, } -#[node] +#[node(Layout)] impl StackNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { - Ok(Content::block(Self { + Ok(Self { dir: args.named("dir")?.unwrap_or(Dir::TTB), spacing: args.named("spacing")?, children: args.all()?, - })) + } + .pack()) } } @@ -55,6 +57,10 @@ impl Layout for StackNode { Ok(layouter.finish()) } + + fn level(&self) -> Level { + Level::Block + } } /// A child of a stack node. @@ -63,7 +69,7 @@ pub enum StackChild { /// Spacing between other nodes. Spacing(Spacing), /// An arbitrary node. - Node(LayoutNode), + Node(Content), } impl Debug for StackChild { @@ -82,7 +88,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.pack()), + Value::Content(v) => Self::Node(v), } /// Performs stack layout. @@ -169,7 +175,7 @@ impl<'a> StackLayouter<'a> { pub fn layout_node( &mut self, world: Tracked, - node: &LayoutNode, + node: &Content, styles: StyleChain, ) -> SourceResult<()> { if self.regions.is_full() { @@ -183,17 +189,17 @@ impl<'a> StackLayouter<'a> { .and_then(|node| node.aligns.get(self.axis)) .map(|align| align.resolve(styles)) .unwrap_or_else(|| { - if let Some(Content::Styled(styled)) = node.downcast::() { - let map = &styled.1; + if let Some(styled) = node.downcast::() { + let map = &styled.map; if map.contains(ParNode::ALIGN) { - return StyleChain::with_root(&styled.1).get(ParNode::ALIGN); + return StyleChain::with_root(map).get(ParNode::ALIGN); } } self.dir.start().into() }); - let frames = node.layout(world, &self.regions, styles)?; + let frames = node.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 ff42744a4..061efa6b3 100644 --- a/src/library/layout/transform.rs +++ b/src/library/layout/transform.rs @@ -7,18 +7,19 @@ pub struct MoveNode { /// The offset by which to move the node. pub delta: Axes>, /// The node whose contents should be moved. - pub child: LayoutNode, + pub child: Content, } -#[node] +#[node(Layout)] impl MoveNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let dx = args.named("dx")?.unwrap_or_default(); let dy = args.named("dy")?.unwrap_or_default(); - Ok(Content::inline(Self { + Ok(Self { delta: Axes::new(dx, dy), child: args.expect("body")?, - })) + } + .pack()) } } @@ -29,7 +30,7 @@ impl Layout for MoveNode { regions: &Regions, styles: StyleChain, ) -> SourceResult> { - let mut frames = self.child.layout(world, regions, styles)?; + let mut frames = self.child.layout_inline(world, regions, styles)?; let delta = self.delta.resolve(styles); for frame in &mut frames { @@ -39,6 +40,10 @@ impl Layout for MoveNode { Ok(frames) } + + fn level(&self) -> Level { + Level::Inline + } } /// Transform a node without affecting layout. @@ -47,7 +52,7 @@ pub struct TransformNode { /// Transformation to apply to the contents. pub transform: Transform, /// The node whose contents should be transformed. - pub child: LayoutNode, + pub child: Content, } /// Rotate a node without affecting layout. @@ -56,7 +61,7 @@ pub type RotateNode = TransformNode; /// Scale a node without affecting layout. pub type ScaleNode = TransformNode; -#[node] +#[node(Layout)] impl TransformNode { /// The origin of the transformation. #[property(resolve)] @@ -76,10 +81,7 @@ impl TransformNode { } }; - Ok(Content::inline(Self { - transform, - child: args.expect("body")?, - })) + Ok(Self { transform, child: args.expect("body")? }.pack()) } } @@ -91,7 +93,7 @@ impl Layout for TransformNode { styles: StyleChain, ) -> SourceResult> { let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON); - let mut frames = self.child.layout(world, regions, styles)?; + let mut frames = self.child.layout_inline(world, regions, styles)?; for frame in &mut frames { let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s)); @@ -104,6 +106,10 @@ 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 63d5f4195..84d4b6ee3 100644 --- a/src/library/math/mod.rs +++ b/src/library/math/mod.rs @@ -36,7 +36,7 @@ pub enum MathNode { Row(Arc>, Span), } -#[node(showable)] +#[node(Show, Layout)] impl MathNode { /// The math font family. #[property(referenced)] @@ -54,16 +54,6 @@ impl MathNode { } impl MathNode { - /// Whether this is a display-style node. - pub fn display(&self) -> bool { - match self { - Self::Row(row, _) => { - matches!(row.as_slice(), [MathNode::Space, .., MathNode::Space]) - } - _ => false, - } - } - /// Strip parentheses from the node. pub fn unparen(self) -> Self { if let Self::Row(row, span) = &self { @@ -80,8 +70,8 @@ impl MathNode { } impl Show for MathNode { - fn unguard(&self, _: Selector) -> ShowNode { - ShowNode::new(self.clone()) + fn unguard_parts(&self, _: Selector) -> Content { + self.clone().pack() } fn field(&self, _: &str) -> Option { @@ -89,13 +79,11 @@ impl Show for MathNode { } fn realize(&self, _: Tracked, _: StyleChain) -> SourceResult { - Ok(if self.display() { - Content::block( - LayoutNode::new(self.clone()) - .aligned(Axes::with_x(Some(Align::Center.into()))), - ) - } else { - Content::inline(self.clone()) + Ok(match self.level() { + Level::Inline => self.clone().pack(), + Level::Block => { + self.clone().pack().aligned(Axes::with_x(Some(Align::Center.into()))) + } }) } @@ -105,10 +93,11 @@ impl Show for MathNode { styles: StyleChain, realized: Content, ) -> SourceResult { - Ok(if self.display() { - realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW)) - } else { - realized + Ok(match self.level() { + Level::Inline => realized, + Level::Block => { + realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW)) + } }) } } @@ -120,13 +109,28 @@ impl Layout for MathNode { _: &Regions, styles: StyleChain, ) -> SourceResult> { - let style = if self.display() { Style::Display } else { Style::Text }; + let style = match self.level() { + Level::Inline => Style::Text, + Level::Block => Style::Display, + }; + let span = match self { &Self::Row(_, span) => span, _ => Span::detached(), }; + 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 17a7a681e..d75e24590 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -18,9 +18,11 @@ pub fn scope() -> Scope { let mut std = Scope::new(); // Text. + std.def_node::("space"); + std.def_node::("linebreak"); + std.def_node::("smartquote"); std.def_node::("text"); std.def_node::("par"); - std.def_node::("linebreak"); std.def_node::("parbreak"); std.def_node::("strong"); std.def_node::("emph"); @@ -146,27 +148,29 @@ pub fn scope() -> Scope { /// Construct the language map. pub fn items() -> LangItems { LangItems { - strong: |body| Content::show(text::StrongNode(body)), - emph: |body| Content::show(text::EmphNode(body)), + space: || text::SpaceNode.pack(), + linebreak: |justify| text::LinebreakNode { justify }.pack(), + text: |text| text::TextNode(text).pack(), + smart_quote: |double| text::SmartQuoteNode { double }.pack(), + parbreak: || text::ParbreakNode.pack(), + strong: |body| text::StrongNode(body).pack(), + emph: |body| text::EmphNode(body).pack(), raw: |text, lang, block| { - let node = Content::show(text::RawNode { text, block }); + let node = text::RawNode { text, block }.pack(); match lang { Some(_) => node.styled(text::RawNode::LANG, lang), None => node, } }, - link: |url| Content::show(text::LinkNode::from_url(url)), - ref_: |target| Content::show(structure::RefNode(target)), - heading: |level, body| Content::show(structure::HeadingNode { level, body }), - list_item: |body| Content::Item(structure::ListItem::List(Box::new(body))), + link: |url| text::LinkNode::from_url(url).pack(), + ref_: |target| structure::RefNode(target).pack(), + heading: |level, body| structure::HeadingNode { level, body }.pack(), + list_item: |body| structure::ListItem::List(Box::new(body)).pack(), enum_item: |number, body| { - Content::Item(structure::ListItem::Enum(number, Box::new(body))) + structure::ListItem::Enum(number, Box::new(body)).pack() }, desc_item: |term, body| { - Content::Item(structure::ListItem::Desc(Box::new(structure::DescItem { - term, - body, - }))) + structure::ListItem::Desc(Box::new(structure::DescItem { term, body })).pack() }, } } @@ -181,19 +185,96 @@ pub trait ContentExt { /// 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 { - Self::show(text::StrongNode(self)) + text::StrongNode(self).pack() } fn emph(self) -> Self { - Self::show(text::EmphNode(self)) + text::EmphNode(self).pack() } fn underlined(self) -> Self { - Self::show(text::DecoNode::<{ text::UNDERLINE }>(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() } } @@ -215,44 +296,66 @@ impl StyleMapExt for StyleMap { } } -/// Additional methods for layout nodes. -pub trait LayoutNodeExt { - /// 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. +#[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, } -impl LayoutNodeExt for LayoutNode { - fn aligned(self, aligns: Axes>) -> Self { - if aligns.any(Option::is_some) { - layout::AlignNode { aligns, child: self }.pack() - } else { - self +#[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 padded(self, padding: Sides>) -> Self { - if !padding.left.is_zero() - || !padding.top.is_zero() - || !padding.right.is_zero() - || !padding.bottom.is_zero() - { - layout::PadNode { padding, child: self }.pack() - } else { - self - } - } - - fn moved(self, delta: Axes>) -> Self { - if delta.any(|r| !r.is_zero()) { - layout::MoveNode { delta, child: self }.pack() - } else { - self - } + 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 dfc74ec84..756904f07 100644 --- a/src/library/prelude.rs +++ b/src/library/prelude.rs @@ -9,16 +9,17 @@ pub use std::sync::Arc; pub use comemo::Tracked; pub use typst_macros::node; -pub use super::{ContentExt, LayoutNodeExt, StyleMapExt}; +pub use super::{ContentExt, StyleMapExt}; 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, LayoutNode, - Node, RawAlign, RawStroke, Regions, Resolve, Scope, Selector, Show, ShowNode, Smart, - Str, StyleChain, StyleMap, StyleVec, Value, Vm, + 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, }; pub use crate::syntax::{Span, Spanned}; pub use crate::util::EcoString; diff --git a/src/library/structure/heading.rs b/src/library/structure/heading.rs index fa96248f5..5b056c306 100644 --- a/src/library/structure/heading.rs +++ b/src/library/structure/heading.rs @@ -1,4 +1,4 @@ -use crate::library::layout::BlockSpacing; +use crate::library::layout::{BlockNode, BlockSpacing}; use crate::library::prelude::*; use crate::library::text::{FontFamily, TextNode, TextSize}; @@ -12,7 +12,7 @@ pub struct HeadingNode { pub body: Content, } -#[node(showable)] +#[node(Show)] impl HeadingNode { /// The heading's font family. Just the normal text family if `auto`. #[property(referenced)] @@ -61,15 +61,16 @@ impl HeadingNode { pub const NUMBERED: bool = true; fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { - Ok(Content::show(Self { + Ok(Self { body: args.expect("body")?, level: args.named("level")?.unwrap_or(NonZeroUsize::new(1).unwrap()), - })) + } + .pack()) } } impl Show for HeadingNode { - fn unguard(&self, sel: Selector) -> ShowNode { + fn unguard_parts(&self, sel: Selector) -> Content { Self { body: self.body.unguard(sel), ..*self }.pack() } @@ -82,7 +83,7 @@ impl Show for HeadingNode { } fn realize(&self, _: Tracked, _: StyleChain) -> SourceResult { - Ok(Content::block(self.body.clone())) + Ok(BlockNode(self.body.clone()).pack()) } fn finalize( diff --git a/src/library/structure/list.rs b/src/library/structure/list.rs index 8a57069e0..f061c5f84 100644 --- a/src/library/structure/list.rs +++ b/src/library/structure/list.rs @@ -1,8 +1,8 @@ use unscanny::Scanner; -use crate::library::layout::{BlockSpacing, GridNode, TrackSizing}; +use crate::library::layout::{BlockSpacing, GridNode, HNode, TrackSizing}; use crate::library::prelude::*; -use crate::library::text::ParNode; +use crate::library::text::{ParNode, SpaceNode}; use crate::library::utility::Numbering; /// An unordered (bulleted) or ordered (numbered) list. @@ -22,7 +22,7 @@ pub type EnumNode = ListNode; /// A description list. pub type DescNode = ListNode; -#[node(showable)] +#[node(Show)] impl ListNode { /// How the list is labelled. #[property(referenced)] @@ -73,16 +73,17 @@ impl ListNode { .collect(), }; - Ok(Content::show(Self { + Ok(Self { tight: args.named("tight")?.unwrap_or(true), attached: args.named("attached")?.unwrap_or(false), items, - })) + } + .pack()) } } impl Show for ListNode { - fn unguard(&self, sel: Selector) -> ShowNode { + fn unguard_parts(&self, sel: Selector) -> Content { Self { items: self.items.map(|item| item.unguard(sel)), ..*self @@ -123,36 +124,37 @@ impl Show for ListNode { number = n; } - cells.push(LayoutNode::default()); + cells.push(Content::empty()); let label = if L == LIST || L == ENUM { - label.resolve(world, L, number)?.styled_with_map(map.clone()).pack() + label.resolve(world, L, number)?.styled_with_map(map.clone()) } else { - LayoutNode::default() + Content::empty() }; cells.push(label); - cells.push(LayoutNode::default()); + cells.push(Content::empty()); let body = match &item { ListItem::List(body) => body.as_ref().clone(), ListItem::Enum(_, body) => body.as_ref().clone(), ListItem::Desc(item) => Content::sequence(vec![ - Content::Horizontal { + HNode { amount: (-body_indent).into(), weak: false, - }, - (item.term.clone() + Content::Text(':'.into())).strong(), - Content::Space, + } + .pack(), + (item.term.clone() + TextNode(':'.into()).pack()).strong(), + SpaceNode.pack(), item.body.clone(), ]), }; - cells.push(body.styled_with_map(map.clone()).pack()); + cells.push(body.styled_with_map(map.clone())); number += 1; } - Ok(Content::block(GridNode { + Ok(GridNode { tracks: Axes::with_x(vec![ TrackSizing::Relative(indent.into()), TrackSizing::Auto, @@ -161,7 +163,8 @@ impl Show for ListNode { ]), gutter: Axes::with_y(vec![TrackSizing::Relative(gutter.into())]), cells, - })) + } + .pack()) } fn finalize( @@ -250,6 +253,9 @@ impl Debug for ListItem { } } +#[node] +impl ListItem {} + /// A description list item. #[derive(Clone, PartialEq, Hash)] pub struct DescItem { @@ -310,14 +316,14 @@ impl Label { ) -> SourceResult { Ok(match self { Self::Default => match kind { - LIST => Content::Text('•'.into()), - ENUM => Content::Text(format_eco!("{}.", number)), + LIST => TextNode('•'.into()).pack(), + ENUM => TextNode(format_eco!("{}.", number)).pack(), DESC | _ => panic!("description lists don't have a label"), }, Self::Pattern(prefix, numbering, upper, suffix) => { let fmt = numbering.apply(number); let mid = if *upper { fmt.to_uppercase() } else { fmt.to_lowercase() }; - Content::Text(format_eco!("{}{}{}", prefix, mid, suffix)) + TextNode(format_eco!("{}{}{}", prefix, mid, suffix)).pack() } Self::Content(content) => content.clone(), Self::Func(func, span) => { @@ -335,7 +341,7 @@ impl Cast> for Label { fn cast(value: Spanned) -> StrResult { match value.v { - Value::None => Ok(Self::Content(Content::Empty)), + Value::None => Ok(Self::Content(Content::empty())), Value::Str(pattern) => { let mut s = Scanner::new(&pattern); let mut prefix; diff --git a/src/library/structure/reference.rs b/src/library/structure/reference.rs index 425ee5184..b4e8b0474 100644 --- a/src/library/structure/reference.rs +++ b/src/library/structure/reference.rs @@ -4,15 +4,15 @@ use crate::library::prelude::*; #[derive(Debug, Hash)] pub struct RefNode(pub EcoString); -#[node(showable)] +#[node(Show)] impl RefNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { - Ok(Content::show(Self(args.expect("label")?))) + Ok(Self(args.expect("label")?).pack()) } } impl Show for RefNode { - fn unguard(&self, _: Selector) -> ShowNode { + fn unguard_parts(&self, _: Selector) -> Content { Self(self.0.clone()).pack() } @@ -24,6 +24,6 @@ impl Show for RefNode { } fn realize(&self, _: Tracked, _: StyleChain) -> SourceResult { - Ok(Content::Text(format_eco!("@{}", self.0))) + Ok(TextNode(format_eco!("@{}", self.0)).pack()) } } diff --git a/src/library/structure/table.rs b/src/library/structure/table.rs index 41dcd104f..d5e8920e8 100644 --- a/src/library/structure/table.rs +++ b/src/library/structure/table.rs @@ -12,7 +12,7 @@ pub struct TableNode { pub cells: Vec, } -#[node(showable)] +#[node(Show)] impl TableNode { /// How to fill the cells. #[property(referenced)] @@ -36,19 +36,20 @@ impl TableNode { let base_gutter: Vec = args.named("gutter")?.unwrap_or_default(); let column_gutter = args.named("column-gutter")?; let row_gutter = args.named("row-gutter")?; - Ok(Content::show(Self { + Ok(Self { tracks: Axes::new(columns, rows), gutter: Axes::new( column_gutter.unwrap_or_else(|| base_gutter.clone()), row_gutter.unwrap_or(base_gutter), ), cells: args.all()?, - })) + } + .pack()) } } impl Show for TableNode { - fn unguard(&self, sel: Selector) -> ShowNode { + fn unguard_parts(&self, sel: Selector) -> Content { Self { tracks: self.tracks.clone(), gutter: self.gutter.clone(), @@ -82,7 +83,7 @@ impl Show for TableNode { .cloned() .enumerate() .map(|(i, child)| { - let mut child = child.pack().padded(Sides::splat(padding)); + let mut child = child.padded(Sides::splat(padding)); if let Some(stroke) = stroke { child = child.stroked(stroke); @@ -98,11 +99,12 @@ impl Show for TableNode { }) .collect::>()?; - Ok(Content::block(GridNode { + Ok(GridNode { tracks: self.tracks.clone(), gutter: self.gutter.clone(), cells, - })) + } + .pack()) } fn finalize( diff --git a/src/library/text/deco.rs b/src/library/text/deco.rs index ead928f63..158647f28 100644 --- a/src/library/text/deco.rs +++ b/src/library/text/deco.rs @@ -17,7 +17,7 @@ pub type StrikethroughNode = DecoNode; /// Typeset overlined text. pub type OverlineNode = DecoNode; -#[node(showable)] +#[node(Show)] impl DecoNode { /// How to stroke the line. The text color and thickness are read from the /// font tables if `auto`. @@ -35,12 +35,12 @@ impl DecoNode { pub const EVADE: bool = true; fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { - Ok(Content::show(Self(args.expect("body")?))) + Ok(Self(args.expect("body")?).pack()) } } impl Show for DecoNode { - fn unguard(&self, sel: Selector) -> ShowNode { + fn unguard_parts(&self, sel: Selector) -> Content { Self(self.0.unguard(sel)).pack() } diff --git a/src/library/text/link.rs b/src/library/text/link.rs index 11d559562..1e9adc3e4 100644 --- a/src/library/text/link.rs +++ b/src/library/text/link.rs @@ -17,7 +17,7 @@ impl LinkNode { } } -#[node(showable)] +#[node(Show)] impl LinkNode { /// The fill color of text in the link. Just the surrounding text color /// if `auto`. @@ -26,14 +26,12 @@ impl LinkNode { pub const UNDERLINE: Smart = Smart::Auto; fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { - Ok(Content::show({ - let dest = args.expect::("destination")?; - let body = match dest { - Destination::Url(_) => args.eat()?, - Destination::Internal(_) => Some(args.expect("body")?), - }; - Self { dest, body } - })) + let dest = args.expect::("destination")?; + let body = match dest { + Destination::Url(_) => args.eat()?, + Destination::Internal(_) => Some(args.expect("body")?), + }; + Ok(Self { dest, body }.pack()) } } @@ -50,7 +48,7 @@ castable! { } impl Show for LinkNode { - fn unguard(&self, sel: Selector) -> ShowNode { + fn unguard_parts(&self, sel: Selector) -> Content { Self { dest: self.dest.clone(), body: self.body.as_ref().map(|body| body.unguard(sel)), @@ -83,9 +81,9 @@ impl Show for LinkNode { text = text.trim_start_matches(prefix); } let shorter = text.len() < url.len(); - Content::Text(if shorter { text.into() } else { url.clone() }) + TextNode(if shorter { text.into() } else { url.clone() }).pack() } - Destination::Internal(_) => Content::Empty, + Destination::Internal(_) => Content::empty(), }) .styled(TextNode::LINK, Some(self.dest.clone()))) } diff --git a/src/library/text/mod.rs b/src/library/text/mod.rs index d7d2ee387..18e747d07 100644 --- a/src/library/text/mod.rs +++ b/src/library/text/mod.rs @@ -5,7 +5,6 @@ mod link; mod par; mod quotes; mod raw; -mod repeat; mod shaping; mod shift; @@ -14,7 +13,6 @@ pub use link::*; pub use par::*; pub use quotes::*; pub use raw::*; -pub use repeat::*; pub use shaping::*; pub use shift::*; @@ -27,8 +25,8 @@ use crate::library::prelude::*; use crate::util::EcoString; /// A single run of text with the same style. -#[derive(Hash)] -pub struct TextNode; +#[derive(Debug, Clone, Hash)] +pub struct TextNode(pub EcoString); #[node] impl TextNode { @@ -142,7 +140,7 @@ impl TextNode { for item in args.items.iter().filter(|item| item.name.is_none()) { if EcoString::is(&item.value) { count += 1; - } else if Content::is(&item.value) { + } else if >>::is(&item.value) { content = true; } } @@ -433,6 +431,45 @@ impl Fold for Vec<(Tag, u32)> { } } +/// A text space. +#[derive(Debug, Clone, Hash)] +pub struct SpaceNode; + +#[node] +impl SpaceNode { + fn construct(_: &mut Vm, _: &mut Args) -> SourceResult { + Ok(Self.pack()) + } +} + +/// A line break. +#[derive(Debug, Clone, Hash)] +pub struct LinebreakNode { + pub justify: bool, +} + +#[node] +impl LinebreakNode { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { + let justify = args.named("justify")?.unwrap_or(false); + Ok(Self { justify }.pack()) + } +} + +/// A smart quote. +#[derive(Debug, Clone, Hash)] +pub struct SmartQuoteNode { + pub double: bool, +} + +#[node] +impl SmartQuoteNode { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { + let double = args.named("double")?.unwrap_or(true); + Ok(Self { double }.pack()) + } +} + /// Convert a string or content to lowercase. pub fn lower(_: &mut Vm, args: &mut Args) -> SourceResult { case(Case::Lower, args) @@ -478,6 +515,62 @@ pub fn smallcaps(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Value::Content(body.styled(TextNode::SMALLCAPS, true))) } +/// Strong text, rendered in boldface by default. +#[derive(Debug, Hash)] +pub struct StrongNode(pub Content); + +#[node(Show)] +impl StrongNode { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { + Ok(Self(args.expect("body")?).pack()) + } +} + +impl Show for StrongNode { + fn unguard_parts(&self, sel: Selector) -> Content { + Self(self.0.unguard(sel)).pack() + } + + fn field(&self, name: &str) -> Option { + match name { + "body" => Some(Value::Content(self.0.clone())), + _ => None, + } + } + + fn realize(&self, _: Tracked, _: StyleChain) -> SourceResult { + Ok(self.0.clone().styled(TextNode::BOLD, Toggle)) + } +} + +/// Emphasized text, rendered with an italic font by default. +#[derive(Debug, Hash)] +pub struct EmphNode(pub Content); + +#[node(Show)] +impl EmphNode { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { + Ok(Self(args.expect("body")?).pack()) + } +} + +impl Show for EmphNode { + fn unguard_parts(&self, sel: Selector) -> Content { + Self(self.0.unguard(sel)).pack() + } + + fn field(&self, name: &str) -> Option { + match name { + "body" => Some(Value::Content(self.0.clone())), + _ => None, + } + } + + fn realize(&self, _: Tracked, _: StyleChain) -> SourceResult { + Ok(self.0.clone().styled(TextNode::ITALIC, Toggle)) + } +} + /// A toggle that turns on and off alternatingly if folded. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct Toggle; @@ -498,59 +591,3 @@ impl Fold for Decoration { outer } } - -/// Strong text, rendered in boldface by default. -#[derive(Debug, Hash)] -pub struct StrongNode(pub Content); - -#[node(showable)] -impl StrongNode { - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { - Ok(Content::show(Self(args.expect("body")?))) - } -} - -impl Show for StrongNode { - fn unguard(&self, sel: Selector) -> ShowNode { - Self(self.0.unguard(sel)).pack() - } - - fn field(&self, name: &str) -> Option { - match name { - "body" => Some(Value::Content(self.0.clone())), - _ => None, - } - } - - fn realize(&self, _: Tracked, _: StyleChain) -> SourceResult { - Ok(self.0.clone().styled(TextNode::BOLD, Toggle)) - } -} - -/// Emphasized text, rendered with an italic font by default. -#[derive(Debug, Hash)] -pub struct EmphNode(pub Content); - -#[node(showable)] -impl EmphNode { - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { - Ok(Content::show(Self(args.expect("body")?))) - } -} - -impl Show for EmphNode { - fn unguard(&self, sel: Selector) -> ShowNode { - Self(self.0.unguard(sel)).pack() - } - - fn field(&self, name: &str) -> Option { - match name { - "body" => Some(Value::Content(self.0.clone())), - _ => None, - } - } - - fn realize(&self, _: Tracked, _: StyleChain) -> SourceResult { - Ok(self.0.clone().styled(TextNode::ITALIC, Toggle)) - } -} diff --git a/src/library/text/par.rs b/src/library/text/par.rs index 477bf97fd..7c8623669 100644 --- a/src/library/text/par.rs +++ b/src/library/text/par.rs @@ -1,10 +1,10 @@ use std::cmp::Ordering; -use unicode_bidi::{BidiInfo, Level}; +use unicode_bidi::{BidiInfo, Level as BidiLevel}; use unicode_script::{Script, UnicodeScript}; use xi_unicode::LineBreakIterator; -use super::{shape, Lang, Quoter, Quotes, RepeatNode, ShapedText, TextNode}; +use super::{shape, Lang, Quoter, Quotes, ShapedText, TextNode}; use crate::library::layout::Spacing; use crate::library::prelude::*; use crate::util::EcoString; @@ -23,10 +23,10 @@ pub enum ParChild { /// Horizontal spacing between other children. Spacing(Spacing), /// An arbitrary inline-level node. - Node(LayoutNode), + Node(Content), } -#[node] +#[node(Layout)] impl ParNode { /// The spacing between lines. #[property(resolve)] @@ -54,9 +54,9 @@ impl ParNode { // node. Instead, it just ensures that the passed content lives is in a // separate paragraph and styles it. Ok(Content::sequence(vec![ - Content::Parbreak, + ParbreakNode.pack(), args.expect("body")?, - Content::Parbreak, + ParbreakNode.pack(), ])) } } @@ -82,6 +82,10 @@ 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 { @@ -166,23 +170,39 @@ impl Resolve for Smart { } /// A paragraph break. +#[derive(Debug, Clone, Hash)] pub struct ParbreakNode; #[node] impl ParbreakNode { fn construct(_: &mut Vm, _: &mut Args) -> SourceResult { - Ok(Content::Parbreak) + Ok(Self.pack()) } } -/// A line break. -pub struct LinebreakNode; +/// A node that should be repeated to fill up a line. +#[derive(Debug, Hash)] +pub struct RepeatNode(pub Content); -#[node] -impl LinebreakNode { +#[node(Layout)] +impl RepeatNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { - let justify = args.named("justify")?.unwrap_or(false); - Ok(Content::Linebreak { justify }) + Ok(Self(args.expect("body")?).pack()) + } +} + +impl Layout for RepeatNode { + fn layout( + &self, + world: Tracked, + regions: &Regions, + styles: StyleChain, + ) -> SourceResult> { + self.0.layout_inline(world, regions, styles) + } + + fn level(&self) -> Level { + Level::Inline } } @@ -272,7 +292,7 @@ enum Segment<'a> { /// Horizontal spacing between other segments. Spacing(Spacing), /// An arbitrary inline-level layout node. - Node(&'a LayoutNode), + Node(&'a Content), } impl Segment<'_> { @@ -504,8 +524,8 @@ fn prepare<'a>( styles: StyleChain<'a>, ) -> SourceResult> { let bidi = BidiInfo::new(&text, match styles.get(TextNode::DIR) { - Dir::LTR => Some(Level::ltr()), - Dir::RTL => Some(Level::rtl()), + Dir::LTR => Some(BidiLevel::ltr()), + Dir::RTL => Some(BidiLevel::rtl()), _ => None, }); @@ -529,12 +549,12 @@ fn prepare<'a>( } }, Segment::Node(node) => { - if let Some(repeat) = node.downcast() { + if let Some(repeat) = node.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(world, &pod, styles)?.remove(0); + let mut frame = node.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)); @@ -566,13 +586,13 @@ fn shape_range<'a>( range: Range, styles: StyleChain<'a>, ) { - let mut process = |text, level: Level| { + let mut process = |text, level: BidiLevel| { let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL }; let shaped = shape(world, text, styles, dir); items.push(Item::Text(shaped)); }; - let mut prev_level = Level::ltr(); + let mut prev_level = BidiLevel::ltr(); let mut prev_script = Script::Unknown; let mut cursor = range.start; diff --git a/src/library/text/raw.rs b/src/library/text/raw.rs index 85e8133cd..0c7696361 100644 --- a/src/library/text/raw.rs +++ b/src/library/text/raw.rs @@ -5,8 +5,8 @@ use syntect::highlighting::{ }; use syntect::parsing::SyntaxSet; -use super::{FontFamily, Hyphenate, TextNode}; -use crate::library::layout::BlockSpacing; +use super::{FontFamily, Hyphenate, LinebreakNode, TextNode}; +use crate::library::layout::{BlockNode, BlockSpacing}; use crate::library::prelude::*; /// Monospaced text with optional syntax highlighting. @@ -18,7 +18,7 @@ pub struct RawNode { pub block: bool, } -#[node(showable)] +#[node(Show)] impl RawNode { /// The language to syntax-highlight in. #[property(referenced)] @@ -34,15 +34,16 @@ impl RawNode { pub const BELOW: Option = Some(Ratio::one().into()); fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { - Ok(Content::show(Self { + Ok(Self { text: args.expect("text")?, block: args.named("block")?.unwrap_or(false), - })) + } + .pack()) } } impl Show for RawNode { - fn unguard(&self, _: Selector) -> ShowNode { + fn unguard_parts(&self, _: Selector) -> Content { Self { text: self.text.clone(), ..*self }.pack() } @@ -86,7 +87,7 @@ impl Show for RawNode { let mut highlighter = HighlightLines::new(syntax, &THEME); for (i, line) in self.text.lines().enumerate() { if i != 0 { - seq.push(Content::Linebreak { justify: false }); + seq.push(LinebreakNode { justify: false }.pack()); } for (style, piece) in @@ -98,11 +99,11 @@ impl Show for RawNode { Content::sequence(seq) } else { - Content::Text(self.text.clone()) + TextNode(self.text.clone()).pack() }; if self.block { - realized = Content::block(realized); + realized = BlockNode(realized).pack(); } let mut map = StyleMap::new(); @@ -132,7 +133,7 @@ impl Show for RawNode { /// Style a piece of text with a syntect style. fn styled(piece: &str, foreground: Paint, style: Style) -> Content { - let mut body = Content::Text(piece.into()); + let mut body = TextNode(piece.into()).pack(); let paint = style.foreground.into(); if paint != foreground { diff --git a/src/library/text/repeat.rs b/src/library/text/repeat.rs deleted file mode 100644 index e3bae3fcb..000000000 --- a/src/library/text/repeat.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::library::prelude::*; - -/// A node that should be repeated to fill up a line. -#[derive(Debug, Hash)] -pub struct RepeatNode(pub LayoutNode); - -#[node] -impl RepeatNode { - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { - Ok(Content::inline(Self(args.expect("body")?))) - } -} - -impl Layout for RepeatNode { - fn layout( - &self, - world: Tracked, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult> { - // The actual repeating happens directly in the paragraph. - self.0.layout(world, regions, styles) - } -} diff --git a/src/library/text/shift.rs b/src/library/text/shift.rs index e2a636a6a..c3cf8b03b 100644 --- a/src/library/text/shift.rs +++ b/src/library/text/shift.rs @@ -1,5 +1,6 @@ -use super::{variant, TextNode, TextSize}; +use super::{variant, SpaceNode, TextNode, TextSize}; use crate::library::prelude::*; +use crate::model::SequenceNode; use crate::util::EcoString; /// Sub or superscript text. @@ -17,7 +18,7 @@ pub type SuperNode = ShiftNode; /// Shift the text into subscript. pub type SubNode = ShiftNode; -#[node] +#[node(Show)] impl ShiftNode { /// Whether to prefer the dedicated sub- and superscript characters of the /// font. @@ -29,12 +30,12 @@ impl ShiftNode { pub const SIZE: TextSize = TextSize(Em::new(0.6).into()); fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { - Ok(Content::show(Self(args.expect("body")?))) + Ok(Self(args.expect("body")?).pack()) } } impl Show for ShiftNode { - fn unguard(&self, _: Selector) -> ShowNode { + fn unguard_parts(&self, _: Selector) -> Content { Self(self.0.clone()).pack() } @@ -54,7 +55,7 @@ impl Show for ShiftNode { if styles.get(Self::TYPOGRAPHIC) { if let Some(text) = search_text(&self.0, S) { if is_shapable(world, &text, styles) { - transformed = Some(Content::Text(text)); + transformed = Some(TextNode(text).pack()); } } }; @@ -71,28 +72,26 @@ impl Show for ShiftNode { /// Find and transform the text contained in `content` to the given script kind /// if and only if it only consists of `Text`, `Space`, and `Empty` leaf nodes. fn search_text(content: &Content, mode: ScriptKind) -> Option { - match content { - Content::Text(_) => { - if let Content::Text(t) = content { - if let Some(sup) = convert_script(t, mode) { - return Some(sup); - } - } - None + if content.is_empty() { + Some(EcoString::new()) + } else if content.is::() { + Some(' '.into()) + } else if let Some(text) = content.downcast::() { + if let Some(sup) = convert_script(&text.0, mode) { + return Some(sup); } - Content::Space => Some(' '.into()), - Content::Empty => Some(EcoString::new()), - Content::Sequence(seq) => { - let mut full = EcoString::new(); - for item in seq.iter() { - match search_text(item, mode) { - Some(text) => full.push_str(&text), - None => return None, - } + None + } else if let Some(seq) = content.downcast::() { + let mut full = EcoString::new(); + for item in seq.0.iter() { + match search_text(item, mode) { + Some(text) => full.push_str(&text), + None => return None, } - Some(full) } - _ => None, + Some(full) + } else { + None } } diff --git a/src/model/cast.rs b/src/model/cast.rs index 00a3fe456..7356ef70d 100644 --- a/src/model/cast.rs +++ b/src/model/cast.rs @@ -1,6 +1,6 @@ use std::num::NonZeroUsize; -use super::{Content, Layout, LayoutNode, Pattern, Regex, Value}; +use super::{Pattern, Regex, Value}; use crate::diag::{with_alternative, StrResult}; use crate::geom::{Corners, Dir, Paint, Sides}; use crate::syntax::Spanned; @@ -170,14 +170,6 @@ castable! { Value::Str(string) => string.into(), } -castable! { - LayoutNode, - Expected: "content", - Value::None => Self::default(), - Value::Str(text) => Content::Text(text.into()).pack(), - Value::Content(content) => content.pack(), -} - castable! { Pattern, Expected: "function, string or regular expression", diff --git a/src/model/collapse.rs b/src/model/collapse.rs index 5a0d6531c..f57a3a42a 100644 --- a/src/model/collapse.rs +++ b/src/model/collapse.rs @@ -1,7 +1,7 @@ use super::{StyleChain, StyleVec, StyleVecBuilder}; /// A wrapper around a [`StyleVecBuilder`] that allows to collapse items. -pub struct CollapsingBuilder<'a, T> { +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. diff --git a/src/model/content.rs b/src/model/content.rs index 3cdc6341d..170d47a1f 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -1,124 +1,84 @@ +use std::any::{Any, TypeId}; use std::fmt::{self, Debug, Formatter}; -use std::hash::Hash; -use std::iter::Sum; +use std::hash::{Hash, Hasher}; +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::{ - Builder, Key, Layout, LayoutNode, Property, Regions, Scratch, Selector, Show, - ShowNode, StyleChain, StyleEntry, StyleMap, + Args, Barrier, Builder, Key, Layout, Level, Property, Regions, Scratch, Selector, + StyleChain, StyleEntry, StyleMap, Vm, }; use crate::diag::{SourceResult, StrResult}; use crate::frame::Frame; -use crate::geom::Abs; -use crate::library::layout::{PageNode, Spacing}; -use crate::library::structure::ListItem; -use crate::util::EcoString; +use crate::util::ReadableTypeId; use crate::World; /// Composable representation of styled content. /// /// This results from: /// - anything written between square brackets in Typst -/// - any node constructor -/// -/// Content is represented as a tree of nodes. There are two nodes of special -/// interest: -/// -/// 1. A `Styled` node attaches a style map to other content. For example, a -/// single bold word could be represented as a `Styled(Text("Hello"), -/// [TextNode::STRONG: true])` node. -/// -/// 2. A `Sequence` node content combines other arbitrary content and is the -/// representation of a "flow" of other nodes. So, when you write `[Hi] + -/// [you]` in Typst, this type's [`Add`] implementation is invoked and the -/// two [`Text`](Self::Text) nodes are combined into a single -/// [`Sequence`](Self::Sequence) node. A sequence may contain nested -/// sequences. -#[derive(PartialEq, Clone, Hash)] -pub enum Content { - /// Empty content. - Empty, - /// A word space. - Space, - /// A forced line break. - Linebreak { justify: bool }, - /// Horizontal spacing. - Horizontal { amount: Spacing, weak: bool }, - /// Plain text. - Text(EcoString), - /// A smart quote. - Quote { double: bool }, - /// An inline-level node. - Inline(LayoutNode), - /// A paragraph break. - Parbreak, - /// A column break. - Colbreak { weak: bool }, - /// Vertical spacing. - Vertical { - amount: Spacing, - weak: bool, - generated: bool, - }, - /// A block-level node. - Block(LayoutNode), - /// A list / enum item. - Item(ListItem), - /// A page break. - Pagebreak { weak: bool }, - /// A page node. - Page(PageNode), - /// A node that can be realized with styles, optionally with attached - /// properties. - Show(ShowNode), - /// Content with attached styles. - Styled(Arc<(Self, StyleMap)>), - /// A sequence of multiple nodes. - Sequence(Arc>), -} +/// - any constructor function +#[derive(Clone, Hash)] +pub struct Content(Arc); impl Content { /// Create empty content. - pub fn new() -> Self { - Self::Empty - } - - /// Create content from an inline-level node. - pub fn inline(node: T) -> Self - where - T: Layout + Debug + Hash + Sync + Send + 'static, - { - Self::Inline(node.pack()) - } - - /// Create content from a block-level node. - pub fn block(node: T) -> Self - where - T: Layout + Debug + Hash + Sync + Send + 'static, - { - Self::Block(node.pack()) - } - - /// Create content from a showable node. - pub fn show(node: T) -> Self - where - T: Show + Debug + Hash + Sync + Send + 'static, - { - Self::Show(node.pack()) + pub fn empty() -> Self { + SequenceNode(vec![]).pack() } /// Create a new sequence node from multiples nodes. pub fn sequence(seq: Vec) -> Self { match seq.as_slice() { - [] => Self::Empty, [_] => seq.into_iter().next().unwrap(), - _ => Self::Sequence(Arc::new(seq)), + _ => SequenceNode(seq).pack(), } } + pub fn is_empty(&self) -> bool { + self.downcast::().map_or(false, |seq| seq.0.is_empty()) + } + + pub fn id(&self) -> NodeId { + (*self.0).id() + } + + pub fn is(&self) -> bool { + (*self.0).as_any().is::() + } + + pub fn downcast(&self) -> Option<&T> { + (*self.0).as_any().downcast_ref::() + } + + fn try_downcast_mut(&mut self) -> Option<&mut T> { + Arc::get_mut(&mut self.0)?.as_any_mut().downcast_mut::() + } + + /// Whether this content has the given capability. + pub fn has(&self) -> bool + where + C: Capability + ?Sized, + { + self.0.vtable(TypeId::of::()).is_some() + } + + /// Cast to a trait object if this content has the given capability. + pub fn to(&self) -> Option<&C> + where + C: Capability + ?Sized, + { + let node: &dyn Bounds = &*self.0; + let vtable = node.vtable(TypeId::of::())?; + let data = node as *const dyn Bounds as *const (); + Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) }) + } + /// Repeat this content `n` times. pub fn repeat(&self, n: i64) -> StrResult { let count = usize::try_from(n) @@ -134,14 +94,12 @@ impl Content { /// Style this content with a style entry. pub fn styled_with_entry(mut self, entry: StyleEntry) -> Self { - if let Self::Styled(styled) = &mut self { - if let Some((_, map)) = Arc::get_mut(styled) { - map.apply(entry); - return self; - } + if let Some(styled) = self.try_downcast_mut::() { + styled.map.apply(entry); + return self; } - Self::Styled(Arc::new((self, entry.into()))) + StyledNode { sub: self, map: entry.into() }.pack() } /// Style this content with a full style map. @@ -150,14 +108,12 @@ impl Content { return self; } - if let Self::Styled(styled) = &mut self { - if let Some((_, map)) = Arc::get_mut(styled) { - map.apply_map(&styles); - return self; - } + if let Some(styled) = self.try_downcast_mut::() { + styled.map.apply_map(&styles); + return self; } - Self::Styled(Arc::new((self, styles))) + StyledNode { sub: self, map: styles }.pack() } /// Reenable the show rule identified by the selector. @@ -165,41 +121,22 @@ impl Content { self.clone().styled_with_entry(StyleEntry::Unguard(sel)) } - /// Add weak vertical spacing above and below the node. - pub 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(Content::Vertical { - amount: above.into(), - weak: true, - generated: true, - }); - } - - seq.push(self); - if let Some(below) = below { - seq.push(Content::Vertical { - amount: below.into(), - weak: true, - generated: true, - }); - } - - Self::sequence(seq) - } -} - -impl Layout for Content { - fn layout( + #[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)?; @@ -207,77 +144,74 @@ impl Layout for Content { flow.layout(world, regions, shared) } - fn pack(self) -> LayoutNode { - match self { - Content::Block(node) => node, - other => LayoutNode::new(other), + + #[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 { fn default() -> Self { - Self::new() + Self::empty() } } impl Debug for Content { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Empty => f.pad("Empty"), - Self::Space => f.pad("Space"), - Self::Linebreak { justify } => write!(f, "Linebreak({justify})"), - Self::Horizontal { amount, weak } => { - write!(f, "Horizontal({amount:?}, {weak})") - } - Self::Text(text) => write!(f, "Text({text:?})"), - Self::Quote { double } => write!(f, "Quote({double})"), - Self::Inline(node) => node.fmt(f), - Self::Parbreak => f.pad("Parbreak"), - Self::Colbreak { weak } => write!(f, "Colbreak({weak})"), - Self::Vertical { amount, weak, generated } => { - write!(f, "Vertical({amount:?}, {weak}, {generated})") - } - Self::Block(node) => node.fmt(f), - Self::Item(item) => item.fmt(f), - Self::Pagebreak { weak } => write!(f, "Pagebreak({weak})"), - Self::Page(page) => page.fmt(f), - Self::Show(node) => node.fmt(f), - Self::Styled(styled) => { - let (sub, map) = styled.as_ref(); - map.fmt(f)?; - sub.fmt(f) - } - Self::Sequence(seq) => f.debug_list().entries(seq.iter()).finish(), - } + self.0.fmt(f) + } +} + +impl PartialEq for Content { + fn eq(&self, other: &Self) -> bool { + (*self.0).hash128() == (*other.0).hash128() } } impl Add for Content { type Output = Self; - fn add(self, rhs: Self) -> Self::Output { - Self::Sequence(match (self, rhs) { - (Self::Empty, rhs) => return rhs, - (lhs, Self::Empty) => return lhs, - (Self::Sequence(mut lhs), Self::Sequence(rhs)) => { - let mutable = Arc::make_mut(&mut lhs); - match Arc::try_unwrap(rhs) { - Ok(vec) => mutable.extend(vec), - Err(rc) => mutable.extend(rc.iter().cloned()), - } - lhs + fn add(self, mut rhs: Self) -> Self::Output { + let mut lhs = self; + if let Some(lhs_mut) = lhs.try_downcast_mut::() { + if let Some(rhs_mut) = rhs.try_downcast_mut::() { + lhs_mut.0.extend(rhs_mut.0.drain(..)); + } else if let Some(rhs) = rhs.downcast::() { + lhs_mut.0.extend(rhs.0.iter().cloned()); + } else { + lhs_mut.0.push(rhs); } - (Self::Sequence(mut lhs), rhs) => { - Arc::make_mut(&mut lhs).push(rhs); - lhs - } - (lhs, Self::Sequence(mut rhs)) => { - Arc::make_mut(&mut rhs).insert(0, lhs); - rhs - } - (lhs, rhs) => Arc::new(vec![lhs, rhs]), - }) + return lhs; + } + + let seq = match ( + lhs.downcast::(), + rhs.downcast::(), + ) { + (Some(lhs), Some(rhs)) => lhs.0.iter().chain(&rhs.0).cloned().collect(), + (Some(lhs), None) => lhs.0.iter().cloned().chain(iter::once(rhs)).collect(), + (None, Some(rhs)) => iter::once(lhs).chain(rhs.0.iter().cloned()).collect(), + (None, None) => vec![lhs, rhs], + }; + + SequenceNode(seq).pack() } } @@ -292,3 +226,124 @@ impl Sum for Content { Self::sequence(iter.collect()) } } + +trait Bounds: Node + Debug + Sync + Send + 'static { + fn as_any(&self) -> &dyn Any; + fn as_any_mut(&mut self) -> &mut dyn Any; + fn hash128(&self) -> u128; +} + +impl Bounds for T +where + T: Node + Debug + Hash + Sync + Send + 'static, +{ + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn hash128(&self) -> u128 { + let mut state = SipHasher::new(); + self.type_id().hash(&mut state); + self.hash(&mut state); + state.finish128().as_u128() + } +} + +impl Hash for dyn Bounds { + fn hash(&self, state: &mut H) { + state.write_u128(self.hash128()); + } +} + +/// A constructable, stylable content node. +pub trait Node: 'static { + /// Pack into type-erased content. + fn pack(self) -> Content + where + Self: Debug + Hash + Sync + Send + Sized + 'static, + { + Content(Arc::new(self)) + } + + /// Construct a node from the arguments. + /// + /// This is passed only the arguments that remain after execution of the + /// node's set rule. + fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult + where + Self: Sized; + + /// Parse relevant arguments into style properties for this node. + /// + /// When `constructor` is true, [`construct`](Self::construct) will run + /// after this invocation of `set` with the remaining arguments. + fn set(args: &mut Args, constructor: bool) -> SourceResult + where + Self: Sized; + + /// A unique identifier of the node type. + fn id(&self) -> NodeId; + + /// Extract the pointer of the vtable of the trait object with the + /// given type `id` if this node implements that trait. + fn vtable(&self, id: TypeId) -> Option<*const ()>; +} + +/// A unique identifier for a node. +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct NodeId(ReadableTypeId); + +impl NodeId { + /// The id of the given node. + pub fn of() -> Self { + Self(ReadableTypeId::of::()) + } +} + +impl Debug for NodeId { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +/// 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 { + pub sub: Content, + pub map: StyleMap, +} + +#[node] +impl StyledNode {} + +impl Debug for StyledNode { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.map.fmt(f)?; + self.sub.fmt(f) + } +} + +/// A sequence of nodes. +/// +/// Combines other arbitrary content. So, when you write `[Hi] + [you]` in +/// Typst, the two text nodes are combined into a single sequence node. +#[derive(Clone, Hash)] +pub struct SequenceNode(pub Vec); + +#[node] +impl SequenceNode {} + +impl Debug for SequenceNode { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.debug_list().entries(self.0.iter()).finish() + } +} diff --git a/src/model/eval.rs b/src/model/eval.rs index ca1c7974f..02617ed6f 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -8,11 +8,12 @@ use unicode_segmentation::UnicodeSegmentation; use super::{ methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Flow, Func, - Pattern, Recipe, Scope, Scopes, Show, StyleEntry, StyleMap, Value, Vm, + Node, Pattern, Recipe, Scope, Scopes, Show, StyleEntry, StyleMap, Value, Vm, }; use crate::diag::{At, SourceResult, StrResult, Trace, Tracepoint}; use crate::geom::{Abs, Angle, Em, Fr, Ratio}; -use crate::library; +use crate::library::math; +use crate::library::text::TextNode; use crate::syntax::ast::TypedNode; use crate::syntax::{ast, SourceId, Span, Spanned, Unit}; use crate::util::EcoString; @@ -189,11 +190,11 @@ impl Eval for ast::MarkupNode { impl Eval for ast::Space { type Output = Content; - fn eval(&self, _: &mut Vm) -> SourceResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { Ok(if self.newlines() < 2 { - Content::Space + (vm.items().space)() } else { - Content::Parbreak + (vm.items().parbreak)() }) } } @@ -201,40 +202,40 @@ impl Eval for ast::Space { impl Eval for ast::Linebreak { type Output = Content; - fn eval(&self, _: &mut Vm) -> SourceResult { - Ok(Content::Linebreak { justify: false }) + fn eval(&self, vm: &mut Vm) -> SourceResult { + Ok((vm.items().linebreak)(false)) } } impl Eval for ast::Text { type Output = Content; - fn eval(&self, _: &mut Vm) -> SourceResult { - Ok(Content::Text(self.get().clone())) + fn eval(&self, vm: &mut Vm) -> SourceResult { + Ok(vm.text(self.get().clone())) } } impl Eval for ast::Escape { type Output = Content; - fn eval(&self, _: &mut Vm) -> SourceResult { - Ok(Content::Text(self.get().into())) + fn eval(&self, vm: &mut Vm) -> SourceResult { + Ok(vm.text(self.get())) } } impl Eval for ast::Shorthand { type Output = Content; - fn eval(&self, _: &mut Vm) -> SourceResult { - Ok(Content::Text(self.get().into())) + fn eval(&self, vm: &mut Vm) -> SourceResult { + Ok(vm.text(self.get())) } } impl Eval for ast::SmartQuote { type Output = Content; - fn eval(&self, _: &mut Vm) -> SourceResult { - Ok(Content::Quote { double: self.double() }) + fn eval(&self, vm: &mut Vm) -> SourceResult { + Ok((vm.items().smart_quote)(self.double())) } } @@ -277,7 +278,7 @@ impl Eval for ast::Label { type Output = Content; fn eval(&self, _: &mut Vm) -> SourceResult { - Ok(Content::Empty) + Ok(Content::empty()) } } @@ -335,26 +336,23 @@ impl Eval for ast::Math { .children() .map(|node| node.eval(vm)) .collect::>()?; - Ok(Content::show(library::math::MathNode::Row( - Arc::new(nodes), - self.span(), - ))) + Ok(math::MathNode::Row(Arc::new(nodes), self.span()).pack()) } } impl Eval for ast::MathNode { - type Output = library::math::MathNode; + type Output = math::MathNode; fn eval(&self, vm: &mut Vm) -> SourceResult { Ok(match self { - Self::Space(_) => library::math::MathNode::Space, - Self::Linebreak(_) => library::math::MathNode::Linebreak, - Self::Escape(c) => library::math::MathNode::Atom(c.get().into()), - Self::Atom(atom) => library::math::MathNode::Atom(atom.get().clone()), + Self::Space(_) => math::MathNode::Space, + Self::Linebreak(_) => math::MathNode::Linebreak, + Self::Escape(c) => math::MathNode::Atom(c.get().into()), + Self::Atom(atom) => math::MathNode::Atom(atom.get().clone()), Self::Script(node) => node.eval(vm)?, Self::Frac(node) => node.eval(vm)?, Self::Align(node) => node.eval(vm)?, - Self::Group(node) => library::math::MathNode::Row( + Self::Group(node) => math::MathNode::Row( Arc::new( node.children() .map(|node| node.eval(vm)) @@ -362,54 +360,54 @@ impl Eval for ast::MathNode { ), node.span(), ), - Self::Expr(expr) => match expr.eval(vm)?.display(vm.world) { - Content::Text(text) => library::math::MathNode::Atom(text), - _ => bail!(expr.span(), "expected text"), - }, + Self::Expr(expr) => { + let content = expr.eval(vm)?.display(vm.world); + if let Some(node) = content.downcast::() { + math::MathNode::Atom(node.0.clone()) + } else { + bail!(expr.span(), "expected text") + } + } }) } } impl Eval for ast::Script { - type Output = library::math::MathNode; + type Output = math::MathNode; fn eval(&self, vm: &mut Vm) -> SourceResult { - Ok(library::math::MathNode::Script(Arc::new( - library::math::ScriptNode { - base: self.base().eval(vm)?, - sub: self - .sub() - .map(|node| node.eval(vm)) - .transpose()? - .map(|node| node.unparen()), - sup: self - .sup() - .map(|node| node.eval(vm)) - .transpose()? - .map(|node| node.unparen()), - }, - ))) + Ok(math::MathNode::Script(Arc::new(math::ScriptNode { + base: self.base().eval(vm)?, + sub: self + .sub() + .map(|node| node.eval(vm)) + .transpose()? + .map(|node| node.unparen()), + sup: self + .sup() + .map(|node| node.eval(vm)) + .transpose()? + .map(|node| node.unparen()), + }))) } } impl Eval for ast::Frac { - type Output = library::math::MathNode; + type Output = math::MathNode; fn eval(&self, vm: &mut Vm) -> SourceResult { - Ok(library::math::MathNode::Frac(Arc::new( - library::math::FracNode { - num: self.num().eval(vm)?.unparen(), - denom: self.denom().eval(vm)?.unparen(), - }, - ))) + Ok(math::MathNode::Frac(Arc::new(math::FracNode { + num: self.num().eval(vm)?.unparen(), + denom: self.denom().eval(vm)?.unparen(), + }))) } } impl Eval for ast::Align { - type Output = library::math::MathNode; + type Output = math::MathNode; fn eval(&self, _: &mut Vm) -> SourceResult { - Ok(library::math::MathNode::Align(self.count())) + Ok(math::MathNode::Align(self.count())) } } @@ -706,8 +704,9 @@ impl Eval for ast::FieldAccess { Ok(match object { Value::Dict(dict) => dict.get(&field).at(span)?.clone(), - Value::Content(Content::Show(node)) => node - .field(&field) + Value::Content(node) => node + .to::() + .and_then(|node| node.field(&field)) .ok_or_else(|| format!("unknown field {field:?}")) .at(span)? .clone(), diff --git a/src/model/func.rs b/src/model/func.rs index a4f63aa1b..47ad44364 100644 --- a/src/model/func.rs +++ b/src/model/func.rs @@ -4,9 +4,7 @@ use std::sync::Arc; use comemo::{Track, Tracked}; -use super::{ - Args, Content, Eval, Flow, NodeId, Route, Scope, Scopes, StyleMap, Value, Vm, -}; +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; @@ -52,7 +50,7 @@ impl Func { Ok(Value::Content(content.styled_with_map(styles.scoped()))) }, set: Some(|args| T::set(args, false)), - node: T::SHOWABLE.then(|| NodeId::of::()), + node: Some(NodeId::of::()), }))) } @@ -168,24 +166,6 @@ impl Hash for Native { } } -/// A constructable, stylable content node. -pub trait Node: 'static { - /// Whether this node can be customized through a show rule. - const SHOWABLE: bool; - - /// Construct a node from the arguments. - /// - /// This is passed only the arguments that remain after execution of the - /// node's set rule. - fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult; - - /// Parse relevant arguments into style properties for this node. - /// - /// When `constructor` is true, [`construct`](Self::construct) will run - /// after this invocation of `set` with the remaining arguments. - fn set(args: &mut Args, constructor: bool) -> SourceResult; -} - /// A user-defined closure. #[derive(Hash)] pub struct Closure { diff --git a/src/model/layout.rs b/src/model/layout.rs index 5f21765dd..e8aa0e968 100644 --- a/src/model/layout.rs +++ b/src/model/layout.rs @@ -1,17 +1,13 @@ //! Layouting infrastructure. -use std::any::Any; -use std::fmt::{self, Debug, Formatter, Write}; use std::hash::Hash; -use std::sync::Arc; -use comemo::{Prehashed, Tracked}; +use comemo::Tracked; -use super::{Barrier, NodeId, Resolve, StyleChain, StyleEntry}; -use super::{Builder, Content, Scratch}; +use super::{Builder, Capability, Content, Scratch, StyleChain}; use crate::diag::SourceResult; -use crate::frame::{Element, Frame}; -use crate::geom::{Abs, Align, Axes, Geometry, Length, Paint, Point, Rel, Size, Stroke}; +use crate::frame::Frame; +use crate::geom::{Abs, Axes, Size}; use crate::World; /// Layout content into a collection of pages. @@ -30,7 +26,7 @@ pub fn layout(world: Tracked, content: &Content) -> SourceResult SourceResult>; - /// Convert to a packed node. - fn pack(self) -> LayoutNode - where - Self: Debug + Hash + Sized + Sync + Send + 'static, - { - LayoutNode::new(self) - } + /// 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. @@ -140,226 +140,3 @@ impl Regions { first.chain(backlog.chain(last).map(|&h| Size::new(self.first.x, h))) } } - -/// A type-erased layouting node with a precomputed hash. -#[derive(Clone, Hash)] -pub struct LayoutNode(Arc>); - -impl LayoutNode { - /// Pack any layoutable node. - pub fn new(node: T) -> Self - where - T: Layout + Debug + Hash + Sync + Send + 'static, - { - Self(Arc::new(Prehashed::new(node))) - } - - /// Check whether the contained node is a specific layout node. - pub fn is(&self) -> bool { - (**self.0).as_any().is::() - } - - /// The id of this node. - pub fn id(&self) -> NodeId { - (**self.0).node_id() - } - - /// Try to downcast to a specific layout node. - pub fn downcast(&self) -> Option<&T> - where - T: Layout + Debug + Hash + 'static, - { - (**self.0).as_any().downcast_ref() - } - - /// Force a size for this node. - pub fn sized(self, sizing: Axes>>) -> Self { - if sizing.any(Option::is_some) { - SizedNode { sizing, child: self }.pack() - } else { - self - } - } - - /// Fill the frames resulting from a node. - pub fn filled(self, fill: Paint) -> Self { - FillNode { fill, child: self }.pack() - } - - /// Stroke the frames resulting from a node. - pub fn stroked(self, stroke: Stroke) -> Self { - StrokeNode { stroke, child: self }.pack() - } -} - -impl Layout for LayoutNode { - #[comemo::memoize] - fn layout( - &self, - world: Tracked, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult> { - let barrier = StyleEntry::Barrier(Barrier::new(self.id())); - let styles = barrier.chain(&styles); - self.0.layout(world, regions, styles) - } - - fn pack(self) -> LayoutNode { - self - } -} - -impl Default for LayoutNode { - fn default() -> Self { - EmptyNode.pack() - } -} - -impl Debug for LayoutNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("Layout(")?; - self.0.fmt(f)?; - f.write_char(')') - } -} - -impl PartialEq for LayoutNode { - fn eq(&self, other: &Self) -> bool { - self.0.eq(&other.0) - } -} - -trait Bounds: Layout + Debug + Sync + Send + 'static { - fn as_any(&self) -> &dyn Any; - fn node_id(&self) -> NodeId; -} - -impl Bounds for T -where - T: Layout + Debug + Hash + Sync + Send + 'static, -{ - fn as_any(&self) -> &dyn Any { - self - } - - fn node_id(&self) -> NodeId { - NodeId::of::() - } -} - -/// A layout node that produces an empty frame. -/// -/// The packed version of this is returned by [`PackedNode::default`]. -#[derive(Debug, Hash)] -struct EmptyNode; - -impl Layout for EmptyNode { - fn layout( - &self, - _: Tracked, - regions: &Regions, - _: StyleChain, - ) -> SourceResult> { - Ok(vec![Frame::new( - regions.expand.select(regions.first, Size::zero()), - )]) - } -} - -/// Fix the size of a node. -#[derive(Debug, Hash)] -struct SizedNode { - /// How to size the node horizontally and vertically. - sizing: Axes>>, - /// The node to be sized. - child: LayoutNode, -} - -impl Layout for SizedNode { - fn layout( - &self, - world: Tracked, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult> { - // The "pod" is the region into which the child will be layouted. - let pod = { - // Resolve the sizing to a concrete size. - let size = self - .sizing - .resolve(styles) - .zip(regions.base) - .map(|(s, b)| s.map(|v| v.relative_to(b))) - .unwrap_or(regions.first); - - // Select the appropriate base and expansion for the child depending - // on whether it is automatically or relatively sized. - let is_auto = self.sizing.as_ref().map(Option::is_none); - let base = is_auto.select(regions.base, size); - let expand = regions.expand | !is_auto; - - Regions::one(size, base, expand) - }; - - // Layout the child. - let mut frames = self.child.layout(world, &pod, styles)?; - - // Ensure frame size matches regions size if expansion is on. - let frame = &mut frames[0]; - let target = regions.expand.select(regions.first, frame.size()); - frame.resize(target, Align::LEFT_TOP); - - Ok(frames) - } -} - -/// 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: LayoutNode, -} - -impl Layout for FillNode { - fn layout( - &self, - world: Tracked, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult> { - let mut frames = self.child.layout(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 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: LayoutNode, -} - -impl Layout for StrokeNode { - fn layout( - &self, - world: Tracked, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult> { - let mut frames = self.child.layout(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/model/methods.rs b/src/model/methods.rs index 57fff681b..07cbb8221 100644 --- a/src/model/methods.rs +++ b/src/model/methods.rs @@ -36,7 +36,6 @@ pub fn call( "position" => string .position(args.expect("pattern")?) .map_or(Value::None, Value::Int), - "match" => string .match_(args.expect("pattern")?) .map_or(Value::None, Value::Dict), diff --git a/src/model/mod.rs b/src/model/mod.rs index 52d8c461e..aba9514ce 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -8,7 +8,6 @@ mod eval; mod layout; mod property; mod recipe; -mod show; #[macro_use] mod cast; #[macro_use] @@ -36,7 +35,6 @@ pub use args::*; pub use array::*; pub use capture::*; pub use cast::*; -pub use collapse::*; pub use content::*; pub use dict::*; pub use eval::*; @@ -48,10 +46,10 @@ pub use raw::*; pub use recipe::*; pub use resolve::*; pub use scope::*; -pub use show::*; pub use styles::*; pub use typst_macros::node; pub use value::*; pub use vm::*; +// use collapse::*; use realize::*; diff --git a/src/model/ops.rs b/src/model/ops.rs index c7d6ac783..7a8c6950b 100644 --- a/src/model/ops.rs +++ b/src/model/ops.rs @@ -2,9 +2,10 @@ use std::cmp::Ordering; -use super::{RawAlign, RawStroke, Regex, Smart, Value}; +use super::{Node, RawAlign, RawStroke, Regex, Smart, Value}; use crate::diag::StrResult; use crate::geom::{Axes, Axis, Length, Numeric, Rel}; +use crate::library::text::TextNode; use Value::*; /// Bail with a type mismatch error. @@ -20,8 +21,8 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult { (a, None) => a, (None, b) => b, (Str(a), Str(b)) => Str(a + b), - (Str(a), Content(b)) => Content(super::Content::Text(a.into()) + b), - (Content(a), Str(b)) => Content(a + super::Content::Text(b.into())), + (Str(a), Content(b)) => Content(TextNode(a.into()).pack() + b), + (Content(a), Str(b)) => Content(a + TextNode(b.into()).pack()), (Content(a), Content(b)) => Content(a + b), (Array(a), Array(b)) => Array(a + b), (Dict(a), Dict(b)) => Dict(a + b), @@ -86,8 +87,8 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult { (Str(a), Str(b)) => Str(a + b), (Content(a), Content(b)) => Content(a + b), - (Content(a), Str(b)) => Content(a + super::Content::Text(b.into())), - (Str(a), Content(b)) => Content(super::Content::Text(a.into()) + b), + (Content(a), Str(b)) => Content(a + TextNode(b.into()).pack()), + (Str(a), Content(b)) => Content(TextNode(a.into()).pack() + b), (Array(a), Array(b)) => Array(a + b), (Dict(a), Dict(b)) => Dict(a + b), diff --git a/src/model/property.rs b/src/model/property.rs index acd31b8a6..3a498d2cc 100644 --- a/src/model/property.rs +++ b/src/model/property.rs @@ -17,10 +17,10 @@ pub struct Property { /// The id of the property's [key](Key). key: KeyId, /// The id of the node the property belongs to. - pub node: NodeId, + node: NodeId, /// Whether the property should only affect the first node down the /// hierarchy. Used by constructors. - pub scoped: bool, + scoped: bool, /// The property's value. value: Arc>, /// The name of the property. @@ -60,6 +60,21 @@ impl Property { } } + /// 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::() { @@ -110,24 +125,7 @@ where } } -/// A unique identifier for a property key. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub 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) - } -} - -/// Style property keys. +/// A style property key. /// /// This trait is not intended to be implemented manually, but rather through /// the `#[node]` proc-macro. @@ -153,6 +151,23 @@ pub trait Key<'a>: Copy + 'static { ) -> 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 diff --git a/src/model/realize.rs b/src/model/realize.rs index 69bae91fa..a99abdd35 100644 --- a/src/model/realize.rs +++ b/src/model/realize.rs @@ -3,15 +3,20 @@ use std::mem; use comemo::Tracked; use typed_arena::Arena; +use super::collapse::CollapsingBuilder; use super::{ - Barrier, CollapsingBuilder, Content, Interruption, Layout, ShowNode, StyleChain, - StyleEntry, StyleMap, StyleVecBuilder, Target, + 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::{FlowChild, FlowNode, PageNode, PlaceNode}; +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::{ParChild, ParNode}; +use crate::library::text::{ + LinebreakNode, ParChild, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode, +}; use crate::World; /// Builds a document or a flow node from content. @@ -78,20 +83,19 @@ impl<'a> Builder<'a> { content: &'a Content, styles: StyleChain<'a>, ) -> SourceResult<()> { - match content { - Content::Empty => return Ok(()), - Content::Text(text) => { - if let Some(realized) = styles.apply(self.world, Target::Text(text))? { - let stored = self.scratch.templates.alloc(realized); - return self.accept(stored, styles); - } + 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(()); } - - Content::Show(node) => return self.show(node, styles), - Content::Styled(styled) => return self.styled(styled, styles), - Content::Sequence(seq) => return self.sequence(seq, styles), - - _ => {} } if self.list.accept(content, styles) { @@ -100,7 +104,7 @@ impl<'a> Builder<'a> { self.interrupt(Interruption::List, styles, false)?; - if let Content::Item(_) = content { + if content.is::() { self.list.accept(content, styles); return Ok(()); } @@ -115,7 +119,7 @@ impl<'a> Builder<'a> { return Ok(()); } - let keep = matches!(content, Content::Pagebreak { weak: false }); + let keep = content.downcast::().map_or(false, |node| !node.weak); self.interrupt(Interruption::Page, styles, keep)?; if let Some(doc) = &mut self.doc { @@ -128,7 +132,7 @@ impl<'a> Builder<'a> { Ok(()) } - fn show(&mut self, node: &ShowNode, styles: StyleChain<'a>) -> SourceResult<()> { + 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()); @@ -137,24 +141,26 @@ impl<'a> Builder<'a> { realized = realized.styled_with_map(map); let stored = self.scratch.templates.alloc(realized); self.accept(stored, styles)?; + Ok(true) + } else { + Ok(false) } - Ok(()) } fn styled( &mut self, - (content, map): &'a (Content, StyleMap), + styled: &'a StyledNode, styles: StyleChain<'a>, ) -> SourceResult<()> { let stored = self.scratch.styles.alloc(styles); - let styles = map.chain(stored); - let intr = map.interruption(); + let styles = styled.map.chain(stored); + let intr = styled.map.interruption(); if let Some(intr) = intr { self.interrupt(intr, styles, false)?; } - self.accept(content, styles)?; + self.accept(&styled.sub, styles)?; if let Some(intr) = intr { self.interrupt(intr, styles, true)?; @@ -193,10 +199,10 @@ impl<'a> Builder<'a> { fn sequence( &mut self, - seq: &'a [Content], + seq: &'a SequenceNode, styles: StyleChain<'a>, ) -> SourceResult<()> { - for content in seq { + for content in &seq.0 { self.accept(content, styles)?; } Ok(()) @@ -213,15 +219,13 @@ struct DocBuilder<'a> { impl<'a> DocBuilder<'a> { fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) { - match content { - Content::Pagebreak { weak } => { - self.keep_next = !weak; - } - Content::Page(page) => { - self.pages.push(page.clone(), styles); - self.keep_next = false; - } - _ => {} + 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; } } } @@ -250,36 +254,34 @@ impl<'a> FlowBuilder<'a> { // 4 | generated weak fractional spacing // 5 | par spacing - match content { - Content::Parbreak => {} - Content::Colbreak { weak } => { - if *weak { - self.0.weak(FlowChild::Colbreak, styles, 0); - } else { - self.0.destructive(FlowChild::Colbreak, styles); - } + 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); } - &Content::Vertical { amount, weak, generated } => { - let child = FlowChild::Spacing(amount); - let frac = amount.is_fractional(); - if weak { - let weakness = 1 + u8::from(frac) + 2 * u8::from(generated); - self.0.weak(child, styles, weakness); - } else if frac { - self.0.destructive(child, styles); - } else { - self.0.ignorant(child, 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); } - Content::Block(node) => { - let child = FlowChild::Node(node.clone()); - if node.is::() { - self.0.ignorant(child, styles); - } else { - self.0.supportive(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); } - _ => return false, + } else { + return false; } true @@ -321,36 +323,34 @@ impl<'a> ParBuilder<'a> { // 1 | weak spacing // 2 | space - match content { - Content::Space => { - self.0.weak(ParChild::Text(' '.into()), styles, 2); + 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); } - &Content::Linebreak { justify } => { - let c = if justify { '\u{2028}' } else { '\n' }; - self.0.destructive(ParChild::Text(c.into()), 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; } - &Content::Horizontal { amount, weak } => { - let child = ParChild::Spacing(amount); - let frac = amount.is_fractional(); - if 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); - } - } - &Content::Quote { double } => { - self.0.supportive(ParChild::Quote { double }, styles); - } - Content::Text(text) => { - self.0.supportive(ParChild::Text(text.clone()), styles); - } - Content::Inline(node) => { - self.0.supportive(ParChild::Node(node.clone()), styles); - } - _ => return false, + } else { + return false; } true @@ -412,29 +412,31 @@ struct ListBuilder<'a> { impl<'a> ListBuilder<'a> { fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { if self.items.is_empty() { - match content { - Content::Space => {} - Content::Item(_) => {} - Content::Parbreak => self.attachable = false, - _ => self.attachable = true, + if content.is::() { + self.attachable = false; + } else if !content.is::() && !content.is::() { + self.attachable = true; } } - match content { - Content::Item(item) - if self - .items - .items() - .next() - .map_or(true, |first| item.kind() == first.kind()) => + 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 != Content::Parbreak); + self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::()); + } else { + return false; } - Content::Space | Content::Parbreak if !self.items.is_empty() => { - self.staged.push((content, styles)); - } - _ => return false, + } else if !self.items.is_empty() + && (content.is::() || content.is::()) + { + self.staged.push((content, styles)); + } else { + return false; } true @@ -450,9 +452,9 @@ impl<'a> ListBuilder<'a> { let tight = self.tight; let attached = tight && self.attachable; let content = match kind { - LIST => Content::show(ListNode:: { tight, attached, items }), - ENUM => Content::show(ListNode:: { tight, attached, items }), - DESC | _ => Content::show(ListNode:: { tight, attached, items }), + 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); diff --git a/src/model/recipe.rs b/src/model/recipe.rs index 8124d4416..c687f4fb4 100644 --- a/src/model/recipe.rs +++ b/src/model/recipe.rs @@ -1,12 +1,15 @@ use std::fmt::{self, Debug, Formatter}; +use std::hash::Hash; use comemo::Tracked; use super::{ - Args, Content, Func, Interruption, NodeId, Regex, Show, ShowNode, StyleEntry, Value, + 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; @@ -38,8 +41,8 @@ impl Recipe { ) -> SourceResult> { let content = match (target, &self.pattern) { (Target::Node(node), &Pattern::Node(id)) if node.id() == id => { - let node = node.unguard(sel); - self.call(world, || Value::Content(Content::Show(node)))? + let node = node.to::().unwrap().unguard_parts(sel); + self.call(world, || Value::Content(node))? } (Target::Text(text), Pattern::Regex(regex)) => { @@ -49,7 +52,7 @@ impl Recipe { for mat in regex.find_iter(text) { let start = mat.start(); if cursor < start { - result.push(Content::Text(text[cursor .. start].into())); + result.push(TextNode(text[cursor .. start].into()).pack()); } result.push(self.call(world, || Value::Str(mat.as_str().into()))?); @@ -61,7 +64,7 @@ impl Recipe { } if cursor < text.len() { - result.push(Content::Text(text[cursor ..].into())); + result.push(TextNode(text[cursor ..].into()).pack()); } Content::sequence(result) @@ -132,7 +135,7 @@ impl Pattern { #[derive(Debug, Copy, Clone, PartialEq)] pub enum Target<'a> { /// A showable node. - Node(&'a ShowNode), + Node(&'a Content), /// A slice of text. Text(&'a str), } @@ -145,3 +148,38 @@ pub enum Selector { /// 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/show.rs b/src/model/show.rs deleted file mode 100644 index cc84e6db8..000000000 --- a/src/model/show.rs +++ /dev/null @@ -1,127 +0,0 @@ -use std::fmt::{self, Debug, Formatter, Write}; -use std::hash::Hash; -use std::sync::Arc; - -use comemo::{Prehashed, Tracked}; - -use super::{Content, NodeId, Selector, StyleChain, Value}; -use crate::diag::SourceResult; -use crate::World; - -/// A node that can be realized given some styles. -pub trait Show: 'static { - /// Unguard nested content against recursive show rules. - fn unguard(&self, sel: Selector) -> ShowNode; - - /// 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) - } - - /// Convert to a packed show node. - fn pack(self) -> ShowNode - where - Self: Debug + Hash + Sized + Sync + Send + 'static, - { - ShowNode::new(self) - } -} - -/// A type-erased showable node with a precomputed hash. -#[derive(Clone, Hash)] -pub struct ShowNode(Arc>); - -impl ShowNode { - /// Pack any showable node. - pub fn new(node: T) -> Self - where - T: Show + Debug + Hash + Sync + Send + 'static, - { - Self(Arc::new(Prehashed::new(node))) - } - - /// The id of this node. - pub fn id(&self) -> NodeId { - (**self.0).node_id() - } -} - -impl Show for ShowNode { - fn unguard(&self, sel: Selector) -> ShowNode { - self.0.unguard(sel) - } - - fn field(&self, name: &str) -> Option { - self.0.field(name) - } - - fn realize( - &self, - world: Tracked, - styles: StyleChain, - ) -> SourceResult { - self.0.realize(world, styles) - } - - fn finalize( - &self, - world: Tracked, - styles: StyleChain, - realized: Content, - ) -> SourceResult { - self.0.finalize(world, styles, realized) - } - - fn pack(self) -> ShowNode { - self - } -} - -impl Debug for ShowNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("Show(")?; - self.0.fmt(f)?; - f.write_char(')') - } -} - -impl PartialEq for ShowNode { - fn eq(&self, other: &Self) -> bool { - self.0.eq(&other.0) - } -} - -trait Bounds: Show + Debug + Sync + Send + 'static { - fn node_id(&self) -> NodeId; -} - -impl Bounds for T -where - T: Show + Debug + Hash + Sync + Send + 'static, -{ - fn node_id(&self) -> NodeId { - NodeId::of::() - } -} diff --git a/src/model/styles.rs b/src/model/styles.rs index f3bd8c4ff..d160a4a6c 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -7,7 +7,6 @@ use comemo::Tracked; use super::{Barrier, Content, Key, Property, Recipe, Selector, Show, Target}; use crate::diag::SourceResult; -use crate::util::ReadableTypeId; use crate::World; /// A map of style properties. @@ -98,7 +97,7 @@ impl StyleMap { pub fn scoped(mut self) -> Self { for entry in &mut self.0 { if let StyleEntry::Property(property) = entry { - property.scoped = true; + property.make_scoped(); } } self @@ -125,23 +124,6 @@ impl Debug for StyleMap { } } -/// A unique identifier for a node. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct NodeId(ReadableTypeId); - -impl NodeId { - /// The id of the given node. - pub fn of() -> Self { - Self(ReadableTypeId::of::()) - } -} - -impl Debug for NodeId { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - /// Determines whether a style could interrupt some composable structure. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] pub enum Interruption { @@ -175,7 +157,7 @@ impl StyleEntry { if !tail .entries() .filter_map(StyleEntry::property) - .any(|p| p.scoped && barrier.is_for(p.node)) + .any(|p| p.scoped() && barrier.is_for(p.node())) { return *tail; } @@ -302,7 +284,13 @@ impl<'a> StyleChain<'a> { if self.guarded(sel) { guarded = true; } else { - let content = node.unguard(sel).realize(world, self)?; + let content = node + .to::() + .unwrap() + .unguard_parts(sel) + .to::() + .unwrap() + .realize(world, self)?; realized = Some(content.styled_with_entry(StyleEntry::Guard(sel))); } } @@ -310,7 +298,9 @@ impl<'a> StyleChain<'a> { // Finalize only if guarding didn't stop any recipe. if !guarded { if let Some(content) = realized { - realized = Some(node.finalize(world, self, content)?); + realized = Some( + node.to::().unwrap().finalize(world, self, content)?, + ); } } } @@ -403,7 +393,7 @@ impl<'a, K: Key<'a>> Iterator for Values<'a, K> { match entry { StyleEntry::Property(property) => { if let Some(value) = property.downcast::() { - if !property.scoped || self.depth <= 1 { + if !property.scoped() || self.depth <= 1 { return Some(value); } } diff --git a/src/model/value.rs b/src/model/value.rs index 1782e4a63..4741401c5 100644 --- a/src/model/value.rs +++ b/src/model/value.rs @@ -7,9 +7,10 @@ use std::sync::Arc; use comemo::Tracked; use siphasher::sip128::{Hasher128, SipHasher}; -use super::{ops, Args, Array, Cast, Content, Dict, Func, Layout, Str}; +use super::{ops, Args, Array, Cast, Content, Dict, Func, Node, Str}; use crate::diag::StrResult; use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor}; +use crate::library::text::TextNode; use crate::util::EcoString; use crate::World; @@ -55,22 +56,6 @@ pub enum Value { } impl Value { - /// Create a content value from an inline-level node. - pub fn inline(node: T) -> Self - where - T: Layout + Debug + Hash + Sync + Send + 'static, - { - Self::Content(Content::inline(node)) - } - - /// Create a content value from a block-level node. - pub fn block(node: T) -> Self - where - T: Layout + Debug + Hash + Sync + Send + 'static, - { - Self::Content(Content::block(node)) - } - /// Create a new dynamic value. pub fn dynamic(any: T) -> Self where @@ -115,16 +100,17 @@ impl Value { /// Return the display representation of the value. pub fn display(self, world: Tracked) -> Content { + let items = &world.config().items; match self { - Value::None => Content::new(), - Value::Int(v) => Content::Text(format_eco!("{}", v)), - Value::Float(v) => Content::Text(format_eco!("{}", v)), - Value::Str(v) => Content::Text(v.into()), + Value::None => Content::empty(), + Value::Int(v) => (items.text)(format_eco!("{}", v)), + Value::Float(v) => (items.text)(format_eco!("{}", v)), + Value::Str(v) => (items.text)(v.into()), Value::Content(v) => v, // For values which can't be shown "naturally", we return the raw // representation with typst code syntax highlighting. - v => (world.config().items.raw)(v.repr().into(), Some("typc".into()), false), + v => (items.raw)(v.repr().into(), Some("typc".into()), false), } } } @@ -398,8 +384,8 @@ primitive! { Color: "color", Color } primitive! { Str: "string", Str } primitive! { Content: "content", Content, - None => Content::new(), - Str(text) => Content::Text(text.into()) + None => Content::empty(), + Str(text) => TextNode(text.into()).pack() } primitive! { Array: "array", Array } primitive! { Dict: "dictionary", Dict } @@ -448,7 +434,6 @@ mod tests { test(dict!["two" => false, "one" => 1], "(one: 1, two: false)"); // Functions, content and dynamics. - test(Content::Text("a".into()), "[...]"); test(Func::from_fn("nil", |_, _| Ok(Value::None)), "nil"); test(Dynamic::new(1), "1"); } diff --git a/src/model/vm.rs b/src/model/vm.rs index 6207ffd5e..4de57d1c8 100644 --- a/src/model/vm.rs +++ b/src/model/vm.rs @@ -2,10 +2,10 @@ use std::path::PathBuf; use comemo::Tracked; -use super::{Route, Scopes, Value}; +use super::{Content, Route, Scopes, Value}; use crate::diag::{SourceError, StrResult}; use crate::syntax::{SourceId, Span}; -use crate::util::PathExt; +use crate::util::{EcoString, PathExt}; use crate::{LangItems, World}; /// A virtual machine. @@ -59,6 +59,13 @@ impl<'a> Vm<'a> { pub fn items(&self) -> &LangItems { &self.world.config().items } + + /// Create text content. + /// + /// This is a shorthand for `(vm.items().text)(..)`. + pub fn text(&self, text: impl Into) -> Content { + (self.items().text)(text.into()) + } } /// A control flow event that occurred during evaluation. diff --git a/src/util/fat.rs b/src/util/fat.rs new file mode 100644 index 000000000..bb557bc91 --- /dev/null +++ b/src/util/fat.rs @@ -0,0 +1,64 @@ +//! Fat pointer handling. +//! +//! This assumes the memory representation of fat pointers. Although it is not +//! guaranteed by Rust, it's improbable that it will change. Still, when the +//! pointer metadata APIs are stable, we should definitely move to them: +//! + +use std::alloc; +use std::mem; + +/// Create a fat pointer from a data address and a vtable address. +/// +/// # Safety +/// Must only be called when `T` is a `dyn Trait`. The data address must point +/// to a value whose type implements the trait of `T` and the `vtable` must have +/// been extracted with [`vtable`]. +pub unsafe fn from_raw_parts(data: *const (), vtable: *const ()) -> *const T { + debug_assert_eq!( + alloc::Layout::new::<*const T>(), + alloc::Layout::new::(), + ); + + let fat = FatPointer { data, vtable }; + mem::transmute_copy::(&fat) +} + +/// Create a mutable fat pointer from a data address and a vtable address. +/// +/// # Safety +/// Must only be called when `T` is a `dyn Trait`. The data address must point +/// to a value whose type implements the trait of `T` and the `vtable` must have +/// been extracted with [`vtable`]. +pub unsafe fn from_raw_parts_mut(data: *mut (), vtable: *const ()) -> *mut T { + debug_assert_eq!( + alloc::Layout::new::<*mut T>(), + alloc::Layout::new::(), + ); + + let fat = FatPointer { data, vtable }; + mem::transmute_copy::(&fat) +} + +/// Extract the address to a trait object's vtable. +/// +/// # Safety +/// Must only be called when `T` is a `dyn Trait`. +pub unsafe fn vtable(ptr: *const T) -> *const () { + debug_assert_eq!( + alloc::Layout::new::<*const T>(), + alloc::Layout::new::(), + ); + + mem::transmute_copy::<*const T, FatPointer>(&ptr).vtable +} + +/// The memory representation of a trait object pointer. +/// +/// Although this is not guaranteed by Rust, it's improbable that it will +/// change. +#[repr(C)] +struct FatPointer { + data: *const (), + vtable: *const (), +} diff --git a/src/util/mod.rs b/src/util/mod.rs index d549fc5f2..bc7aa250c 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,12 +1,14 @@ //! Utilities. +pub mod fat; + +pub use buffer::Buffer; +pub use eco::EcoString; + #[macro_use] mod eco; mod buffer; -pub use buffer::Buffer; -pub use eco::EcoString; - use std::any::TypeId; use std::fmt::{self, Debug, Formatter}; use std::path::{Component, Path, PathBuf};