summaryrefslogtreecommitdiff
path: root/src/model
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-11-01 16:56:35 +0100
committerLaurenz <laurmaedje@gmail.com>2022-11-02 09:18:33 +0100
commit37ac5d966ebaf97ac79c507028cd5b742b510b89 (patch)
tree249d43ff0f8d880cb5d00c236993f8ff0c1f10d8 /src/model
parentf547c97072881069417acd3b79b08fb7ecf40ba2 (diff)
More dynamic content representation
Diffstat (limited to 'src/model')
-rw-r--r--src/model/cast.rs10
-rw-r--r--src/model/collapse.rs2
-rw-r--r--src/model/content.rs421
-rw-r--r--src/model/eval.rs113
-rw-r--r--src/model/func.rs24
-rw-r--r--src/model/layout.rs255
-rw-r--r--src/model/methods.rs1
-rw-r--r--src/model/mod.rs4
-rw-r--r--src/model/ops.rs11
-rw-r--r--src/model/property.rs55
-rw-r--r--src/model/realize.rs224
-rw-r--r--src/model/recipe.rs50
-rw-r--r--src/model/show.rs127
-rw-r--r--src/model/styles.rs36
-rw-r--r--src/model/value.rs35
-rw-r--r--src/model/vm.rs11
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.