summaryrefslogtreecommitdiff
path: root/src/model/content.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-11-22 20:12:37 +0100
committerLaurenz <laurmaedje@gmail.com>2022-11-22 20:12:37 +0100
commit5992f11b4c33d82fa245205980094accb57a8c76 (patch)
treeb0043ba4b4cabab2fa8f63ec80f37c9d247e134a /src/model/content.rs
parentb476de87b7cea1405bf3c051ff8e0ac7c473dbae (diff)
Reorganize content type
Diffstat (limited to 'src/model/content.rs')
-rw-r--r--src/model/content.rs366
1 files changed, 185 insertions, 181 deletions
diff --git a/src/model/content.rs b/src/model/content.rs
index d72e4e19..9d78020c 100644
--- a/src/model/content.rs
+++ b/src/model/content.rs
@@ -9,19 +9,22 @@ use comemo::Tracked;
use siphasher::sip128::{Hasher128, SipHasher};
use typst_macros::node;
-use super::{Args, Key, Property, Recipe, RecipeId, Style, StyleMap, Value, Vm};
+use super::{
+ Args, Key, Property, Recipe, RecipeId, Style, StyleMap, Unlabellable, Value, Vm,
+};
use crate::diag::{SourceResult, StrResult};
use crate::syntax::Span;
use crate::util::{EcoString, ReadableTypeId};
use crate::World;
/// Composable representation of styled content.
-///
-/// This results from:
-/// - anything written between square brackets in Typst
-/// - any constructor function
#[derive(Clone, Hash)]
-pub struct Content(Arc<dyn Bounds>, Vec<RecipeId>, Option<Span>, Option<EcoString>);
+pub struct Content {
+ obj: Arc<dyn Bounds>,
+ guards: Vec<RecipeId>,
+ span: Option<Span>,
+ label: Option<EcoString>,
+}
impl Content {
/// Create empty content.
@@ -37,99 +40,38 @@ impl Content {
}
}
- /// Whether the content is empty.
- pub fn is_empty(&self) -> bool {
- self.downcast::<SequenceNode>().map_or(false, |seq| seq.0.is_empty())
- }
-
- /// The node's human-readable name.
- pub fn name(&self) -> &'static str {
- (*self.0).name()
- }
-
- /// The id of the contained node.
- pub fn id(&self) -> NodeId {
- (*self.0).id()
- }
-
- /// Whether the contained node is of type `T`.
- pub fn is<T: 'static>(&self) -> bool {
- (*self.0).as_any().is::<T>()
- }
-
- /// Cast to `T` if the contained node is of type `T`.
- pub fn downcast<T: 'static>(&self) -> Option<&T> {
- (*self.0).as_any().downcast_ref::<T>()
- }
-
- /// Try to cast to a mutable instance of `T`.
- fn try_downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
- Arc::get_mut(&mut self.0)?.as_any_mut().downcast_mut::<T>()
- }
-
- /// Access a field on this content.
- pub fn field(&self, name: &str) -> Option<Value> {
- if name == "label" {
- return Some(match &self.3 {
- Some(label) => Value::Str(label.clone().into()),
- None => Value::None,
- });
+ /// Attach a span to the content.
+ pub fn spanned(mut self, span: Span) -> Self {
+ if let Some(styled) = self.to_mut::<StyledNode>() {
+ styled.sub.span = Some(span);
+ } else if let Some(styled) = self.to::<StyledNode>() {
+ self = StyledNode {
+ sub: styled.sub.clone().spanned(span),
+ map: styled.map.clone(),
+ }
+ .pack();
}
-
- self.0.field(name)
- }
-
- /// Whether this content has the given capability.
- pub fn has<C>(&self) -> bool
- where
- C: Capability + ?Sized,
- {
- self.0.vtable(TypeId::of::<C>()).is_some()
- }
-
- /// 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) })
+ self.span = Some(span);
+ self
}
- /// Repeat this content `n` times.
- pub fn repeat(&self, n: i64) -> StrResult<Self> {
- let count = usize::try_from(n)
- .map_err(|_| format!("cannot repeat this content {} times", n))?;
-
- Ok(Self::sequence(vec![self.clone(); count]))
+ /// Attach a label to the content.
+ pub fn labelled(mut self, label: EcoString) -> Self {
+ self.label = Some(label);
+ self
}
/// Style this content with a single style property.
- pub fn styled<'k, K: Key<'k>>(self, key: K, value: K::Value) -> Self {
+ pub fn styled<K: Key>(self, key: K, value: K::Value) -> Self {
self.styled_with_entry(Style::Property(Property::new(key, value)))
}
- /// Style this content with a recipe, eagerly applying it if possible.
- pub fn styled_with_recipe(
- self,
- world: Tracked<dyn World>,
- recipe: Recipe,
- ) -> SourceResult<Self> {
- if recipe.selector.is_none() {
- recipe.transform.apply(world, recipe.span, self)
- } else {
- Ok(self.styled_with_entry(Style::Recipe(recipe)))
- }
- }
-
/// Style this content with a style entry.
pub fn styled_with_entry(mut self, style: Style) -> Self {
- if let Some(styled) = self.try_downcast_mut::<StyledNode>() {
+ if let Some(styled) = self.to_mut::<StyledNode>() {
styled.map.apply(style);
self
- } else if let Some(styled) = self.downcast::<StyledNode>() {
+ } else if let Some(styled) = self.to::<StyledNode>() {
let mut map = styled.map.clone();
map.apply(style);
StyledNode { sub: styled.sub.clone(), map }.pack()
@@ -144,7 +86,7 @@ impl Content {
return self;
}
- if let Some(styled) = self.try_downcast_mut::<StyledNode>() {
+ if let Some(styled) = self.to_mut::<StyledNode>() {
styled.map.apply_map(&styles);
return self;
}
@@ -152,86 +94,140 @@ impl Content {
StyledNode { sub: self, map: styles }.pack()
}
- /// Disable a show rule recipe.
- pub fn guard(mut self, id: RecipeId) -> Self {
- self.1.push(id);
- self
+ /// Style this content with a recipe, eagerly applying it if possible.
+ pub fn styled_with_recipe(
+ self,
+ world: Tracked<dyn World>,
+ recipe: Recipe,
+ ) -> SourceResult<Self> {
+ if recipe.selector.is_none() {
+ recipe.transform.apply(world, recipe.span, self)
+ } else {
+ Ok(self.styled_with_entry(Style::Recipe(recipe)))
+ }
}
- /// Whether no show rule was executed for this node so far.
- pub fn pristine(&self) -> bool {
- self.1.is_empty()
+ /// Repeat this content `n` times.
+ pub fn repeat(&self, n: i64) -> StrResult<Self> {
+ let count = usize::try_from(n)
+ .map_err(|_| format!("cannot repeat this content {} times", n))?;
+
+ Ok(Self::sequence(vec![self.clone(); count]))
}
+}
- /// Check whether a show rule recipe is disabled.
- pub fn guarded(&self, id: RecipeId) -> bool {
- self.1.contains(&id)
+impl Content {
+ /// The id of the contained node.
+ pub fn id(&self) -> NodeId {
+ (*self.obj).id()
+ }
+
+ /// The node's human-readable name.
+ pub fn name(&self) -> &'static str {
+ (*self.obj).name()
}
/// The node's span.
pub fn span(&self) -> Option<Span> {
- self.2
+ self.span
}
- /// Set the content's span.
- pub fn set_span(&mut self, span: Span) {
- if let Some(styled) = self.try_downcast_mut::<StyledNode>() {
- styled.sub.2 = Some(span);
- } else if let Some(styled) = self.downcast::<StyledNode>() {
- *self = StyledNode {
- sub: styled.sub.clone().spanned(span),
- map: styled.map.clone(),
- }
- .pack();
+ /// The content's label.
+ pub fn label(&self) -> Option<&EcoString> {
+ self.label.as_ref()
+ }
+
+ /// Access a field on this content.
+ pub fn field(&self, name: &str) -> Option<Value> {
+ if name == "label" {
+ return Some(match &self.label {
+ Some(label) => Value::Str(label.clone().into()),
+ None => Value::None,
+ });
}
- self.2 = Some(span);
+
+ self.obj.field(name)
}
- /// Attach a span to the content.
- pub fn spanned(mut self, span: Span) -> Self {
- self.set_span(span);
- self
+ /// Whether the contained node is of type `T`.
+ pub fn is<T: 'static>(&self) -> bool {
+ (*self.obj).as_any().is::<T>()
}
- /// The content's label.
- pub fn label(&self) -> Option<&EcoString> {
- self.3.as_ref()
+ /// Cast to `T` if the contained node is of type `T`.
+ pub fn to<T: 'static>(&self) -> Option<&T> {
+ (*self.obj).as_any().downcast_ref::<T>()
}
- /// Set the content's label.
- pub fn set_label(&mut self, label: EcoString) {
- self.3 = Some(label);
+ /// Whether this content has the given capability.
+ pub fn has<C>(&self) -> bool
+ where
+ C: Capability + ?Sized,
+ {
+ self.obj.vtable(TypeId::of::<C>()).is_some()
}
- /// Attacha label to the content.
- pub fn labelled(mut self, label: EcoString) -> Self {
- self.set_label(label);
+ /// Cast to a trait object if this content has the given capability.
+ pub fn with<C>(&self) -> Option<&C>
+ where
+ C: Capability + ?Sized,
+ {
+ let node: &dyn Bounds = &*self.obj;
+ 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) })
+ }
+
+ /// Try to cast to a mutable instance of `T`.
+ fn to_mut<T: 'static>(&mut self) -> Option<&mut T> {
+ Arc::get_mut(&mut self.obj)?.as_any_mut().downcast_mut::<T>()
+ }
+
+ /// Disable a show rule recipe.
+ #[doc(hidden)]
+ pub fn guarded(mut self, id: RecipeId) -> Self {
+ self.guards.push(id);
self
}
- /// Copy the metadata from other content.
- pub fn copy_meta(&mut self, from: &Content) {
- self.1 = from.1.clone();
- self.2 = from.2;
- self.3 = from.3.clone();
+ /// Whether a label can be attached to the content.
+ pub(super) fn labellable(&self) -> bool {
+ !self.has::<dyn Unlabellable>()
}
-}
-impl Default for Content {
- fn default() -> Self {
- Self::empty()
+ /// Whether no show rule was executed for this node so far.
+ pub(super) fn is_pristine(&self) -> bool {
+ self.guards.is_empty()
+ }
+
+ /// Check whether a show rule recipe is disabled.
+ pub(super) fn is_guarded(&self, id: RecipeId) -> bool {
+ self.guards.contains(&id)
+ }
+
+ /// Copy the metadata from other content.
+ pub(super) fn copy_meta(&mut self, from: &Content) {
+ self.guards = from.guards.clone();
+ self.span = from.span;
+ self.label = from.label.clone();
}
}
impl Debug for Content {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- self.0.fmt(f)
+ self.obj.fmt(f)
+ }
+}
+
+impl Default for Content {
+ fn default() -> Self {
+ Self::empty()
}
}
impl PartialEq for Content {
fn eq(&self, other: &Self) -> bool {
- (*self.0).hash128() == (*other.0).hash128()
+ (*self.obj).hash128() == (*other.obj).hash128()
}
}
@@ -240,10 +236,10 @@ impl Add for Content {
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>() {
+ if let Some(lhs_mut) = lhs.to_mut::<SequenceNode>() {
+ if let Some(rhs_mut) = rhs.to_mut::<SequenceNode>() {
lhs_mut.0.append(&mut rhs_mut.0);
- } else if let Some(rhs) = rhs.downcast::<SequenceNode>() {
+ } else if let Some(rhs) = rhs.to::<SequenceNode>() {
lhs_mut.0.extend(rhs.0.iter().cloned());
} else {
lhs_mut.0.push(rhs);
@@ -251,7 +247,7 @@ impl Add for Content {
return lhs;
}
- let seq = match (lhs.downcast::<SequenceNode>(), rhs.downcast::<SequenceNode>()) {
+ let seq = match (lhs.to::<SequenceNode>(), rhs.to::<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(),
@@ -306,14 +302,54 @@ impl Hash for dyn Bounds {
}
}
+/// A node with applied styles.
+#[derive(Clone, Hash)]
+pub struct StyledNode {
+ /// The styled content.
+ pub sub: Content,
+ /// The styles.
+ 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()
+ }
+}
+
/// A constructable, stylable content node.
-pub trait Node: 'static {
- /// Pack into type-erased content.
+pub trait Node: 'static + Capable {
+ /// Pack a node into type-erased content.
fn pack(self) -> Content
where
- Self: Debug + Hash + Sync + Send + Sized + 'static,
+ Self: Node + Debug + Hash + Sync + Send + Sized + 'static,
{
- Content(Arc::new(self), vec![], None, None)
+ Content {
+ obj: Arc::new(self),
+ guards: vec![],
+ span: None,
+ label: None,
+ }
}
/// A unique identifier of the node type.
@@ -340,17 +376,8 @@ pub trait Node: 'static {
/// Access a field on this node.
fn field(&self, name: &str) -> Option<Value>;
-
- /// 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 capability a node can have.
-///
-/// This is implemented by trait objects.
-pub trait Capability: 'static + Send + Sync {}
-
/// A unique identifier for a node.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct NodeId(ReadableTypeId);
@@ -368,37 +395,14 @@ impl Debug for NodeId {
}
}
-/// A node with applied styles.
-#[derive(Clone, Hash)]
-pub struct StyledNode {
- /// The styled content.
- pub sub: Content,
- /// The styles.
- 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.
+/// A capability a node can have.
///
-/// 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 {}
+/// This is implemented by trait objects.
+pub trait Capability: 'static {}
-impl Debug for SequenceNode {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.debug_list().entries(self.0.iter()).finish()
- }
+/// Dynamically access a trait implementation at runtime.
+pub unsafe trait Capable {
+ /// Return the vtable pointer of the trait object with given type `id`
+ /// if `self` implements the trait.
+ fn vtable(&self, of: TypeId) -> Option<*const ()>;
}