diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-11-01 16:56:35 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-11-02 09:18:33 +0100 |
| commit | 37ac5d966ebaf97ac79c507028cd5b742b510b89 (patch) | |
| tree | 249d43ff0f8d880cb5d00c236993f8ff0c1f10d8 /src/model | |
| parent | f547c97072881069417acd3b79b08fb7ecf40ba2 (diff) | |
More dynamic content representation
Diffstat (limited to 'src/model')
| -rw-r--r-- | src/model/cast.rs | 10 | ||||
| -rw-r--r-- | src/model/collapse.rs | 2 | ||||
| -rw-r--r-- | src/model/content.rs | 421 | ||||
| -rw-r--r-- | src/model/eval.rs | 113 | ||||
| -rw-r--r-- | src/model/func.rs | 24 | ||||
| -rw-r--r-- | src/model/layout.rs | 255 | ||||
| -rw-r--r-- | src/model/methods.rs | 1 | ||||
| -rw-r--r-- | src/model/mod.rs | 4 | ||||
| -rw-r--r-- | src/model/ops.rs | 11 | ||||
| -rw-r--r-- | src/model/property.rs | 55 | ||||
| -rw-r--r-- | src/model/realize.rs | 224 | ||||
| -rw-r--r-- | src/model/recipe.rs | 50 | ||||
| -rw-r--r-- | src/model/show.rs | 127 | ||||
| -rw-r--r-- | src/model/styles.rs | 36 | ||||
| -rw-r--r-- | src/model/value.rs | 35 | ||||
| -rw-r--r-- | src/model/vm.rs | 11 |
16 files changed, 545 insertions, 834 deletions
diff --git a/src/model/cast.rs b/src/model/cast.rs index 00a3fe45..7356ef70 100644 --- a/src/model/cast.rs +++ b/src/model/cast.rs @@ -1,6 +1,6 @@ use std::num::NonZeroUsize; -use super::{Content, Layout, LayoutNode, Pattern, Regex, Value}; +use super::{Pattern, Regex, Value}; use crate::diag::{with_alternative, StrResult}; use crate::geom::{Corners, Dir, Paint, Sides}; use crate::syntax::Spanned; @@ -171,14 +171,6 @@ castable! { } castable! { - LayoutNode, - Expected: "content", - Value::None => Self::default(), - Value::Str(text) => Content::Text(text.into()).pack(), - Value::Content(content) => content.pack(), -} - -castable! { Pattern, Expected: "function, string or regular expression", Value::Func(func) => Self::Node(func.node()?), diff --git a/src/model/collapse.rs b/src/model/collapse.rs index 5a0d6531..f57a3a42 100644 --- a/src/model/collapse.rs +++ b/src/model/collapse.rs @@ -1,7 +1,7 @@ use super::{StyleChain, StyleVec, StyleVecBuilder}; /// A wrapper around a [`StyleVecBuilder`] that allows to collapse items. -pub struct CollapsingBuilder<'a, T> { +pub(super) struct CollapsingBuilder<'a, T> { /// The internal builder. builder: StyleVecBuilder<'a, T>, /// Staged weak and ignorant items that we can't yet commit to the builder. 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() + } +} diff --git a/src/model/eval.rs b/src/model/eval.rs index ca1c7974..02617ed6 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -8,11 +8,12 @@ use unicode_segmentation::UnicodeSegmentation; use super::{ methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Flow, Func, - Pattern, Recipe, Scope, Scopes, Show, StyleEntry, StyleMap, Value, Vm, + Node, Pattern, Recipe, Scope, Scopes, Show, StyleEntry, StyleMap, Value, Vm, }; use crate::diag::{At, SourceResult, StrResult, Trace, Tracepoint}; use crate::geom::{Abs, Angle, Em, Fr, Ratio}; -use crate::library; +use crate::library::math; +use crate::library::text::TextNode; use crate::syntax::ast::TypedNode; use crate::syntax::{ast, SourceId, Span, Spanned, Unit}; use crate::util::EcoString; @@ -189,11 +190,11 @@ impl Eval for ast::MarkupNode { impl Eval for ast::Space { type Output = Content; - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { + fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { Ok(if self.newlines() < 2 { - Content::Space + (vm.items().space)() } else { - Content::Parbreak + (vm.items().parbreak)() }) } } @@ -201,40 +202,40 @@ impl Eval for ast::Space { impl Eval for ast::Linebreak { type Output = Content; - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Content::Linebreak { justify: false }) + fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { + Ok((vm.items().linebreak)(false)) } } impl Eval for ast::Text { type Output = Content; - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Content::Text(self.get().clone())) + fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { + Ok(vm.text(self.get().clone())) } } impl Eval for ast::Escape { type Output = Content; - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Content::Text(self.get().into())) + fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { + Ok(vm.text(self.get())) } } impl Eval for ast::Shorthand { type Output = Content; - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Content::Text(self.get().into())) + fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { + Ok(vm.text(self.get())) } } impl Eval for ast::SmartQuote { type Output = Content; - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Content::Quote { double: self.double() }) + fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { + Ok((vm.items().smart_quote)(self.double())) } } @@ -277,7 +278,7 @@ impl Eval for ast::Label { type Output = Content; fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Content::Empty) + Ok(Content::empty()) } } @@ -335,26 +336,23 @@ impl Eval for ast::Math { .children() .map(|node| node.eval(vm)) .collect::<SourceResult<_>>()?; - Ok(Content::show(library::math::MathNode::Row( - Arc::new(nodes), - self.span(), - ))) + Ok(math::MathNode::Row(Arc::new(nodes), self.span()).pack()) } } impl Eval for ast::MathNode { - type Output = library::math::MathNode; + type Output = math::MathNode; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { Ok(match self { - Self::Space(_) => library::math::MathNode::Space, - Self::Linebreak(_) => library::math::MathNode::Linebreak, - Self::Escape(c) => library::math::MathNode::Atom(c.get().into()), - Self::Atom(atom) => library::math::MathNode::Atom(atom.get().clone()), + Self::Space(_) => math::MathNode::Space, + Self::Linebreak(_) => math::MathNode::Linebreak, + Self::Escape(c) => math::MathNode::Atom(c.get().into()), + Self::Atom(atom) => math::MathNode::Atom(atom.get().clone()), Self::Script(node) => node.eval(vm)?, Self::Frac(node) => node.eval(vm)?, Self::Align(node) => node.eval(vm)?, - Self::Group(node) => library::math::MathNode::Row( + Self::Group(node) => math::MathNode::Row( Arc::new( node.children() .map(|node| node.eval(vm)) @@ -362,54 +360,54 @@ impl Eval for ast::MathNode { ), node.span(), ), - Self::Expr(expr) => match expr.eval(vm)?.display(vm.world) { - Content::Text(text) => library::math::MathNode::Atom(text), - _ => bail!(expr.span(), "expected text"), - }, + Self::Expr(expr) => { + let content = expr.eval(vm)?.display(vm.world); + if let Some(node) = content.downcast::<TextNode>() { + math::MathNode::Atom(node.0.clone()) + } else { + bail!(expr.span(), "expected text") + } + } }) } } impl Eval for ast::Script { - type Output = library::math::MathNode; + type Output = math::MathNode; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok(library::math::MathNode::Script(Arc::new( - library::math::ScriptNode { - base: self.base().eval(vm)?, - sub: self - .sub() - .map(|node| node.eval(vm)) - .transpose()? - .map(|node| node.unparen()), - sup: self - .sup() - .map(|node| node.eval(vm)) - .transpose()? - .map(|node| node.unparen()), - }, - ))) + Ok(math::MathNode::Script(Arc::new(math::ScriptNode { + base: self.base().eval(vm)?, + sub: self + .sub() + .map(|node| node.eval(vm)) + .transpose()? + .map(|node| node.unparen()), + sup: self + .sup() + .map(|node| node.eval(vm)) + .transpose()? + .map(|node| node.unparen()), + }))) } } impl Eval for ast::Frac { - type Output = library::math::MathNode; + type Output = math::MathNode; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok(library::math::MathNode::Frac(Arc::new( - library::math::FracNode { - num: self.num().eval(vm)?.unparen(), - denom: self.denom().eval(vm)?.unparen(), - }, - ))) + Ok(math::MathNode::Frac(Arc::new(math::FracNode { + num: self.num().eval(vm)?.unparen(), + denom: self.denom().eval(vm)?.unparen(), + }))) } } impl Eval for ast::Align { - type Output = library::math::MathNode; + type Output = math::MathNode; fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(library::math::MathNode::Align(self.count())) + Ok(math::MathNode::Align(self.count())) } } @@ -706,8 +704,9 @@ impl Eval for ast::FieldAccess { Ok(match object { Value::Dict(dict) => dict.get(&field).at(span)?.clone(), - Value::Content(Content::Show(node)) => node - .field(&field) + Value::Content(node) => node + .to::<dyn Show>() + .and_then(|node| node.field(&field)) .ok_or_else(|| format!("unknown field {field:?}")) .at(span)? .clone(), diff --git a/src/model/func.rs b/src/model/func.rs index a4f63aa1..47ad4436 100644 --- a/src/model/func.rs +++ b/src/model/func.rs @@ -4,9 +4,7 @@ use std::sync::Arc; use comemo::{Track, Tracked}; -use super::{ - Args, Content, Eval, Flow, NodeId, Route, Scope, Scopes, StyleMap, Value, Vm, -}; +use super::{Args, Eval, Flow, Node, NodeId, Route, Scope, Scopes, StyleMap, Value, Vm}; use crate::diag::{SourceResult, StrResult}; use crate::syntax::ast::Expr; use crate::syntax::SourceId; @@ -52,7 +50,7 @@ impl Func { Ok(Value::Content(content.styled_with_map(styles.scoped()))) }, set: Some(|args| T::set(args, false)), - node: T::SHOWABLE.then(|| NodeId::of::<T>()), + node: Some(NodeId::of::<T>()), }))) } @@ -168,24 +166,6 @@ impl Hash for Native { } } -/// A constructable, stylable content node. -pub trait Node: 'static { - /// Whether this node can be customized through a show rule. - const SHOWABLE: bool; - - /// 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>; - - /// 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>; -} - /// A user-defined closure. #[derive(Hash)] pub struct Closure { diff --git a/src/model/layout.rs b/src/model/layout.rs index 5f21765d..e8aa0e96 100644 --- a/src/model/layout.rs +++ b/src/model/layout.rs @@ -1,17 +1,13 @@ //! Layouting infrastructure. -use std::any::Any; -use std::fmt::{self, Debug, Formatter, Write}; use std::hash::Hash; -use std::sync::Arc; -use comemo::{Prehashed, Tracked}; +use comemo::Tracked; -use super::{Barrier, NodeId, Resolve, StyleChain, StyleEntry}; -use super::{Builder, Content, Scratch}; +use super::{Builder, Capability, Content, Scratch, StyleChain}; use crate::diag::SourceResult; -use crate::frame::{Element, Frame}; -use crate::geom::{Abs, Align, Axes, Geometry, Length, Paint, Point, Rel, Size, Stroke}; +use crate::frame::Frame; +use crate::geom::{Abs, Axes, Size}; use crate::World; /// Layout content into a collection of pages. @@ -30,7 +26,7 @@ pub fn layout(world: Tracked<dyn World>, content: &Content) -> SourceResult<Vec< /// A node that can be layouted into a sequence of regions. /// /// Layouting returns one frame per used region. -pub trait Layout: 'static { +pub trait Layout: 'static + Sync + Send { /// Layout this node into the given regions, producing frames. fn layout( &self, @@ -39,13 +35,17 @@ pub trait Layout: 'static { styles: StyleChain, ) -> SourceResult<Vec<Frame>>; - /// Convert to a packed node. - fn pack(self) -> LayoutNode - where - Self: Debug + Hash + Sized + Sync + Send + 'static, - { - LayoutNode::new(self) - } + /// Whether this is an inline-level or block-level node. + fn level(&self) -> Level; +} + +impl Capability for dyn Layout {} + +/// At which level a node operates. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum Level { + Inline, + Block, } /// A sequence of regions to layout into. @@ -140,226 +140,3 @@ impl Regions { first.chain(backlog.chain(last).map(|&h| Size::new(self.first.x, h))) } } - -/// A type-erased layouting node with a precomputed hash. -#[derive(Clone, Hash)] -pub struct LayoutNode(Arc<Prehashed<dyn Bounds>>); - -impl LayoutNode { - /// Pack any layoutable node. - pub fn new<T>(node: T) -> Self - where - T: Layout + Debug + Hash + Sync + Send + 'static, - { - Self(Arc::new(Prehashed::new(node))) - } - - /// Check whether the contained node is a specific layout node. - pub fn is<T: 'static>(&self) -> bool { - (**self.0).as_any().is::<T>() - } - - /// The id of this node. - pub fn id(&self) -> NodeId { - (**self.0).node_id() - } - - /// Try to downcast to a specific layout node. - pub fn downcast<T>(&self) -> Option<&T> - where - T: Layout + Debug + Hash + 'static, - { - (**self.0).as_any().downcast_ref() - } - - /// Force a size for this node. - pub fn sized(self, sizing: Axes<Option<Rel<Length>>>) -> Self { - if sizing.any(Option::is_some) { - SizedNode { sizing, child: self }.pack() - } else { - self - } - } - - /// Fill the frames resulting from a node. - pub fn filled(self, fill: Paint) -> Self { - FillNode { fill, child: self }.pack() - } - - /// Stroke the frames resulting from a node. - pub fn stroked(self, stroke: Stroke) -> Self { - StrokeNode { stroke, child: self }.pack() - } -} - -impl Layout for LayoutNode { - #[comemo::memoize] - fn layout( - &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); - self.0.layout(world, regions, styles) - } - - fn pack(self) -> LayoutNode { - self - } -} - -impl Default for LayoutNode { - fn default() -> Self { - EmptyNode.pack() - } -} - -impl Debug for LayoutNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("Layout(")?; - self.0.fmt(f)?; - f.write_char(')') - } -} - -impl PartialEq for LayoutNode { - fn eq(&self, other: &Self) -> bool { - self.0.eq(&other.0) - } -} - -trait Bounds: Layout + Debug + Sync + Send + 'static { - fn as_any(&self) -> &dyn Any; - fn node_id(&self) -> NodeId; -} - -impl<T> Bounds for T -where - T: Layout + Debug + Hash + Sync + Send + 'static, -{ - fn as_any(&self) -> &dyn Any { - self - } - - fn node_id(&self) -> NodeId { - NodeId::of::<Self>() - } -} - -/// A layout node that produces an empty frame. -/// -/// The packed version of this is returned by [`PackedNode::default`]. -#[derive(Debug, Hash)] -struct EmptyNode; - -impl Layout for EmptyNode { - fn layout( - &self, - _: Tracked<dyn World>, - regions: &Regions, - _: StyleChain, - ) -> SourceResult<Vec<Frame>> { - Ok(vec![Frame::new( - regions.expand.select(regions.first, Size::zero()), - )]) - } -} - -/// Fix the size of a node. -#[derive(Debug, Hash)] -struct SizedNode { - /// How to size the node horizontally and vertically. - sizing: Axes<Option<Rel<Length>>>, - /// The node to be sized. - child: LayoutNode, -} - -impl Layout for SizedNode { - 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(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) - } -} - -/// 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: LayoutNode, -} - -impl Layout for FillNode { - fn layout( - &self, - world: Tracked<dyn World>, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult<Vec<Frame>> { - let mut frames = self.child.layout(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) - } -} - -/// 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: LayoutNode, -} - -impl Layout for StrokeNode { - fn layout( - &self, - world: Tracked<dyn World>, - regions: &Regions, - styles: StyleChain, - ) -> SourceResult<Vec<Frame>> { - let mut frames = self.child.layout(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) - } -} diff --git a/src/model/methods.rs b/src/model/methods.rs index 57fff681..07cbb822 100644 --- a/src/model/methods.rs +++ b/src/model/methods.rs @@ -36,7 +36,6 @@ pub fn call( "position" => string .position(args.expect("pattern")?) .map_or(Value::None, Value::Int), - "match" => string .match_(args.expect("pattern")?) .map_or(Value::None, Value::Dict), diff --git a/src/model/mod.rs b/src/model/mod.rs index 52d8c461..aba9514c 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -8,7 +8,6 @@ mod eval; mod layout; mod property; mod recipe; -mod show; #[macro_use] mod cast; #[macro_use] @@ -36,7 +35,6 @@ pub use args::*; pub use array::*; pub use capture::*; pub use cast::*; -pub use collapse::*; pub use content::*; pub use dict::*; pub use eval::*; @@ -48,10 +46,10 @@ pub use raw::*; pub use recipe::*; pub use resolve::*; pub use scope::*; -pub use show::*; pub use styles::*; pub use typst_macros::node; pub use value::*; pub use vm::*; +// use collapse::*; use realize::*; diff --git a/src/model/ops.rs b/src/model/ops.rs index c7d6ac78..7a8c6950 100644 --- a/src/model/ops.rs +++ b/src/model/ops.rs @@ -2,9 +2,10 @@ use std::cmp::Ordering; -use super::{RawAlign, RawStroke, Regex, Smart, Value}; +use super::{Node, RawAlign, RawStroke, Regex, Smart, Value}; use crate::diag::StrResult; use crate::geom::{Axes, Axis, Length, Numeric, Rel}; +use crate::library::text::TextNode; use Value::*; /// Bail with a type mismatch error. @@ -20,8 +21,8 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> { (a, None) => a, (None, b) => b, (Str(a), Str(b)) => Str(a + b), - (Str(a), Content(b)) => Content(super::Content::Text(a.into()) + b), - (Content(a), Str(b)) => Content(a + super::Content::Text(b.into())), + (Str(a), Content(b)) => Content(TextNode(a.into()).pack() + b), + (Content(a), Str(b)) => Content(a + TextNode(b.into()).pack()), (Content(a), Content(b)) => Content(a + b), (Array(a), Array(b)) => Array(a + b), (Dict(a), Dict(b)) => Dict(a + b), @@ -86,8 +87,8 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> { (Str(a), Str(b)) => Str(a + b), (Content(a), Content(b)) => Content(a + b), - (Content(a), Str(b)) => Content(a + super::Content::Text(b.into())), - (Str(a), Content(b)) => Content(super::Content::Text(a.into()) + b), + (Content(a), Str(b)) => Content(a + TextNode(b.into()).pack()), + (Str(a), Content(b)) => Content(TextNode(a.into()).pack() + b), (Array(a), Array(b)) => Array(a + b), (Dict(a), Dict(b)) => Dict(a + b), diff --git a/src/model/property.rs b/src/model/property.rs index acd31b8a..3a498d2c 100644 --- a/src/model/property.rs +++ b/src/model/property.rs @@ -17,10 +17,10 @@ pub struct Property { /// The id of the property's [key](Key). key: KeyId, /// The id of the node the property belongs to. - pub node: NodeId, + node: NodeId, /// Whether the property should only affect the first node down the /// hierarchy. Used by constructors. - pub scoped: bool, + scoped: bool, /// The property's value. value: Arc<Prehashed<dyn Bounds>>, /// The name of the property. @@ -60,6 +60,21 @@ impl Property { } } + /// The node this property is for. + pub fn node(&self) -> NodeId { + self.node + } + + /// Whether the property is scoped. + pub fn scoped(&self) -> bool { + self.scoped + } + + /// Make the property scoped. + pub fn make_scoped(&mut self) { + self.scoped = true; + } + /// What kind of structure the property interrupts. pub fn interruption(&self) -> Option<Interruption> { if self.is_of::<PageNode>() { @@ -110,24 +125,7 @@ where } } -/// A unique identifier for a property key. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct KeyId(ReadableTypeId); - -impl KeyId { - /// The id of the given key. - pub fn of<'a, T: Key<'a>>() -> Self { - Self(ReadableTypeId::of::<T>()) - } -} - -impl Debug for KeyId { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -/// Style property keys. +/// A style property key. /// /// This trait is not intended to be implemented manually, but rather through /// the `#[node]` proc-macro. @@ -153,6 +151,23 @@ pub trait Key<'a>: Copy + 'static { ) -> Self::Output; } +/// A unique identifier for a property key. +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +struct KeyId(ReadableTypeId); + +impl KeyId { + /// The id of the given key. + pub fn of<'a, T: Key<'a>>() -> Self { + Self(ReadableTypeId::of::<T>()) + } +} + +impl Debug for KeyId { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + /// A scoped property barrier. /// /// Barriers interact with [scoped](super::StyleMap::scoped) styles: A scoped diff --git a/src/model/realize.rs b/src/model/realize.rs index 69bae91f..a99abdd3 100644 --- a/src/model/realize.rs +++ b/src/model/realize.rs @@ -3,15 +3,20 @@ use std::mem; use comemo::Tracked; use typed_arena::Arena; +use super::collapse::CollapsingBuilder; use super::{ - Barrier, CollapsingBuilder, Content, Interruption, Layout, ShowNode, StyleChain, - StyleEntry, StyleMap, StyleVecBuilder, Target, + Barrier, Content, Interruption, Layout, Level, Node, SequenceNode, Show, StyleChain, + StyleEntry, StyleMap, StyleVecBuilder, StyledNode, Target, }; use crate::diag::SourceResult; use crate::geom::Numeric; -use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode}; +use crate::library::layout::{ + ColbreakNode, FlowChild, FlowNode, HNode, PageNode, PagebreakNode, PlaceNode, VNode, +}; use crate::library::structure::{DocNode, ListItem, ListNode, DESC, ENUM, LIST}; -use crate::library::text::{ParChild, ParNode}; +use crate::library::text::{ + LinebreakNode, ParChild, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode, +}; use crate::World; /// Builds a document or a flow node from content. @@ -78,20 +83,19 @@ impl<'a> Builder<'a> { content: &'a Content, styles: StyleChain<'a>, ) -> SourceResult<()> { - match content { - Content::Empty => return Ok(()), - Content::Text(text) => { - if let Some(realized) = styles.apply(self.world, Target::Text(text))? { - let stored = self.scratch.templates.alloc(realized); - return self.accept(stored, styles); - } + if let Some(node) = content.downcast::<TextNode>() { + if let Some(realized) = styles.apply(self.world, Target::Text(&node.0))? { + let stored = self.scratch.templates.alloc(realized); + return self.accept(stored, styles); + } + } else if let Some(styled) = content.downcast::<StyledNode>() { + return self.styled(styled, styles); + } else if let Some(seq) = content.downcast::<SequenceNode>() { + return self.sequence(seq, styles); + } else if content.has::<dyn Show>() { + if self.show(&content, styles)? { + return Ok(()); } - - Content::Show(node) => return self.show(node, styles), - Content::Styled(styled) => return self.styled(styled, styles), - Content::Sequence(seq) => return self.sequence(seq, styles), - - _ => {} } if self.list.accept(content, styles) { @@ -100,7 +104,7 @@ impl<'a> Builder<'a> { self.interrupt(Interruption::List, styles, false)?; - if let Content::Item(_) = content { + if content.is::<ListItem>() { self.list.accept(content, styles); return Ok(()); } @@ -115,7 +119,7 @@ impl<'a> Builder<'a> { return Ok(()); } - let keep = matches!(content, Content::Pagebreak { weak: false }); + let keep = content.downcast::<PagebreakNode>().map_or(false, |node| !node.weak); self.interrupt(Interruption::Page, styles, keep)?; if let Some(doc) = &mut self.doc { @@ -128,7 +132,7 @@ impl<'a> Builder<'a> { Ok(()) } - fn show(&mut self, node: &ShowNode, styles: StyleChain<'a>) -> SourceResult<()> { + fn show(&mut self, node: &'a Content, styles: StyleChain<'a>) -> SourceResult<bool> { if let Some(mut realized) = styles.apply(self.world, Target::Node(node))? { let mut map = StyleMap::new(); let barrier = Barrier::new(node.id()); @@ -137,24 +141,26 @@ impl<'a> Builder<'a> { realized = realized.styled_with_map(map); let stored = self.scratch.templates.alloc(realized); self.accept(stored, styles)?; + Ok(true) + } else { + Ok(false) } - Ok(()) } fn styled( &mut self, - (content, map): &'a (Content, StyleMap), + styled: &'a StyledNode, styles: StyleChain<'a>, ) -> SourceResult<()> { let stored = self.scratch.styles.alloc(styles); - let styles = map.chain(stored); - let intr = map.interruption(); + let styles = styled.map.chain(stored); + let intr = styled.map.interruption(); if let Some(intr) = intr { self.interrupt(intr, styles, false)?; } - self.accept(content, styles)?; + self.accept(&styled.sub, styles)?; if let Some(intr) = intr { self.interrupt(intr, styles, true)?; @@ -193,10 +199,10 @@ impl<'a> Builder<'a> { fn sequence( &mut self, - seq: &'a [Content], + seq: &'a SequenceNode, styles: StyleChain<'a>, ) -> SourceResult<()> { - for content in seq { + for content in &seq.0 { self.accept(content, styles)?; } Ok(()) @@ -213,15 +219,13 @@ struct DocBuilder<'a> { impl<'a> DocBuilder<'a> { fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) { - match content { - Content::Pagebreak { weak } => { - self.keep_next = !weak; - } - Content::Page(page) => { - self.pages.push(page.clone(), styles); - self.keep_next = false; - } - _ => {} + if let Some(pagebreak) = content.downcast::<PagebreakNode>() { + self.keep_next = !pagebreak.weak; + } + + if let Some(page) = content.downcast::<PageNode>() { + self.pages.push(page.clone(), styles); + self.keep_next = false; } } } @@ -250,36 +254,34 @@ impl<'a> FlowBuilder<'a> { // 4 | generated weak fractional spacing // 5 | par spacing - match content { - Content::Parbreak => {} - Content::Colbreak { weak } => { - if *weak { - self.0.weak(FlowChild::Colbreak, styles, 0); - } else { - self.0.destructive(FlowChild::Colbreak, styles); - } + if let Some(_) = content.downcast::<ParbreakNode>() { + /* Nothing to do */ + } else if let Some(colbreak) = content.downcast::<ColbreakNode>() { + if colbreak.weak { + self.0.weak(FlowChild::Colbreak, styles, 0); + } else { + self.0.destructive(FlowChild::Colbreak, styles); } - &Content::Vertical { amount, weak, generated } => { - let child = FlowChild::Spacing(amount); - let frac = amount.is_fractional(); - if weak { - let weakness = 1 + u8::from(frac) + 2 * u8::from(generated); - self.0.weak(child, styles, weakness); - } else if frac { - self.0.destructive(child, styles); - } else { - self.0.ignorant(child, styles); - } + } else if let Some(vertical) = content.downcast::<VNode>() { + let child = FlowChild::Spacing(vertical.amount); + let frac = vertical.amount.is_fractional(); + if vertical.weak { + let weakness = 1 + u8::from(frac) + 2 * u8::from(vertical.generated); + self.0.weak(child, styles, weakness); + } else if frac { + self.0.destructive(child, styles); + } else { + self.0.ignorant(child, styles); } - Content::Block(node) => { - let child = FlowChild::Node(node.clone()); - if node.is::<PlaceNode>() { - self.0.ignorant(child, styles); - } else { - self.0.supportive(child, styles); - } + } else if content.has::<dyn Layout>() { + let child = FlowChild::Node(content.clone()); + if content.is::<PlaceNode>() { + self.0.ignorant(child, styles); + } else { + self.0.supportive(child, styles); } - _ => return false, + } else { + return false; } true @@ -321,36 +323,34 @@ impl<'a> ParBuilder<'a> { // 1 | weak spacing // 2 | space - match content { - Content::Space => { - self.0.weak(ParChild::Text(' '.into()), styles, 2); - } - &Content::Linebreak { justify } => { - let c = if justify { '\u{2028}' } else { '\n' }; - self.0.destructive(ParChild::Text(c.into()), styles); - } - &Content::Horizontal { amount, weak } => { - let child = ParChild::Spacing(amount); - let frac = amount.is_fractional(); - if weak { - let weakness = u8::from(!frac); - self.0.weak(child, styles, weakness); - } else if frac { - self.0.destructive(child, styles); - } else { - self.0.ignorant(child, styles); - } - } - &Content::Quote { double } => { - self.0.supportive(ParChild::Quote { double }, styles); + if content.is::<SpaceNode>() { + self.0.weak(ParChild::Text(' '.into()), styles, 2); + } else if let Some(linebreak) = content.downcast::<LinebreakNode>() { + let c = if linebreak.justify { '\u{2028}' } else { '\n' }; + self.0.destructive(ParChild::Text(c.into()), styles); + } else if let Some(horizontal) = content.downcast::<HNode>() { + let child = ParChild::Spacing(horizontal.amount); + let frac = horizontal.amount.is_fractional(); + if horizontal.weak { + let weakness = u8::from(!frac); + self.0.weak(child, styles, weakness); + } else if frac { + self.0.destructive(child, styles); + } else { + self.0.ignorant(child, styles); } - Content::Text(text) => { - self.0.supportive(ParChild::Text(text.clone()), styles); + } else if let Some(quote) = content.downcast::<SmartQuoteNode>() { + self.0.supportive(ParChild::Quote { double: quote.double }, styles); + } else if let Some(node) = content.downcast::<TextNode>() { + self.0.supportive(ParChild::Text(node.0.clone()), styles); + } else if let Some(node) = content.to::<dyn Layout>() { + if node.level() == Level::Inline { + self.0.supportive(ParChild::Node(content.clone()), styles); + } else { + return false; } - Content::Inline(node) => { - self.0.supportive(ParChild::Node(node.clone()), styles); - } - _ => return false, + } else { + return false; } true @@ -412,29 +412,31 @@ struct ListBuilder<'a> { impl<'a> ListBuilder<'a> { fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { if self.items.is_empty() { - match content { - Content::Space => {} - Content::Item(_) => {} - Content::Parbreak => self.attachable = false, - _ => self.attachable = true, + if content.is::<ParbreakNode>() { + self.attachable = false; + } else if !content.is::<SpaceNode>() && !content.is::<ListItem>() { + self.attachable = true; } } - match content { - Content::Item(item) - if self - .items - .items() - .next() - .map_or(true, |first| item.kind() == first.kind()) => + if let Some(item) = content.downcast::<ListItem>() { + if self + .items + .items() + .next() + .map_or(true, |first| item.kind() == first.kind()) { self.items.push(item.clone(), styles); - self.tight &= self.staged.drain(..).all(|(t, _)| *t != Content::Parbreak); + self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakNode>()); + } else { + return false; } - Content::Space | Content::Parbreak if !self.items.is_empty() => { - self.staged.push((content, styles)); - } - _ => return false, + } else if !self.items.is_empty() + && (content.is::<SpaceNode>() || content.is::<ParbreakNode>()) + { + self.staged.push((content, styles)); + } else { + return false; } true @@ -450,9 +452,9 @@ impl<'a> ListBuilder<'a> { let tight = self.tight; let attached = tight && self.attachable; let content = match kind { - LIST => Content::show(ListNode::<LIST> { tight, attached, items }), - ENUM => Content::show(ListNode::<ENUM> { tight, attached, items }), - DESC | _ => Content::show(ListNode::<DESC> { tight, attached, items }), + LIST => ListNode::<LIST> { tight, attached, items }.pack(), + ENUM => ListNode::<ENUM> { tight, attached, items }.pack(), + DESC | _ => ListNode::<DESC> { tight, attached, items }.pack(), }; let stored = parent.scratch.templates.alloc(content); diff --git a/src/model/recipe.rs b/src/model/recipe.rs index 8124d441..c687f4fb 100644 --- a/src/model/recipe.rs +++ b/src/model/recipe.rs @@ -1,12 +1,15 @@ use std::fmt::{self, Debug, Formatter}; +use std::hash::Hash; use comemo::Tracked; use super::{ - Args, Content, Func, Interruption, NodeId, Regex, Show, ShowNode, StyleEntry, Value, + Args, Capability, Content, Func, Interruption, Node, NodeId, Regex, StyleChain, + StyleEntry, Value, }; use crate::diag::SourceResult; use crate::library::structure::{DescNode, EnumNode, ListNode}; +use crate::library::text::TextNode; use crate::syntax::Spanned; use crate::World; @@ -38,8 +41,8 @@ impl Recipe { ) -> SourceResult<Option<Content>> { let content = match (target, &self.pattern) { (Target::Node(node), &Pattern::Node(id)) if node.id() == id => { - let node = node.unguard(sel); - self.call(world, || Value::Content(Content::Show(node)))? + let node = node.to::<dyn Show>().unwrap().unguard_parts(sel); + self.call(world, || Value::Content(node))? } (Target::Text(text), Pattern::Regex(regex)) => { @@ -49,7 +52,7 @@ impl Recipe { for mat in regex.find_iter(text) { let start = mat.start(); if cursor < start { - result.push(Content::Text(text[cursor .. start].into())); + result.push(TextNode(text[cursor .. start].into()).pack()); } result.push(self.call(world, || Value::Str(mat.as_str().into()))?); @@ -61,7 +64,7 @@ impl Recipe { } if cursor < text.len() { - result.push(Content::Text(text[cursor ..].into())); + result.push(TextNode(text[cursor ..].into()).pack()); } Content::sequence(result) @@ -132,7 +135,7 @@ impl Pattern { #[derive(Debug, Copy, Clone, PartialEq)] pub enum Target<'a> { /// A showable node. - Node(&'a ShowNode), + Node(&'a Content), /// A slice of text. Text(&'a str), } @@ -145,3 +148,38 @@ pub enum Selector { /// The base recipe for a kind of node. Base(NodeId), } + +/// A node that can be realized given some styles. +pub trait Show: 'static + Sync + Send { + /// Unguard nested content against recursive show rules. + fn unguard_parts(&self, sel: Selector) -> Content; + + /// Access a field on this node. + fn field(&self, name: &str) -> Option<Value>; + + /// The base recipe for this node that is executed if there is no + /// user-defined show rule. + fn realize( + &self, + world: Tracked<dyn World>, + styles: StyleChain, + ) -> SourceResult<Content>; + + /// Finalize this node given the realization of a base or user recipe. Use + /// this for effects that should work even in the face of a user-defined + /// show rule, for example: + /// - Application of general settable properties + /// + /// Defaults to just the realized content. + #[allow(unused_variables)] + fn finalize( + &self, + world: Tracked<dyn World>, + styles: StyleChain, + realized: Content, + ) -> SourceResult<Content> { + Ok(realized) + } +} + +impl Capability for dyn Show {} diff --git a/src/model/show.rs b/src/model/show.rs deleted file mode 100644 index cc84e6db..00000000 --- a/src/model/show.rs +++ /dev/null @@ -1,127 +0,0 @@ -use std::fmt::{self, Debug, Formatter, Write}; -use std::hash::Hash; -use std::sync::Arc; - -use comemo::{Prehashed, Tracked}; - -use super::{Content, NodeId, Selector, StyleChain, Value}; -use crate::diag::SourceResult; -use crate::World; - -/// A node that can be realized given some styles. -pub trait Show: 'static { - /// Unguard nested content against recursive show rules. - fn unguard(&self, sel: Selector) -> ShowNode; - - /// Access a field on this node. - fn field(&self, name: &str) -> Option<Value>; - - /// The base recipe for this node that is executed if there is no - /// user-defined show rule. - fn realize( - &self, - world: Tracked<dyn World>, - styles: StyleChain, - ) -> SourceResult<Content>; - - /// Finalize this node given the realization of a base or user recipe. Use - /// this for effects that should work even in the face of a user-defined - /// show rule, for example: - /// - Application of general settable properties - /// - /// Defaults to just the realized content. - #[allow(unused_variables)] - fn finalize( - &self, - world: Tracked<dyn World>, - styles: StyleChain, - realized: Content, - ) -> SourceResult<Content> { - Ok(realized) - } - - /// Convert to a packed show node. - fn pack(self) -> ShowNode - where - Self: Debug + Hash + Sized + Sync + Send + 'static, - { - ShowNode::new(self) - } -} - -/// A type-erased showable node with a precomputed hash. -#[derive(Clone, Hash)] -pub struct ShowNode(Arc<Prehashed<dyn Bounds>>); - -impl ShowNode { - /// Pack any showable node. - pub fn new<T>(node: T) -> Self - where - T: Show + Debug + Hash + Sync + Send + 'static, - { - Self(Arc::new(Prehashed::new(node))) - } - - /// The id of this node. - pub fn id(&self) -> NodeId { - (**self.0).node_id() - } -} - -impl Show for ShowNode { - fn unguard(&self, sel: Selector) -> ShowNode { - self.0.unguard(sel) - } - - fn field(&self, name: &str) -> Option<Value> { - self.0.field(name) - } - - fn realize( - &self, - world: Tracked<dyn World>, - styles: StyleChain, - ) -> SourceResult<Content> { - self.0.realize(world, styles) - } - - fn finalize( - &self, - world: Tracked<dyn World>, - styles: StyleChain, - realized: Content, - ) -> SourceResult<Content> { - self.0.finalize(world, styles, realized) - } - - fn pack(self) -> ShowNode { - self - } -} - -impl Debug for ShowNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("Show(")?; - self.0.fmt(f)?; - f.write_char(')') - } -} - -impl PartialEq for ShowNode { - fn eq(&self, other: &Self) -> bool { - self.0.eq(&other.0) - } -} - -trait Bounds: Show + Debug + Sync + Send + 'static { - fn node_id(&self) -> NodeId; -} - -impl<T> Bounds for T -where - T: Show + Debug + Hash + Sync + Send + 'static, -{ - fn node_id(&self) -> NodeId { - NodeId::of::<Self>() - } -} diff --git a/src/model/styles.rs b/src/model/styles.rs index f3bd8c4f..d160a4a6 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -7,7 +7,6 @@ use comemo::Tracked; use super::{Barrier, Content, Key, Property, Recipe, Selector, Show, Target}; use crate::diag::SourceResult; -use crate::util::ReadableTypeId; use crate::World; /// A map of style properties. @@ -98,7 +97,7 @@ impl StyleMap { pub fn scoped(mut self) -> Self { for entry in &mut self.0 { if let StyleEntry::Property(property) = entry { - property.scoped = true; + property.make_scoped(); } } self @@ -125,23 +124,6 @@ impl Debug for StyleMap { } } -/// 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) - } -} - /// Determines whether a style could interrupt some composable structure. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] pub enum Interruption { @@ -175,7 +157,7 @@ impl StyleEntry { if !tail .entries() .filter_map(StyleEntry::property) - .any(|p| p.scoped && barrier.is_for(p.node)) + .any(|p| p.scoped() && barrier.is_for(p.node())) { return *tail; } @@ -302,7 +284,13 @@ impl<'a> StyleChain<'a> { if self.guarded(sel) { guarded = true; } else { - let content = node.unguard(sel).realize(world, self)?; + let content = node + .to::<dyn Show>() + .unwrap() + .unguard_parts(sel) + .to::<dyn Show>() + .unwrap() + .realize(world, self)?; realized = Some(content.styled_with_entry(StyleEntry::Guard(sel))); } } @@ -310,7 +298,9 @@ impl<'a> StyleChain<'a> { // Finalize only if guarding didn't stop any recipe. if !guarded { if let Some(content) = realized { - realized = Some(node.finalize(world, self, content)?); + realized = Some( + node.to::<dyn Show>().unwrap().finalize(world, self, content)?, + ); } } } @@ -403,7 +393,7 @@ impl<'a, K: Key<'a>> Iterator for Values<'a, K> { match entry { StyleEntry::Property(property) => { if let Some(value) = property.downcast::<K>() { - if !property.scoped || self.depth <= 1 { + if !property.scoped() || self.depth <= 1 { return Some(value); } } diff --git a/src/model/value.rs b/src/model/value.rs index 1782e4a6..4741401c 100644 --- a/src/model/value.rs +++ b/src/model/value.rs @@ -7,9 +7,10 @@ use std::sync::Arc; use comemo::Tracked; use siphasher::sip128::{Hasher128, SipHasher}; -use super::{ops, Args, Array, Cast, Content, Dict, Func, Layout, Str}; +use super::{ops, Args, Array, Cast, Content, Dict, Func, Node, Str}; use crate::diag::StrResult; use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor}; +use crate::library::text::TextNode; use crate::util::EcoString; use crate::World; @@ -55,22 +56,6 @@ pub enum Value { } impl Value { - /// Create a content value from an inline-level node. - pub fn inline<T>(node: T) -> Self - where - T: Layout + Debug + Hash + Sync + Send + 'static, - { - Self::Content(Content::inline(node)) - } - - /// Create a content value from a block-level node. - pub fn block<T>(node: T) -> Self - where - T: Layout + Debug + Hash + Sync + Send + 'static, - { - Self::Content(Content::block(node)) - } - /// Create a new dynamic value. pub fn dynamic<T>(any: T) -> Self where @@ -115,16 +100,17 @@ impl Value { /// Return the display representation of the value. pub fn display(self, world: Tracked<dyn World>) -> Content { + let items = &world.config().items; match self { - Value::None => Content::new(), - Value::Int(v) => Content::Text(format_eco!("{}", v)), - Value::Float(v) => Content::Text(format_eco!("{}", v)), - Value::Str(v) => Content::Text(v.into()), + Value::None => Content::empty(), + Value::Int(v) => (items.text)(format_eco!("{}", v)), + Value::Float(v) => (items.text)(format_eco!("{}", v)), + Value::Str(v) => (items.text)(v.into()), Value::Content(v) => v, // For values which can't be shown "naturally", we return the raw // representation with typst code syntax highlighting. - v => (world.config().items.raw)(v.repr().into(), Some("typc".into()), false), + v => (items.raw)(v.repr().into(), Some("typc".into()), false), } } } @@ -398,8 +384,8 @@ primitive! { Color: "color", Color } primitive! { Str: "string", Str } primitive! { Content: "content", Content, - None => Content::new(), - Str(text) => Content::Text(text.into()) + None => Content::empty(), + Str(text) => TextNode(text.into()).pack() } primitive! { Array: "array", Array } primitive! { Dict: "dictionary", Dict } @@ -448,7 +434,6 @@ mod tests { test(dict!["two" => false, "one" => 1], "(one: 1, two: false)"); // Functions, content and dynamics. - test(Content::Text("a".into()), "[...]"); test(Func::from_fn("nil", |_, _| Ok(Value::None)), "nil"); test(Dynamic::new(1), "1"); } diff --git a/src/model/vm.rs b/src/model/vm.rs index 6207ffd5..4de57d1c 100644 --- a/src/model/vm.rs +++ b/src/model/vm.rs @@ -2,10 +2,10 @@ use std::path::PathBuf; use comemo::Tracked; -use super::{Route, Scopes, Value}; +use super::{Content, Route, Scopes, Value}; use crate::diag::{SourceError, StrResult}; use crate::syntax::{SourceId, Span}; -use crate::util::PathExt; +use crate::util::{EcoString, PathExt}; use crate::{LangItems, World}; /// A virtual machine. @@ -59,6 +59,13 @@ impl<'a> Vm<'a> { pub fn items(&self) -> &LangItems { &self.world.config().items } + + /// Create text content. + /// + /// This is a shorthand for `(vm.items().text)(..)`. + pub fn text(&self, text: impl Into<EcoString>) -> Content { + (self.items().text)(text.into()) + } } /// A control flow event that occurred during evaluation. |
