diff --git a/src/eval/class.rs b/src/eval/class.rs index 5682cb4d3..7888cba28 100644 --- a/src/eval/class.rs +++ b/src/eval/class.rs @@ -1,6 +1,4 @@ use std::fmt::{self, Debug, Formatter, Write}; -use std::marker::PhantomData; -use std::rc::Rc; use super::{Args, EvalContext, Node, StyleMap}; use crate::diag::TypResult; @@ -36,12 +34,10 @@ use crate::util::EcoString; /// [`TextNode`]: crate::library::TextNode /// [`set`]: Self::set #[derive(Clone)] -pub struct Class(Rc>); - -/// The unsized structure behind the [`Rc`]. -struct Inner { +pub struct Class { name: EcoString, - shim: T, + construct: fn(&mut EvalContext, &mut Args) -> TypResult, + set: fn(&mut Args, &mut StyleMap) -> TypResult<()>, } impl Class { @@ -50,15 +46,16 @@ impl Class { where T: Construct + Set + 'static, { - // By specializing the shim to `T`, its vtable will contain T's - // `Construct` and `Set` impls (through the `Bounds` trait), enabling us - // to use them in the class's methods. - Self(Rc::new(Inner { name, shim: Shim::(PhantomData) })) + Self { + name, + construct: T::construct, + set: T::set, + } } /// The name of the class. pub fn name(&self) -> &EcoString { - &self.0.name + &self.name } /// Construct an instance of the class. @@ -68,7 +65,7 @@ impl Class { pub fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult { let mut styles = StyleMap::new(); self.set(args, &mut styles)?; - let node = self.0.shim.construct(ctx, args)?; + let node = (self.construct)(ctx, args)?; Ok(node.styled(styles)) } @@ -77,7 +74,7 @@ impl Class { /// This parses property arguments and writes the resulting styles into the /// given style map. There are no further side effects. pub fn set(&self, args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { - self.0.shim.set(args, styles) + (self.set)(args, styles) } } @@ -91,12 +88,7 @@ impl Debug for Class { impl PartialEq for Class { fn eq(&self, other: &Self) -> bool { - // We cast to thin pointers for comparison because we don't want to - // compare vtables (there can be duplicate vtables across codegen units). - std::ptr::eq( - Rc::as_ptr(&self.0) as *const (), - Rc::as_ptr(&other.0) as *const (), - ) + self.name == other.name } } @@ -115,25 +107,3 @@ pub trait Set { /// given style map. fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()>; } - -/// Rewires the operations available on a class in an object-safe way. This is -/// only implemented by the zero-sized `Shim` struct. -trait Bounds { - fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult; - fn set(&self, args: &mut Args, styles: &mut StyleMap) -> TypResult<()>; -} - -struct Shim(PhantomData); - -impl Bounds for Shim -where - T: Construct + Set, -{ - fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult { - T::construct(ctx, args) - } - - fn set(&self, args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { - T::set(args, styles) - } -} diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 062187524..e8c8fcd29 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -171,7 +171,7 @@ impl Eval for Markup { let upper = nodes.size_hint().1.unwrap_or_default(); let mut seq = Vec::with_capacity(upper); for piece in nodes { - seq.push((piece.eval(ctx)?, ctx.styles.clone())); + seq.push(Styled::new(piece.eval(ctx)?, ctx.styles.clone())); } ctx.styles = prev; Ok(Node::Sequence(seq)) diff --git a/src/eval/node.rs b/src/eval/node.rs index 54d4104df..2c955d012 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -5,13 +5,12 @@ use std::iter::Sum; use std::mem; use std::ops::{Add, AddAssign}; -use super::StyleMap; +use super::{StyleMap, Styled}; use crate::diag::StrResult; use crate::geom::SpecAxis; use crate::layout::{Layout, PackedNode, RootNode}; use crate::library::{ - FlowChild, FlowNode, PageNode, ParChild, ParNode, PlacedNode, SpacingKind, - SpacingNode, TextNode, + FlowChild, FlowNode, PageNode, ParChild, ParNode, PlacedNode, SpacingKind, TextNode, }; use crate::util::EcoString; @@ -50,20 +49,17 @@ pub enum Node { /// /// For example, the Typst template `[Hi *you!*]` would result in the /// sequence: - /// ```ignore - /// Sequence([ - /// (Text("Hi"), {}), - /// (Space, {}), - /// (Text("you!"), { TextNode::STRONG: true }), - /// ]) - /// ``` + /// - `Text("Hi")` with empty style map, + /// - `Space` with empty style map, + /// - `Text("you!")` with `TextNode::STRONG` set to `true`. + /// /// A sequence may contain nested sequences (meaning this variant /// effectively allows nodes to form trees). All nested sequences can /// equivalently be represented as a single flat sequence, but allowing /// nesting doesn't hurt since we can just recurse into the nested sequences /// during packing. Also, in theory, this allows better complexity when /// adding (large) sequence nodes (just like for a text rope). - Sequence(Vec<(Self, StyleMap)>), + Sequence(Vec>), } impl Node { @@ -90,12 +86,7 @@ impl Node { /// Style this node. pub fn styled(self, styles: StyleMap) -> Self { - match self { - Self::Inline(inline) => Self::Inline(inline.styled(styles)), - Self::Block(block) => Self::Block(block.styled(styles)), - Self::Page(page) => Self::Page(page.styled(styles)), - other => Self::Sequence(vec![(other, styles)]), - } + Self::Sequence(vec![Styled::new(self, styles)]) } /// Style this node in monospace. @@ -127,7 +118,7 @@ impl Node { .map_err(|_| format!("cannot repeat this template {} times", n))?; // TODO(style): Make more efficient. - Ok(Self::Sequence(vec![(self.clone(), StyleMap::new()); count])) + Ok(Self::Sequence(vec![Styled::bare(self.clone()); count])) } } @@ -142,7 +133,7 @@ impl Add for Node { fn add(self, rhs: Self) -> Self::Output { // TODO(style): Make more efficient. - Self::Sequence(vec![(self, StyleMap::new()), (rhs, StyleMap::new())]) + Self::Sequence(vec![Styled::bare(self), Styled::bare(rhs)]) } } @@ -154,7 +145,7 @@ impl AddAssign for Node { impl Sum for Node { fn sum>(iter: I) -> Self { - Self::Sequence(iter.map(|n| (n, StyleMap::new())).collect()) + Self::Sequence(iter.map(|n| Styled::bare(n)).collect()) } } @@ -163,11 +154,11 @@ struct Packer { /// Whether this packer produces a root node. top: bool, /// The accumulated page nodes. - pages: Vec, + pages: Vec>, /// The accumulated flow children. - flow: Builder, + flow: Builder>, /// The accumulated paragraph children. - par: Builder, + par: Builder>, } impl Packer { @@ -199,12 +190,12 @@ impl Packer { Node::Space => { // A text space is "soft", meaning that it can be eaten up by // adjacent line breaks or explicit spacings. - self.par.last.soft(ParChild::text(' ', styles)); + self.par.last.soft(Styled::new(ParChild::text(' '), styles)); } Node::Linebreak => { // A line break eats up surrounding text spaces. self.par.last.hard(); - self.push_inline(ParChild::text('\n', styles)); + self.push_inline(Styled::new(ParChild::text('\n'), styles)); self.par.last.hard(); } Node::Parbreak => { @@ -219,7 +210,7 @@ impl Packer { // discards the paragraph break. self.parbreak(None); self.make_flow_compatible(&styles); - self.flow.children.push(FlowChild::Skip); + self.flow.children.push(Styled::new(FlowChild::Skip, styles)); self.flow.last.hard(); } Node::Pagebreak => { @@ -230,13 +221,13 @@ impl Packer { self.flow.styles = styles; } Node::Text(text) => { - self.push_inline(ParChild::text(text, styles)); + self.push_inline(Styled::new(ParChild::text(text), styles)); } Node::Spacing(SpecAxis::Horizontal, kind) => { // Just like a line break, explicit horizontal spacing eats up // surrounding text spaces. self.par.last.hard(); - self.push_inline(ParChild::Spacing(SpacingNode { kind, styles })); + self.push_inline(Styled::new(ParChild::Spacing(kind), styles)); self.par.last.hard(); } Node::Spacing(SpecAxis::Vertical, kind) => { @@ -244,57 +235,56 @@ impl Packer { // discards the paragraph break. self.parbreak(None); self.make_flow_compatible(&styles); - self.flow - .children - .push(FlowChild::Spacing(SpacingNode { kind, styles })); + self.flow.children.push(Styled::new(FlowChild::Spacing(kind), styles)); self.flow.last.hard(); } Node::Inline(inline) => { - self.push_inline(ParChild::Node(inline.styled(styles))); + self.push_inline(Styled::new(ParChild::Node(inline), styles)); } Node::Block(block) => { - self.push_block(block.styled(styles)); + self.push_block(Styled::new(block, styles)); } Node::Page(page) => { if self.top { self.pagebreak(); - self.pages.push(page.styled(styles)); + self.pages.push(Styled::new(page, styles)); } else { - let flow = page.child.styled(page.styles); - self.push_block(flow.styled(styles)); + self.push_block(Styled::new(page.0, styles)); } } Node::Sequence(list) => { // For a list of nodes, we apply the list's styles to each node // individually. - for (node, mut inner) in list { - inner.apply(&styles); - self.walk(node, inner); + for mut node in list { + node.map.apply(&styles); + self.walk(node.item, node.map); } } } } /// Insert an inline-level element into the current paragraph. - fn push_inline(&mut self, child: ParChild) { - if let Some(child) = self.par.last.any() { - self.push_coalescing(child); + fn push_inline(&mut self, child: Styled) { + if let Some(styled) = self.par.last.any() { + self.push_coalescing(styled); } // The node must be both compatible with the current page and the // current paragraph. - self.make_flow_compatible(child.styles()); - self.make_par_compatible(child.styles()); + self.make_flow_compatible(&child.map); + self.make_par_compatible(&child.map); self.push_coalescing(child); self.par.last.any(); } /// Push a paragraph child, coalescing text nodes with compatible styles. - fn push_coalescing(&mut self, child: ParChild) { - if let ParChild::Text(right) = &child { - if let Some(ParChild::Text(left)) = self.par.children.last_mut() { - if left.styles.compatible(&right.styles, TextNode::has_property) { - left.text.push_str(&right.text); + fn push_coalescing(&mut self, child: Styled) { + if let ParChild::Text(right) = &child.item { + if let Some(Styled { item: ParChild::Text(left), map }) = + self.par.children.last_mut() + { + if child.map.compatible(map, TextNode::has_property) { + left.0.push_str(&right.0); return; } } @@ -304,13 +294,13 @@ impl Packer { } /// Insert a block-level element into the current flow. - fn push_block(&mut self, node: PackedNode) { - let placed = node.is::(); + fn push_block(&mut self, node: Styled) { + let placed = node.item.is::(); self.parbreak(None); - self.make_flow_compatible(&node.styles); + self.make_flow_compatible(&node.map); self.flow.children.extend(self.flow.last.any()); - self.flow.children.push(FlowChild::Node(node)); + self.flow.children.push(node.map(FlowChild::Node)); self.parbreak(None); // Prevent paragraph spacing between the placed node and the paragraph @@ -324,8 +314,8 @@ impl Packer { fn parbreak(&mut self, break_styles: Option) { // Erase any styles that will be inherited anyway. let Builder { mut children, styles, .. } = mem::take(&mut self.par); - for child in &mut children { - child.styles_mut().erase(&styles); + for Styled { map, .. } in &mut children { + map.erase(&styles); } // For explicit paragraph breaks, `break_styles` is already `Some(_)`. @@ -338,13 +328,13 @@ impl Packer { // The paragraph's children are all compatible with the page, so the // paragraph is too, meaning we don't need to check or intersect // anything here. - let par = ParNode(children).pack().styled(styles); + let par = ParNode(children).pack(); self.flow.children.extend(self.flow.last.any()); - self.flow.children.push(FlowChild::Node(par)); + self.flow.children.push(Styled::new(FlowChild::Node(par), styles)); } // Insert paragraph spacing. - self.flow.last.soft(FlowChild::Break(break_styles)); + self.flow.last.soft(Styled::new(FlowChild::Break, break_styles)); } /// Advance to the next page. @@ -354,13 +344,12 @@ impl Packer { // Take the flow and erase any styles that will be inherited anyway. let Builder { mut children, styles, .. } = mem::take(&mut self.flow); - for local in children.iter_mut().filter_map(FlowChild::styles_mut) { - local.erase(&styles); + for Styled { map, .. } in &mut children { + map.erase(&styles); } let flow = FlowNode(children).pack(); - let page = PageNode { child: flow, styles }; - self.pages.push(page); + self.pages.push(Styled::new(PageNode(flow), styles)); } } diff --git a/src/eval/styles.rs b/src/eval/styles.rs index d7c17b92f..54ac2697f 100644 --- a/src/eval/styles.rs +++ b/src/eval/styles.rs @@ -8,6 +8,42 @@ use std::rc::Rc; // - Store map in `Option` to make empty maps non-allocating // - Store small properties inline +/// An item with associated styles. +#[derive(PartialEq, Clone, Hash)] +pub struct Styled { + /// The item to apply styles to. + pub item: T, + /// The associated style map. + pub map: StyleMap, +} + +impl Styled { + /// Create a new instance from an item and a style map. + pub fn new(item: T, map: StyleMap) -> Self { + Self { item, map } + } + + /// Create a new instance with empty style map. + pub fn bare(item: T) -> Self { + Self { item, map: StyleMap::new() } + } + + /// Map the item with `f`. + pub fn map(self, f: F) -> Styled + where + F: FnOnce(T) -> U, + { + Styled { item: f(self.item), map: self.map } + } +} + +impl Debug for Styled { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.map.fmt(f)?; + self.item.fmt(f) + } +} + /// A map of style properties. #[derive(Default, Clone, Hash)] pub struct StyleMap(Vec); diff --git a/src/layout/mod.rs b/src/layout/mod.rs index e1df3d11c..a00687ebf 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -15,7 +15,7 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::rc::Rc; -use crate::eval::{StyleChain, StyleMap}; +use crate::eval::{StyleChain, Styled}; use crate::font::FontStore; use crate::frame::Frame; use crate::geom::{Align, Linear, Point, Sides, Size, Spec, Transform}; @@ -25,13 +25,16 @@ use crate::Context; /// The root layout node, a document consisting of top-level page runs. #[derive(Hash)] -pub struct RootNode(pub Vec); +pub struct RootNode(pub Vec>); impl RootNode { /// Layout the document into a sequence of frames, one per page. pub fn layout(&self, ctx: &mut Context) -> Vec> { let (mut ctx, styles) = LayoutContext::new(ctx); - self.0.iter().flat_map(|node| node.layout(&mut ctx, styles)).collect() + self.0 + .iter() + .flat_map(|styled| styled.item.layout(&mut ctx, styled.map.chain(&styles))) + .collect() } } @@ -64,7 +67,6 @@ pub trait Layout { #[cfg(feature = "layout-cache")] hash: self.hash64(), node: Rc::new(self), - styles: StyleMap::new(), } } } @@ -118,7 +120,7 @@ impl Layout for EmptyNode { } } -/// A packed layouting node with style properties and a precomputed hash. +/// A packed layouting node with a precomputed hash. #[derive(Clone)] pub struct PackedNode { /// The type-erased node. @@ -126,8 +128,6 @@ pub struct PackedNode { /// A precomputed hash for the node. #[cfg(feature = "layout-cache")] hash: u64, - /// The node's styles. - pub styles: StyleMap, } impl PackedNode { @@ -144,16 +144,6 @@ impl PackedNode { self.node.as_any().downcast_ref() } - /// Style the node with styles from a style map. - pub fn styled(mut self, styles: StyleMap) -> Self { - if self.styles.is_empty() { - self.styles = styles; - } else { - self.styles.apply(&styles); - } - self - } - /// Force a size for this node. pub fn sized(self, sizing: Spec>) -> Self { if sizing.any(Option::is_some) { @@ -207,10 +197,8 @@ impl Layout for PackedNode { regions: &Regions, styles: StyleChain, ) -> Vec>> { - let chained = self.styles.chain(&styles); - #[cfg(not(feature = "layout-cache"))] - return self.node.layout(ctx, regions, chained); + return self.node.layout(ctx, regions, styles); #[cfg(feature = "layout-cache")] let hash = { @@ -223,7 +211,7 @@ impl Layout for PackedNode { #[cfg(feature = "layout-cache")] ctx.layouts.get(hash, regions).unwrap_or_else(|| { ctx.level += 1; - let frames = self.node.layout(ctx, regions, chained); + let frames = self.node.layout(ctx, regions, styles); ctx.level -= 1; let entry = FramesEntry::new(frames.clone(), ctx.level); @@ -260,9 +248,6 @@ impl Default for PackedNode { impl Debug for PackedNode { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if f.alternate() { - self.styles.fmt(f)?; - } self.node.fmt(f) } } @@ -283,9 +268,6 @@ impl Hash for PackedNode { state.write_u64(self.hash); #[cfg(not(feature = "layout-cache"))] state.write_u64(self.hash64()); - - // Hash the styles. - self.styles.hash(state); } } diff --git a/src/library/flow.rs b/src/library/flow.rs index 8656efca4..f274c9b6d 100644 --- a/src/library/flow.rs +++ b/src/library/flow.rs @@ -3,14 +3,14 @@ use std::fmt::{self, Debug, Formatter}; use super::prelude::*; -use super::{AlignNode, ParNode, PlacedNode, SpacingKind, SpacingNode, TextNode}; +use super::{AlignNode, ParNode, PlacedNode, SpacingKind, TextNode}; /// A vertical flow of content consisting of paragraphs and other layout nodes. /// /// This node is reponsible for layouting both the top-level content flow and /// the contents of boxes. #[derive(Hash)] -pub struct FlowNode(pub Vec); +pub struct FlowNode(pub Vec>); impl Layout for FlowNode { fn layout( @@ -19,7 +19,7 @@ impl Layout for FlowNode { regions: &Regions, styles: StyleChain, ) -> Vec>> { - FlowLayouter::new(self, regions.clone(), styles).layout(ctx) + FlowLayouter::new(self, regions.clone()).layout(ctx, styles) } } @@ -33,50 +33,23 @@ impl Debug for FlowNode { /// A child of a flow node. #[derive(Hash)] pub enum FlowChild { - /// Vertical spacing between other children. - Spacing(SpacingNode), - /// An arbitrary node. - Node(PackedNode), /// A paragraph/block break. - Break(StyleMap), + Break, /// Skip the rest of the region and move to the next. Skip, -} - -impl FlowChild { - /// A reference to the child's styles. - pub fn styles(&self) -> Option<&StyleMap> { - match self { - Self::Spacing(node) => Some(&node.styles), - Self::Node(node) => Some(&node.styles), - Self::Break(styles) => Some(styles), - Self::Skip => None, - } - } - - /// A mutable reference to the child's styles. - pub fn styles_mut(&mut self) -> Option<&mut StyleMap> { - match self { - Self::Spacing(node) => Some(&mut node.styles), - Self::Node(node) => Some(&mut node.styles), - Self::Break(styles) => Some(styles), - Self::Skip => None, - } - } + /// Vertical spacing between other children. + Spacing(SpacingKind), + /// An arbitrary node. + Node(PackedNode), } impl Debug for FlowChild { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { + Self::Break => f.pad("Break"), + Self::Skip => f.pad("Skip"), Self::Spacing(node) => node.fmt(f), Self::Node(node) => node.fmt(f), - Self::Break(styles) => { - if f.alternate() { - styles.fmt(f)?; - } - write!(f, "Break") - } - Self::Skip => f.pad("Skip"), } } } @@ -84,11 +57,9 @@ impl Debug for FlowChild { /// Performs flow layout. struct FlowLayouter<'a> { /// The flow node to layout. - children: &'a [FlowChild], + children: &'a [Styled], /// The regions to layout children into. regions: Regions, - /// The inherited styles. - styles: StyleChain<'a>, /// Whether the flow should expand to fill the region. expand: Spec, /// The full size of `regions.current` that was available before we started @@ -118,7 +89,7 @@ enum FlowItem { impl<'a> FlowLayouter<'a> { /// Create a new flow layouter. - fn new(flow: &'a FlowNode, mut regions: Regions, styles: StyleChain<'a>) -> Self { + fn new(flow: &'a FlowNode, mut regions: Regions) -> Self { let expand = regions.expand; let full = regions.current; @@ -128,7 +99,6 @@ impl<'a> FlowLayouter<'a> { Self { children: &flow.0, regions, - styles, expand, full, used: Size::zero(), @@ -139,28 +109,32 @@ impl<'a> FlowLayouter<'a> { } /// Layout all children. - fn layout(mut self, ctx: &mut LayoutContext) -> Vec>> { - for child in self.children { - match child { - FlowChild::Spacing(node) => { - self.layout_spacing(node.kind); - } - FlowChild::Node(node) => { - if self.regions.is_full() { - self.finish_region(); - } - - self.layout_node(ctx, node); - } - FlowChild::Break(styles) => { - let chain = styles.chain(&self.styles); - let em = chain.get(TextNode::SIZE).abs; - let amount = chain.get(ParNode::SPACING).resolve(em); + fn layout( + mut self, + ctx: &mut LayoutContext, + styles: StyleChain, + ) -> Vec>> { + for styled in self.children { + let styles = styled.map.chain(&styles); + match styled.item { + FlowChild::Break => { + let em = styles.get(TextNode::SIZE).abs; + let amount = styles.get(ParNode::SPACING).resolve(em); self.layout_absolute(amount.into()); } FlowChild::Skip => { self.finish_region(); } + FlowChild::Spacing(kind) => { + self.layout_spacing(kind); + } + FlowChild::Node(ref node) => { + if self.regions.is_full() { + self.finish_region(); + } + + self.layout_node(ctx, node, styles); + } } } @@ -190,12 +164,17 @@ impl<'a> FlowLayouter<'a> { } /// Layout a node. - fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode) { + fn layout_node( + &mut self, + ctx: &mut LayoutContext, + node: &PackedNode, + styles: StyleChain, + ) { // Placed nodes that are out of flow produce placed items which aren't // aligned later. if let Some(placed) = node.downcast::() { if placed.out_of_flow() { - let frame = node.layout(ctx, &self.regions, self.styles).remove(0); + let frame = node.layout(ctx, &self.regions, styles).remove(0); self.items.push(FlowItem::Placed(frame.item)); return; } @@ -204,9 +183,9 @@ impl<'a> FlowLayouter<'a> { // How to align the node. let aligns = Spec::new( // For non-expanding paragraphs it is crucial that we align the - // whole paragraph according to its internal alignment. + // whole paragraph as it is itself aligned. if node.is::() { - node.styles.chain(&self.styles).get(ParNode::ALIGN) + styles.get(ParNode::ALIGN) } else { Align::Left }, @@ -216,7 +195,7 @@ impl<'a> FlowLayouter<'a> { .unwrap_or(Align::Top), ); - let frames = node.layout(ctx, &self.regions, self.styles); + let frames = node.layout(ctx, &self.regions, styles); let len = frames.len(); for (i, frame) in frames.into_iter().enumerate() { // Grow our size, shrink the region and save the frame for later. diff --git a/src/library/mod.rs b/src/library/mod.rs index 9033b3a76..89f4ab9d6 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -62,7 +62,7 @@ prelude! { pub use crate::diag::{At, TypResult}; pub use crate::eval::{ Args, Construct, EvalContext, Node, Property, Set, Smart, StyleChain, StyleMap, - Value, + Styled, Value, }; pub use crate::frame::*; pub use crate::geom::*; diff --git a/src/library/page.rs b/src/library/page.rs index fa9345f52..a6d489ba4 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -8,12 +8,7 @@ use super::{ColumnsNode, PadNode}; /// Layouts its child onto one or multiple pages. #[derive(Clone, PartialEq, Hash)] -pub struct PageNode { - /// The node producing the content. - pub child: PackedNode, - /// The page's styles. - pub styles: StyleMap, -} +pub struct PageNode(pub PackedNode); #[properties] impl PageNode { @@ -43,10 +38,7 @@ impl PageNode { impl Construct for PageNode { fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult { - Ok(Node::Page(Self { - child: args.expect("body")?, - styles: StyleMap::new(), - })) + Ok(Node::Page(Self(args.expect("body")?))) } } @@ -84,16 +76,8 @@ impl Set for PageNode { } impl PageNode { - /// Style the node with styles from a style map. - pub fn styled(mut self, styles: StyleMap) -> Self { - self.styles.apply(&styles); - self - } - /// Layout the page run into a sequence of frames, one per page. pub fn layout(&self, ctx: &mut LayoutContext, styles: StyleChain) -> Vec> { - let styles = self.styles.chain(&styles); - // When one of the lengths is infinite the page fits its content along // that axis. let width = styles.get(Self::WIDTH).unwrap_or(Length::inf()); @@ -113,21 +97,21 @@ impl PageNode { bottom: styles.get(Self::BOTTOM).unwrap_or(default.bottom), }; + let mut child = self.0.clone(); + // Realize columns with columns node. let columns = styles.get(Self::COLUMNS); - let child = if columns.get() > 1 { - ColumnsNode { + if columns.get() > 1 { + child = ColumnsNode { columns, gutter: styles.get(Self::COLUMN_GUTTER), - child: self.child.clone(), + child: self.0.clone(), } - .pack() - } else { - self.child.clone() - }; + .pack(); + } // Realize margins with padding node. - let child = PadNode { child, padding }.pack(); + child = PadNode { child, padding }.pack(); // Layout the child. let expand = size.map(Length::is_finite); @@ -152,11 +136,8 @@ impl PageNode { impl Debug for PageNode { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if f.alternate() { - self.styles.fmt(f)?; - } f.write_str("Page(")?; - self.child.fmt(f)?; + self.0.fmt(f)?; f.write_str(")") } } diff --git a/src/library/par.rs b/src/library/par.rs index 65a541f66..87ad2ebec 100644 --- a/src/library/par.rs +++ b/src/library/par.rs @@ -8,12 +8,12 @@ use unicode_bidi::{BidiInfo, Level}; use xi_unicode::LineBreakIterator; use super::prelude::*; -use super::{shape, ShapedText, SpacingKind, SpacingNode, TextNode}; +use super::{shape, ShapedText, SpacingKind, TextNode}; use crate::util::{EcoString, RangeExt, RcExt, SliceExt}; /// A node that arranges its children into a paragraph. #[derive(Hash)] -pub struct ParNode(pub Vec); +pub struct ParNode(pub Vec>); #[properties] impl ParNode { @@ -120,9 +120,9 @@ impl ParNode { /// The string representation of each child. fn strings(&self) -> impl Iterator { - self.0.iter().map(|child| match child { + self.0.iter().map(|styled| match &styled.item { ParChild::Spacing(_) => " ", - ParChild::Text(ref node) => &node.text, + ParChild::Text(node) => &node.0, ParChild::Node(_) => "\u{FFFC}", }) } @@ -139,7 +139,7 @@ impl Debug for ParNode { #[derive(Hash)] pub enum ParChild { /// Spacing between other nodes. - Spacing(SpacingNode), + Spacing(SpacingKind), /// A run of text and how to align it in its line. Text(TextNode), /// Any child node and how to align it in its line. @@ -148,26 +148,8 @@ pub enum ParChild { impl ParChild { /// Create a text child. - pub fn text(text: impl Into, styles: StyleMap) -> Self { - Self::Text(TextNode { text: text.into(), styles }) - } - - /// A reference to the child's styles. - pub fn styles(&self) -> &StyleMap { - match self { - Self::Spacing(node) => &node.styles, - Self::Text(node) => &node.styles, - Self::Node(node) => &node.styles, - } - } - - /// A mutable reference to the child's styles. - pub fn styles_mut(&mut self) -> &mut StyleMap { - match self { - Self::Spacing(node) => &mut node.styles, - Self::Text(node) => &mut node.styles, - Self::Node(node) => &mut node.styles, - } + pub fn text(text: impl Into) -> Self { + Self::Text(TextNode(text.into())) } } @@ -234,9 +216,10 @@ impl<'a> ParLayouter<'a> { let mut ranges = vec![]; // Layout the children and collect them into items. - for (range, child) in par.ranges().zip(&par.0) { - match child { - ParChild::Spacing(node) => match node.kind { + for (range, styled) in par.ranges().zip(&par.0) { + let styles = styled.map.chain(styles); + match styled.item { + ParChild::Spacing(kind) => match kind { SpacingKind::Linear(v) => { let resolved = v.resolve(regions.current.x); items.push(ParItem::Absolute(resolved)); @@ -247,7 +230,7 @@ impl<'a> ParLayouter<'a> { ranges.push(range); } }, - ParChild::Text(node) => { + ParChild::Text(_) => { // TODO: Also split by language and script. let mut cursor = range.start; for (level, group) in bidi.levels[range].group_by_key(|&lvl| lvl) { @@ -255,16 +238,15 @@ impl<'a> ParLayouter<'a> { cursor += group.len(); let subrange = start .. cursor; let text = &bidi.text[subrange.clone()]; - let styles = node.styles.chain(styles); let shaped = shape(ctx.fonts, text, styles, level.dir()); items.push(ParItem::Text(shaped)); ranges.push(subrange); } } - ParChild::Node(node) => { + ParChild::Node(ref node) => { let size = Size::new(regions.current.x, regions.base.y); let pod = Regions::one(size, regions.base, Spec::splat(false)); - let frame = node.layout(ctx, &pod, *styles).remove(0); + let frame = node.layout(ctx, &pod, styles).remove(0); items.push(ParItem::Frame(Rc::take(frame.item))); ranges.push(range); } diff --git a/src/library/spacing.rs b/src/library/spacing.rs index a58884855..1b1403e93 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -18,24 +18,6 @@ pub fn v(_: &mut EvalContext, args: &mut Args) -> TypResult { ))) } -/// Explicit spacing in a flow or paragraph. -#[derive(Hash)] -pub struct SpacingNode { - /// The kind of spacing. - pub kind: SpacingKind, - /// The spacing's styles. - pub styles: StyleMap, -} - -impl Debug for SpacingNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if f.alternate() { - self.styles.fmt(f)?; - } - write!(f, "{:?}", self.kind) - } -} - /// Kinds of spacing. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum SpacingKind { diff --git a/src/library/stack.rs b/src/library/stack.rs index d36c2e15c..f4f7a3cf6 100644 --- a/src/library/stack.rs +++ b/src/library/stack.rs @@ -1,7 +1,7 @@ //! Side-by-side layout of nodes along an axis. use super::prelude::*; -use super::{AlignNode, SpacingKind, SpacingNode}; +use super::{AlignNode, SpacingKind}; /// `stack`: Stack children along an axis. pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult { @@ -38,17 +38,11 @@ impl Layout for StackNode { #[derive(Hash)] pub enum StackChild { /// Spacing between other nodes. - Spacing(SpacingNode), + Spacing(SpacingKind), /// An arbitrary node. Node(PackedNode), } -impl From for StackChild { - fn from(kind: SpacingKind) -> Self { - Self::Spacing(SpacingNode { kind, styles: StyleMap::new() }) - } -} - impl Debug for StackChild { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { @@ -61,10 +55,10 @@ impl Debug for StackChild { castable! { StackChild, Expected: "linear, fractional or template", - Value::Length(v) => SpacingKind::Linear(v.into()).into(), - Value::Relative(v) => SpacingKind::Linear(v.into()).into(), - Value::Linear(v) => SpacingKind::Linear(v).into(), - Value::Fractional(v) => SpacingKind::Fractional(v).into(), + Value::Length(v) => Self::Spacing(SpacingKind::Linear(v.into())), + Value::Relative(v) => Self::Spacing(SpacingKind::Linear(v.into())), + Value::Linear(v) => Self::Spacing(SpacingKind::Linear(v)), + Value::Fractional(v) => Self::Spacing(SpacingKind::Fractional(v)), Value::Node(v) => Self::Node(v.into_block()), } @@ -140,12 +134,12 @@ impl<'a> StackLayouter<'a> { let mut deferred = None; for child in self.children { - match child { - StackChild::Spacing(node) => { - self.layout_spacing(node.kind); + match *child { + StackChild::Spacing(kind) => { + self.layout_spacing(kind); deferred = None; } - StackChild::Node(node) => { + StackChild::Node(ref node) => { if let Some(kind) = deferred { self.layout_spacing(kind); } diff --git a/src/library/text.rs b/src/library/text.rs index 486cb77f7..ab5c15c31 100644 --- a/src/library/text.rs +++ b/src/library/text.rs @@ -18,12 +18,7 @@ use crate::util::{EcoString, SliceExt}; /// A single run of text with the same style. #[derive(Hash)] -pub struct TextNode { - /// The run's text. - pub text: EcoString, - /// The run's styles. - pub styles: StyleMap, -} +pub struct TextNode(pub EcoString); #[properties] impl TextNode { @@ -154,10 +149,7 @@ impl Set for TextNode { impl Debug for TextNode { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if f.alternate() { - self.styles.fmt(f)?; - } - write!(f, "Text({:?})", self.text) + write!(f, "Text({:?})", self.0) } }