summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-01-05 16:01:56 +0100
committerLaurenz <laurmaedje@gmail.com>2022-01-05 23:55:06 +0100
commit5fd9c0b0d7b519802d56dd04cb61340c11014cb1 (patch)
treea791ad72a92037426c1b170ccc543fb1afff0c77 /src/eval
parentf7e8624b4cf31744d600167dd7f3a9d9d1626014 (diff)
Lift styles out of individual nodes
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/class.rs54
-rw-r--r--src/eval/mod.rs2
-rw-r--r--src/eval/node.rs113
-rw-r--r--src/eval/styles.rs36
4 files changed, 100 insertions, 105 deletions
diff --git a/src/eval/class.rs b/src/eval/class.rs
index 5682cb4d..7888cba2 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<Inner<dyn Bounds>>);
-
-/// The unsized structure behind the [`Rc`].
-struct Inner<T: ?Sized> {
+pub struct Class {
name: EcoString,
- shim: T,
+ construct: fn(&mut EvalContext, &mut Args) -> TypResult<Node>,
+ 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::<T>(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<Node> {
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<Node>;
- fn set(&self, args: &mut Args, styles: &mut StyleMap) -> TypResult<()>;
-}
-
-struct Shim<T>(PhantomData<T>);
-
-impl<T> Bounds for Shim<T>
-where
- T: Construct + Set,
-{
- fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
- 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 06218752..e8c8fcd2 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 54d4104d..2c955d01 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<Styled<Self>>),
}
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<I: Iterator<Item = Self>>(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<PageNode>,
+ pages: Vec<Styled<PageNode>>,
/// The accumulated flow children.
- flow: Builder<FlowChild>,
+ flow: Builder<Styled<FlowChild>>,
/// The accumulated paragraph children.
- par: Builder<ParChild>,
+ par: Builder<Styled<ParChild>>,
}
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<ParChild>) {
+ 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<ParChild>) {
+ 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::<PlacedNode>();
+ fn push_block(&mut self, node: Styled<PackedNode>) {
+ let placed = node.item.is::<PlacedNode>();
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<StyleMap>) {
// 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 d7c17b92..54ac2697 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<T> {
+ /// The item to apply styles to.
+ pub item: T,
+ /// The associated style map.
+ pub map: StyleMap,
+}
+
+impl<T> Styled<T> {
+ /// 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<F, U>(self, f: F) -> Styled<U>
+ where
+ F: FnOnce(T) -> U,
+ {
+ Styled { item: f(self.item), map: self.map }
+ }
+}
+
+impl<T: Debug> Debug for Styled<T> {
+ 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<Entry>);