diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-11-01 16:56:35 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-11-02 09:18:33 +0100 |
| commit | 37ac5d966ebaf97ac79c507028cd5b742b510b89 (patch) | |
| tree | 249d43ff0f8d880cb5d00c236993f8ff0c1f10d8 /src/library | |
| parent | f547c97072881069417acd3b79b08fb7ecf40ba2 (diff) | |
More dynamic content representation
Diffstat (limited to 'src/library')
29 files changed, 623 insertions, 340 deletions
diff --git a/src/library/graphics/hide.rs b/src/library/graphics/hide.rs index 65684272..fafd7421 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<Content> { - 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<Vec<Frame>> { - 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 343b4788..9c3a775a 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 78878014..c2f89404 100644 --- a/src/library/graphics/line.rs +++ b/src/library/graphics/line.rs @@ -9,7 +9,7 @@ pub struct LineNode { delta: Axes<Rel<Length>>, } -#[node] +#[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 7a742109..608c9842 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<const S: ShapeKind>(pub Option<LayoutNode>); +pub struct ShapeNode<const S: ShapeKind>(pub Option<Content>); /// Place a node into a square. pub type SquareNode = ShapeNode<SQUARE>; @@ -19,7 +19,7 @@ pub type CircleNode = ShapeNode<CIRCLE>; /// Place a node into an ellipse. pub type EllipseNode = ShapeNode<ELLIPSE>; -#[node] +#[node(Layout)] impl<const S: ShapeKind> ShapeNode<S> { /// How to fill the shape. pub const FILL: Option<Paint> = None; @@ -55,9 +55,7 @@ impl<const S: ShapeKind> ShapeNode<S> { 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<const S: ShapeKind> Layout for ShapeNode<S> { 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<const S: ShapeKind> Layout for ShapeNode<S> { 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<const S: ShapeKind> Layout for ShapeNode<S> { Ok(frames) } + + fn level(&self) -> Level { + Level::Inline + } } /// A category of shape. diff --git a/src/library/layout/align.rs b/src/library/layout/align.rs index 95f5c01f..f49763b5 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<Option<RawAlign>>, /// The node to be aligned. - pub child: LayoutNode, + pub child: Content, } -#[node] +#[node(Layout)] impl AlignNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { let aligns: Axes<Option<RawAlign>> = args.find()?.unwrap_or_default(); let body: Content = args.expect("body")?; - 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::<dyn Layout>() + .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 3ba3598c..79d98e11 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<Length> = Ratio::new(0.04).into(); fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - 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<Content> { 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 9b1f8f56..60f9139b 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<Option<Rel<Length>>>, + /// The node to be sized. + pub child: Content, +} -#[node] +#[node(Layout)] impl BoxNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { 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::<Content>()?.unwrap_or_default(); + Ok(body.boxed(Axes::new(width, height))) + } +} + +impl Layout for BoxNode { + fn layout( + &self, + world: Tracked<dyn World>, + regions: &Regions, + styles: StyleChain, + ) -> SourceResult<Vec<Frame>> { + // 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<Content> { - 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<dyn World>, + regions: &Regions, + styles: StyleChain, + ) -> SourceResult<Vec<Frame>> { + self.0.layout_block(world, regions, styles) + } + + fn level(&self) -> Level { + Level::Block } } diff --git a/src/library/layout/flow.rs b/src/library/layout/flow.rs index 1f0a2b4a..01ee9dc9 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<dyn World>, - 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::<PlaceNode>() { 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 a1098c6d..7e5cbbd5 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<Vec<TrackSizing>>, /// The nodes to be arranged in a grid. - pub cells: Vec<LayoutNode>, + pub cells: Vec<Content>, } -#[node] +#[node(Layout)] impl GridNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { let columns = args.named("columns")?.unwrap_or_default(); @@ -19,14 +19,15 @@ impl GridNode { let base_gutter: Vec<TrackSizing> = 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<TrackSizing>, /// 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 b0238d40..effdd5f8 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<Rel<Length>>, /// 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<Content> { 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::<Content>("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 2e5cf2f9..f5821ae6 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<Content> { - 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<Content> { let weak = args.named("weak")?.unwrap_or(false); - Ok(Content::Pagebreak { weak }) + Ok(Self { weak }.pack()) } } @@ -201,7 +204,7 @@ impl Cast<Spanned<Value>> for Marginal { fn cast(value: Spanned<Value>) -> StrResult<Self> { 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 8b68c087..42ab0fba 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<Content> { 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::<Content>("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 28e52d73..6df5cde5 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<Content> { 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<Content> { 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 b9663dd6..9ea7965d 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<StackChild>, } -#[node] +#[node(Layout)] impl StackNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - 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<dyn World>, - 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::<Content>() { - let map = &styled.1; + if let Some(styled) = node.downcast::<StyledNode>() { + 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 ff42744a..061efa6b 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<Rel<Length>>, /// 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<Content> { 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<Vec<Frame>> { - 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<const T: TransformKind> { /// 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<ROTATE>; /// Scale a node without affecting layout. pub type ScaleNode = TransformNode<SCALE>; -#[node] +#[node(Layout)] impl<const T: TransformKind> TransformNode<T> { /// The origin of the transformation. #[property(resolve)] @@ -76,10 +81,7 @@ impl<const T: TransformKind> TransformNode<T> { } }; - Ok(Content::inline(Self { - transform, - child: args.expect("body")?, - })) + Ok(Self { transform, child: args.expect("body")? }.pack()) } } @@ -91,7 +93,7 @@ impl<const T: TransformKind> Layout for TransformNode<T> { styles: StyleChain, ) -> SourceResult<Vec<Frame>> { 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<const T: TransformKind> Layout for TransformNode<T> { Ok(frames) } + + fn level(&self) -> Level { + Level::Inline + } } /// Kinds of transformations. diff --git a/src/library/math/mod.rs b/src/library/math/mod.rs index 63d5f419..84d4b6ee 100644 --- a/src/library/math/mod.rs +++ b/src/library/math/mod.rs @@ -36,7 +36,7 @@ pub enum MathNode { Row(Arc<Vec<MathNode>>, Span), } -#[node(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<Value> { @@ -89,13 +79,11 @@ impl Show for MathNode { } fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> { - 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<Content> { - 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<Vec<Frame>> { - 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 17a7a681..d75e2459 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::<text::SpaceNode>("space"); + std.def_node::<text::LinebreakNode>("linebreak"); + std.def_node::<text::SmartQuoteNode>("smartquote"); std.def_node::<text::TextNode>("text"); std.def_node::<text::ParNode>("par"); - std.def_node::<text::LinebreakNode>("linebreak"); std.def_node::<text::ParbreakNode>("parbreak"); std.def_node::<text::StrongNode>("strong"); std.def_node::<text::EmphNode>("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<Abs>, below: Option<Abs>) -> Self; + + /// Force a size for this node. + fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self; + + /// Set alignments for this node. + fn aligned(self, aligns: Axes<Option<RawAlign>>) -> Self; + + /// Pad this node at the sides. + fn padded(self, padding: Sides<Rel<Length>>) -> Self; + + /// Transform this node's contents without affecting layout. + fn moved(self, delta: Axes<Rel<Length>>) -> Self; + + /// Fill the frames resulting from a node. + fn filled(self, fill: Paint) -> Self; + + /// Stroke the frames resulting from a node. + fn stroked(self, stroke: Stroke) -> Self; } impl ContentExt for Content { fn strong(self) -> Self { - 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<Abs>, below: Option<Abs>) -> Self { + if above.is_none() && below.is_none() { + return self; + } + + let mut seq = vec![]; + if let Some(above) = above { + seq.push( + layout::VNode { + amount: above.into(), + weak: true, + generated: true, + } + .pack(), + ); + } + + seq.push(self); + if let Some(below) = below { + seq.push( + layout::VNode { + amount: below.into(), + weak: true, + generated: true, + } + .pack(), + ); + } + + Self::sequence(seq) + } + + fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self { + layout::BoxNode { sizing, child: self }.pack() + } + + fn aligned(self, aligns: Axes<Option<RawAlign>>) -> Self { + layout::AlignNode { aligns, child: self }.pack() + } + + fn padded(self, padding: Sides<Rel<Length>>) -> Self { + layout::PadNode { padding, child: self }.pack() + } + + fn moved(self, delta: Axes<Rel<Length>>) -> Self { + layout::MoveNode { delta, child: self }.pack() + } + + fn filled(self, fill: Paint) -> Self { + FillNode { fill, child: self }.pack() + } + + fn stroked(self, stroke: Stroke) -> Self { + StrokeNode { stroke, child: self }.pack() } } @@ -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<Option<RawAlign>>) -> Self; - - /// Pad this node at the sides. - fn padded(self, padding: Sides<Rel<Length>>) -> Self; - - /// Transform this node's contents without affecting layout. - fn moved(self, delta: Axes<Rel<Length>>) -> Self; +/// Fill the frames resulting from a node. +#[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<Option<RawAlign>>) -> 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<dyn World>, + regions: &Regions, + styles: StyleChain, + ) -> SourceResult<Vec<Frame>> { + let mut frames = self.child.layout_block(world, regions, styles)?; + for frame in &mut frames { + let shape = Geometry::Rect(frame.size()).filled(self.fill); + frame.prepend(Point::zero(), Element::Shape(shape)); } + Ok(frames) } - fn padded(self, padding: Sides<Rel<Length>>) -> 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 level(&self) -> Level { + Level::Block } +} - fn moved(self, delta: Axes<Rel<Length>>) -> Self { - if delta.any(|r| !r.is_zero()) { - layout::MoveNode { delta, child: self }.pack() - } else { - self +/// Stroke the frames resulting from a node. +#[derive(Debug, Hash)] +struct StrokeNode { + /// How to stroke the frames resulting from the `child`. + stroke: Stroke, + /// The node whose frames should be stroked. + child: Content, +} + +#[node(Layout)] +impl StrokeNode {} + +impl Layout for StrokeNode { + fn layout( + &self, + world: Tracked<dyn World>, + regions: &Regions, + styles: StyleChain, + ) -> SourceResult<Vec<Frame>> { + let mut frames = self.child.layout_block(world, regions, styles)?; + for frame in &mut frames { + let shape = Geometry::Rect(frame.size()).stroked(self.stroke); + frame.prepend(Point::zero(), Element::Shape(shape)); } + Ok(frames) + } + + fn level(&self) -> Level { + Level::Block } } diff --git a/src/library/prelude.rs b/src/library/prelude.rs index dfc74ec8..756904f0 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 fa96248f..5b056c30 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<Content> { - 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<dyn World>, _: StyleChain) -> SourceResult<Content> { - 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 8a57069e..f061c5f8 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<ENUM>; /// A description list. pub type DescNode = ListNode<DESC>; -#[node(showable)] +#[node(Show)] impl<const L: ListKind> ListNode<L> { /// How the list is labelled. #[property(referenced)] @@ -73,16 +73,17 @@ impl<const L: ListKind> ListNode<L> { .collect(), }; - Ok(Content::show(Self { + Ok(Self { tight: args.named("tight")?.unwrap_or(true), attached: args.named("attached")?.unwrap_or(false), items, - })) + } + .pack()) } } impl<const L: ListKind> Show for ListNode<L> { - 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<const L: ListKind> Show for ListNode<L> { 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<const L: ListKind> Show for ListNode<L> { ]), 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<Content> { 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<Spanned<Value>> for Label { fn cast(value: Spanned<Value>) -> StrResult<Self> { 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 425ee518..b4e8b047 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<Content> { - 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<dyn World>, _: StyleChain) -> SourceResult<Content> { - 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 41dcd104..d5e8920e 100644 --- a/src/library/structure/table.rs +++ b/src/library/structure/table.rs @@ -12,7 +12,7 @@ pub struct TableNode { pub cells: Vec<Content>, } -#[node(showable)] +#[node(Show)] impl TableNode { /// How to fill the cells. #[property(referenced)] @@ -36,19 +36,20 @@ impl TableNode { let base_gutter: Vec<TrackSizing> = 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::<SourceResult<_>>()?; - 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 ead928f6..158647f2 100644 --- a/src/library/text/deco.rs +++ b/src/library/text/deco.rs @@ -17,7 +17,7 @@ pub type StrikethroughNode = DecoNode<STRIKETHROUGH>; /// Typeset overlined text. pub type OverlineNode = DecoNode<OVERLINE>; -#[node(showable)] +#[node(Show)] impl<const L: DecoLine> DecoNode<L> { /// How to stroke the line. The text color and thickness are read from the /// font tables if `auto`. @@ -35,12 +35,12 @@ impl<const L: DecoLine> DecoNode<L> { pub const EVADE: bool = true; fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Content::show(Self(args.expect("body")?))) + Ok(Self(args.expect("body")?).pack()) } } impl<const L: DecoLine> Show for DecoNode<L> { - 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 11d55956..1e9adc3e 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<bool> = Smart::Auto; fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Content::show({ - let dest = args.expect::<Destination>("destination")?; - let body = match dest { - Destination::Url(_) => args.eat()?, - Destination::Internal(_) => Some(args.expect("body")?), - }; - Self { dest, body } - })) + let dest = args.expect::<Destination>("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 d7d2ee38..18e747d0 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 <Content as Cast<Spanned<Value>>>::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<Content> { + 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<Content> { + 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<Content> { + 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<Value> { case(Case::Lower, args) @@ -478,40 +515,19 @@ pub fn smallcaps(_: &mut Vm, args: &mut Args) -> SourceResult<Value> { Ok(Value::Content(body.styled(TextNode::SMALLCAPS, true))) } -/// A toggle that turns on and off alternatingly if folded. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Toggle; - -impl Fold for Toggle { - type Output = bool; - - fn fold(self, outer: Self::Output) -> Self::Output { - !outer - } -} - -impl Fold for Decoration { - type Output = Vec<Self>; - - fn fold(self, mut outer: Self::Output) -> Self::Output { - outer.insert(0, self); - outer - } -} - /// Strong text, rendered in boldface by default. #[derive(Debug, Hash)] pub struct StrongNode(pub Content); -#[node(showable)] +#[node(Show)] impl StrongNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Content::show(Self(args.expect("body")?))) + Ok(Self(args.expect("body")?).pack()) } } impl Show for StrongNode { - fn unguard(&self, sel: Selector) -> ShowNode { + fn unguard_parts(&self, sel: Selector) -> Content { Self(self.0.unguard(sel)).pack() } @@ -531,15 +547,15 @@ impl Show for StrongNode { #[derive(Debug, Hash)] pub struct EmphNode(pub Content); -#[node(showable)] +#[node(Show)] impl EmphNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Content::show(Self(args.expect("body")?))) + Ok(Self(args.expect("body")?).pack()) } } impl Show for EmphNode { - fn unguard(&self, sel: Selector) -> ShowNode { + fn unguard_parts(&self, sel: Selector) -> Content { Self(self.0.unguard(sel)).pack() } @@ -554,3 +570,24 @@ impl Show for EmphNode { 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; + +impl Fold for Toggle { + type Output = bool; + + fn fold(self, outer: Self::Output) -> Self::Output { + !outer + } +} + +impl Fold for Decoration { + type Output = Vec<Self>; + + fn fold(self, mut outer: Self::Output) -> Self::Output { + outer.insert(0, self); + outer + } +} diff --git a/src/library/text/par.rs b/src/library/text/par.rs index 477bf97f..7c862366 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<Linebreaks> { } /// A paragraph break. +#[derive(Debug, Clone, Hash)] pub struct ParbreakNode; #[node] impl ParbreakNode { fn construct(_: &mut Vm, _: &mut Args) -> SourceResult<Content> { - 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<Content> { - 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<dyn World>, + regions: &Regions, + styles: StyleChain, + ) -> SourceResult<Vec<Frame>> { + 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<Preparation<'a>> { 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::<RepeatNode>() { items.push(Item::Repeat(repeat, styles)); } else { let size = Size::new(regions.first.x, regions.base.y); let pod = Regions::one(size, regions.base, Axes::splat(false)); - let mut frame = node.layout(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 85e8133c..0c769636 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<BlockSpacing> = Some(Ratio::one().into()); fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - 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 e3bae3fc..00000000 --- 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<Content> { - Ok(Content::inline(Self(args.expect("body")?))) - } -} - -impl Layout for RepeatNode { - fn layout( - &self, - world: Tracked<dyn World>, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult<Vec<Frame>> { - // 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 e2a636a6..c3cf8b03 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<SUPERSCRIPT>; /// Shift the text into subscript. pub type SubNode = ShiftNode<SUBSCRIPT>; -#[node] +#[node(Show)] impl<const S: ScriptKind> ShiftNode<S> { /// Whether to prefer the dedicated sub- and superscript characters of the /// font. @@ -29,12 +30,12 @@ impl<const S: ScriptKind> ShiftNode<S> { pub const SIZE: TextSize = TextSize(Em::new(0.6).into()); fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Content::show(Self(args.expect("body")?))) + Ok(Self(args.expect("body")?).pack()) } } impl<const S: ScriptKind> Show for ShiftNode<S> { - fn unguard(&self, _: Selector) -> ShowNode { + fn unguard_parts(&self, _: Selector) -> Content { Self(self.0.clone()).pack() } @@ -54,7 +55,7 @@ impl<const S: ScriptKind> Show for ShiftNode<S> { 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<const S: ScriptKind> Show for ShiftNode<S> { /// 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<EcoString> { - 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::<SpaceNode>() { + Some(' '.into()) + } else if let Some(text) = content.downcast::<TextNode>() { + 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::<SequenceNode>() { + 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 } } |
