summaryrefslogtreecommitdiff
path: root/src/model/content.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/model/content.rs')
-rw-r--r--src/model/content.rs421
1 files changed, 238 insertions, 183 deletions
diff --git a/src/model/content.rs b/src/model/content.rs
index 3cdc6341..170d47a1 100644
--- a/src/model/content.rs
+++ b/src/model/content.rs
@@ -1,122 +1,82 @@
+use std::any::{Any, TypeId};
use std::fmt::{self, Debug, Formatter};
-use std::hash::Hash;
-use std::iter::Sum;
+use std::hash::{Hash, Hasher};
+use std::iter::{self, Sum};
use std::ops::{Add, AddAssign};
use std::sync::Arc;
use comemo::Tracked;
+use siphasher::sip128::{Hasher128, SipHasher};
+use typst_macros::node;
use super::{
- Builder, Key, Layout, LayoutNode, Property, Regions, Scratch, Selector, Show,
- ShowNode, StyleChain, StyleEntry, StyleMap,
+ Args, Barrier, Builder, Key, Layout, Level, Property, Regions, Scratch, Selector,
+ StyleChain, StyleEntry, StyleMap, Vm,
};
use crate::diag::{SourceResult, StrResult};
use crate::frame::Frame;
-use crate::geom::Abs;
-use crate::library::layout::{PageNode, Spacing};
-use crate::library::structure::ListItem;
-use crate::util::EcoString;
+use crate::util::ReadableTypeId;
use crate::World;
/// Composable representation of styled content.
///
/// This results from:
/// - anything written between square brackets in Typst
-/// - any node constructor
-///
-/// Content is represented as a tree of nodes. There are two nodes of special
-/// interest:
-///
-/// 1. A `Styled` node attaches a style map to other content. For example, a
-/// single bold word could be represented as a `Styled(Text("Hello"),
-/// [TextNode::STRONG: true])` node.
-///
-/// 2. A `Sequence` node content combines other arbitrary content and is the
-/// representation of a "flow" of other nodes. So, when you write `[Hi] +
-/// [you]` in Typst, this type's [`Add`] implementation is invoked and the
-/// two [`Text`](Self::Text) nodes are combined into a single
-/// [`Sequence`](Self::Sequence) node. A sequence may contain nested
-/// sequences.
-#[derive(PartialEq, Clone, Hash)]
-pub enum Content {
- /// Empty content.
- Empty,
- /// A word space.
- Space,
- /// A forced line break.
- Linebreak { justify: bool },
- /// Horizontal spacing.
- Horizontal { amount: Spacing, weak: bool },
- /// Plain text.
- Text(EcoString),
- /// A smart quote.
- Quote { double: bool },
- /// An inline-level node.
- Inline(LayoutNode),
- /// A paragraph break.
- Parbreak,
- /// A column break.
- Colbreak { weak: bool },
- /// Vertical spacing.
- Vertical {
- amount: Spacing,
- weak: bool,
- generated: bool,
- },
- /// A block-level node.
- Block(LayoutNode),
- /// A list / enum item.
- Item(ListItem),
- /// A page break.
- Pagebreak { weak: bool },
- /// A page node.
- Page(PageNode),
- /// A node that can be realized with styles, optionally with attached
- /// properties.
- Show(ShowNode),
- /// Content with attached styles.
- Styled(Arc<(Self, StyleMap)>),
- /// A sequence of multiple nodes.
- Sequence(Arc<Vec<Self>>),
-}
+/// - any constructor function
+#[derive(Clone, Hash)]
+pub struct Content(Arc<dyn Bounds>);
impl Content {
/// Create empty content.
- pub fn new() -> Self {
- Self::Empty
+ pub fn empty() -> Self {
+ SequenceNode(vec![]).pack()
}
- /// Create content from an inline-level node.
- pub fn inline<T>(node: T) -> Self
- where
- T: Layout + Debug + Hash + Sync + Send + 'static,
- {
- Self::Inline(node.pack())
+ /// Create a new sequence node from multiples nodes.
+ pub fn sequence(seq: Vec<Self>) -> Self {
+ match seq.as_slice() {
+ [_] => seq.into_iter().next().unwrap(),
+ _ => SequenceNode(seq).pack(),
+ }
}
- /// Create content from a block-level node.
- pub fn block<T>(node: T) -> Self
- where
- T: Layout + Debug + Hash + Sync + Send + 'static,
- {
- Self::Block(node.pack())
+ pub fn is_empty(&self) -> bool {
+ self.downcast::<SequenceNode>().map_or(false, |seq| seq.0.is_empty())
+ }
+
+ pub fn id(&self) -> NodeId {
+ (*self.0).id()
}
- /// Create content from a showable node.
- pub fn show<T>(node: T) -> Self
+ pub fn is<T: 'static>(&self) -> bool {
+ (*self.0).as_any().is::<T>()
+ }
+
+ pub fn downcast<T: 'static>(&self) -> Option<&T> {
+ (*self.0).as_any().downcast_ref::<T>()
+ }
+
+ fn try_downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
+ Arc::get_mut(&mut self.0)?.as_any_mut().downcast_mut::<T>()
+ }
+
+ /// Whether this content has the given capability.
+ pub fn has<C>(&self) -> bool
where
- T: Show + Debug + Hash + Sync + Send + 'static,
+ C: Capability + ?Sized,
{
- Self::Show(node.pack())
+ self.0.vtable(TypeId::of::<C>()).is_some()
}
- /// Create a new sequence node from multiples nodes.
- pub fn sequence(seq: Vec<Self>) -> Self {
- match seq.as_slice() {
- [] => Self::Empty,
- [_] => seq.into_iter().next().unwrap(),
- _ => Self::Sequence(Arc::new(seq)),
- }
+ /// Cast to a trait object if this content has the given capability.
+ pub fn to<C>(&self) -> Option<&C>
+ where
+ C: Capability + ?Sized,
+ {
+ let node: &dyn Bounds = &*self.0;
+ let vtable = node.vtable(TypeId::of::<C>())?;
+ let data = node as *const dyn Bounds as *const ();
+ Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) })
}
/// Repeat this content `n` times.
@@ -134,14 +94,12 @@ impl Content {
/// Style this content with a style entry.
pub fn styled_with_entry(mut self, entry: StyleEntry) -> Self {
- if let Self::Styled(styled) = &mut self {
- if let Some((_, map)) = Arc::get_mut(styled) {
- map.apply(entry);
- return self;
- }
+ if let Some(styled) = self.try_downcast_mut::<StyledNode>() {
+ styled.map.apply(entry);
+ return self;
}
- Self::Styled(Arc::new((self, entry.into())))
+ StyledNode { sub: self, map: entry.into() }.pack()
}
/// Style this content with a full style map.
@@ -150,14 +108,12 @@ impl Content {
return self;
}
- if let Self::Styled(styled) = &mut self {
- if let Some((_, map)) = Arc::get_mut(styled) {
- map.apply_map(&styles);
- return self;
- }
+ if let Some(styled) = self.try_downcast_mut::<StyledNode>() {
+ styled.map.apply_map(&styles);
+ return self;
}
- Self::Styled(Arc::new((self, styles)))
+ StyledNode { sub: self, map: styles }.pack()
}
/// Reenable the show rule identified by the selector.
@@ -165,119 +121,97 @@ impl Content {
self.clone().styled_with_entry(StyleEntry::Unguard(sel))
}
- /// Add weak vertical spacing above and below the node.
- pub fn spaced(self, above: Option<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(Content::Vertical {
- amount: above.into(),
- weak: true,
- generated: true,
- });
- }
+ #[comemo::memoize]
+ pub fn layout_block(
+ &self,
+ world: Tracked<dyn World>,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> SourceResult<Vec<Frame>> {
+ let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
+ let styles = barrier.chain(&styles);
- seq.push(self);
- if let Some(below) = below {
- seq.push(Content::Vertical {
- amount: below.into(),
- weak: true,
- generated: true,
- });
+ if let Some(node) = self.to::<dyn Layout>() {
+ if node.level() == Level::Block {
+ return node.layout(world, regions, styles);
+ }
}
- Self::sequence(seq)
+ let scratch = Scratch::default();
+ let mut builder = Builder::new(world, &scratch, false);
+ builder.accept(self, styles)?;
+ let (flow, shared) = builder.into_flow(styles)?;
+ flow.layout(world, regions, shared)
}
-}
-impl Layout for Content {
- fn layout(
+
+ #[comemo::memoize]
+ pub fn layout_inline(
&self,
world: Tracked<dyn World>,
regions: &Regions,
styles: StyleChain,
) -> SourceResult<Vec<Frame>> {
+ let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
+ let styles = barrier.chain(&styles);
+
+ if let Some(node) = self.to::<dyn Layout>() {
+ return node.layout(world, regions, styles);
+ }
+
let scratch = Scratch::default();
let mut builder = Builder::new(world, &scratch, false);
builder.accept(self, styles)?;
let (flow, shared) = builder.into_flow(styles)?;
flow.layout(world, regions, shared)
}
-
- fn pack(self) -> LayoutNode {
- match self {
- Content::Block(node) => node,
- other => LayoutNode::new(other),
- }
- }
}
impl Default for Content {
fn default() -> Self {
- Self::new()
+ Self::empty()
}
}
impl Debug for Content {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- match self {
- Self::Empty => f.pad("Empty"),
- Self::Space => f.pad("Space"),
- Self::Linebreak { justify } => write!(f, "Linebreak({justify})"),
- Self::Horizontal { amount, weak } => {
- write!(f, "Horizontal({amount:?}, {weak})")
- }
- Self::Text(text) => write!(f, "Text({text:?})"),
- Self::Quote { double } => write!(f, "Quote({double})"),
- Self::Inline(node) => node.fmt(f),
- Self::Parbreak => f.pad("Parbreak"),
- Self::Colbreak { weak } => write!(f, "Colbreak({weak})"),
- Self::Vertical { amount, weak, generated } => {
- write!(f, "Vertical({amount:?}, {weak}, {generated})")
- }
- Self::Block(node) => node.fmt(f),
- Self::Item(item) => item.fmt(f),
- Self::Pagebreak { weak } => write!(f, "Pagebreak({weak})"),
- Self::Page(page) => page.fmt(f),
- Self::Show(node) => node.fmt(f),
- Self::Styled(styled) => {
- let (sub, map) = styled.as_ref();
- map.fmt(f)?;
- sub.fmt(f)
- }
- Self::Sequence(seq) => f.debug_list().entries(seq.iter()).finish(),
- }
+ self.0.fmt(f)
+ }
+}
+
+impl PartialEq for Content {
+ fn eq(&self, other: &Self) -> bool {
+ (*self.0).hash128() == (*other.0).hash128()
}
}
impl Add for Content {
type Output = Self;
- fn add(self, rhs: Self) -> Self::Output {
- Self::Sequence(match (self, rhs) {
- (Self::Empty, rhs) => return rhs,
- (lhs, Self::Empty) => return lhs,
- (Self::Sequence(mut lhs), Self::Sequence(rhs)) => {
- let mutable = Arc::make_mut(&mut lhs);
- match Arc::try_unwrap(rhs) {
- Ok(vec) => mutable.extend(vec),
- Err(rc) => mutable.extend(rc.iter().cloned()),
- }
- lhs
- }
- (Self::Sequence(mut lhs), rhs) => {
- Arc::make_mut(&mut lhs).push(rhs);
- lhs
+ fn add(self, mut rhs: Self) -> Self::Output {
+ let mut lhs = self;
+ if let Some(lhs_mut) = lhs.try_downcast_mut::<SequenceNode>() {
+ if let Some(rhs_mut) = rhs.try_downcast_mut::<SequenceNode>() {
+ lhs_mut.0.extend(rhs_mut.0.drain(..));
+ } else if let Some(rhs) = rhs.downcast::<SequenceNode>() {
+ lhs_mut.0.extend(rhs.0.iter().cloned());
+ } else {
+ lhs_mut.0.push(rhs);
}
- (lhs, Self::Sequence(mut rhs)) => {
- Arc::make_mut(&mut rhs).insert(0, lhs);
- rhs
- }
- (lhs, rhs) => Arc::new(vec![lhs, rhs]),
- })
+ return lhs;
+ }
+
+ let seq = match (
+ lhs.downcast::<SequenceNode>(),
+ rhs.downcast::<SequenceNode>(),
+ ) {
+ (Some(lhs), Some(rhs)) => lhs.0.iter().chain(&rhs.0).cloned().collect(),
+ (Some(lhs), None) => lhs.0.iter().cloned().chain(iter::once(rhs)).collect(),
+ (None, Some(rhs)) => iter::once(lhs).chain(rhs.0.iter().cloned()).collect(),
+ (None, None) => vec![lhs, rhs],
+ };
+
+ SequenceNode(seq).pack()
}
}
@@ -292,3 +226,124 @@ impl Sum for Content {
Self::sequence(iter.collect())
}
}
+
+trait Bounds: Node + Debug + Sync + Send + 'static {
+ fn as_any(&self) -> &dyn Any;
+ fn as_any_mut(&mut self) -> &mut dyn Any;
+ fn hash128(&self) -> u128;
+}
+
+impl<T> Bounds for T
+where
+ T: Node + Debug + Hash + Sync + Send + 'static,
+{
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+
+ fn as_any_mut(&mut self) -> &mut dyn Any {
+ self
+ }
+
+ fn hash128(&self) -> u128 {
+ let mut state = SipHasher::new();
+ self.type_id().hash(&mut state);
+ self.hash(&mut state);
+ state.finish128().as_u128()
+ }
+}
+
+impl Hash for dyn Bounds {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ state.write_u128(self.hash128());
+ }
+}
+
+/// A constructable, stylable content node.
+pub trait Node: 'static {
+ /// Pack into type-erased content.
+ fn pack(self) -> Content
+ where
+ Self: Debug + Hash + Sync + Send + Sized + 'static,
+ {
+ Content(Arc::new(self))
+ }
+
+ /// Construct a node from the arguments.
+ ///
+ /// This is passed only the arguments that remain after execution of the
+ /// node's set rule.
+ fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult<Content>
+ where
+ Self: Sized;
+
+ /// Parse relevant arguments into style properties for this node.
+ ///
+ /// When `constructor` is true, [`construct`](Self::construct) will run
+ /// after this invocation of `set` with the remaining arguments.
+ fn set(args: &mut Args, constructor: bool) -> SourceResult<StyleMap>
+ where
+ Self: Sized;
+
+ /// A unique identifier of the node type.
+ fn id(&self) -> NodeId;
+
+ /// Extract the pointer of the vtable of the trait object with the
+ /// given type `id` if this node implements that trait.
+ fn vtable(&self, id: TypeId) -> Option<*const ()>;
+}
+
+/// A unique identifier for a node.
+#[derive(Copy, Clone, Eq, PartialEq, Hash)]
+pub struct NodeId(ReadableTypeId);
+
+impl NodeId {
+ /// The id of the given node.
+ pub fn of<T: 'static>() -> Self {
+ Self(ReadableTypeId::of::<T>())
+ }
+}
+
+impl Debug for NodeId {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+/// A capability a node can have.
+///
+/// This is implemented by trait objects.
+pub trait Capability: 'static + Send + Sync {}
+
+/// A node with applied styles.
+#[derive(Clone, Hash)]
+pub struct StyledNode {
+ pub sub: Content,
+ pub map: StyleMap,
+}
+
+#[node]
+impl StyledNode {}
+
+impl Debug for StyledNode {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ self.map.fmt(f)?;
+ self.sub.fmt(f)
+ }
+}
+
+/// A sequence of nodes.
+///
+/// Combines other arbitrary content. So, when you write `[Hi] + [you]` in
+/// Typst, the two text nodes are combined into a single sequence node.
+#[derive(Clone, Hash)]
+pub struct SequenceNode(pub Vec<Content>);
+
+#[node]
+impl SequenceNode {}
+
+impl Debug for SequenceNode {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.debug_list().entries(self.0.iter()).finish()
+ }
+}