summaryrefslogtreecommitdiff
path: root/src/library
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-11-01 16:56:35 +0100
committerLaurenz <laurmaedje@gmail.com>2022-11-02 09:18:33 +0100
commit37ac5d966ebaf97ac79c507028cd5b742b510b89 (patch)
tree249d43ff0f8d880cb5d00c236993f8ff0c1f10d8 /src/library
parentf547c97072881069417acd3b79b08fb7ecf40ba2 (diff)
More dynamic content representation
Diffstat (limited to 'src/library')
-rw-r--r--src/library/graphics/hide.rs12
-rw-r--r--src/library/graphics/image.rs10
-rw-r--r--src/library/graphics/line.rs8
-rw-r--r--src/library/graphics/shape.rs16
-rw-r--r--src/library/layout/align.rs26
-rw-r--r--src/library/layout/columns.rs24
-rw-r--r--src/library/layout/container.rs78
-rw-r--r--src/library/layout/flow.rs15
-rw-r--r--src/library/layout/grid.rs28
-rw-r--r--src/library/layout/pad.rs14
-rw-r--r--src/library/layout/page.rs17
-rw-r--r--src/library/layout/place.rs16
-rw-r--r--src/library/layout/spacing.rs17
-rw-r--r--src/library/layout/stack.rs26
-rw-r--r--src/library/layout/transform.rs30
-rw-r--r--src/library/math/mod.rs54
-rw-r--r--src/library/mod.rs197
-rw-r--r--src/library/prelude.rs9
-rw-r--r--src/library/structure/heading.rs13
-rw-r--r--src/library/structure/list.rs48
-rw-r--r--src/library/structure/reference.rs8
-rw-r--r--src/library/structure/table.rs16
-rw-r--r--src/library/text/deco.rs6
-rw-r--r--src/library/text/link.rs22
-rw-r--r--src/library/text/mod.rs101
-rw-r--r--src/library/text/par.rs60
-rw-r--r--src/library/text/raw.rs21
-rw-r--r--src/library/text/repeat.rs24
-rw-r--r--src/library/text/shift.rs47
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, &regions, styles)?;
+ let mut frames = child.layout_block(world, &regions, 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
}
}