diff options
Diffstat (limited to 'src/model/content.rs')
| -rw-r--r-- | src/model/content.rs | 421 |
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() + } +} |
