summaryrefslogtreecommitdiff
path: root/src/model
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-03-19 22:28:49 +0100
committerLaurenz <laurmaedje@gmail.com>2023-03-19 22:39:19 +0100
commitab43bd802eafe33977a91893907e67553e099569 (patch)
treeaf4dead92b143348f52e2e8f869df3f7dfd7322a /src/model
parentd6aaae0cea1e79eecd85dc94ab85b9ad8eff48e8 (diff)
Renaming and refactoring
Diffstat (limited to 'src/model')
-rw-r--r--src/model/content.rs538
-rw-r--r--src/model/element.rs145
-rw-r--r--src/model/introspect.rs170
-rw-r--r--src/model/mod.rs83
-rw-r--r--src/model/realize.rs96
-rw-r--r--src/model/styles.rs271
-rw-r--r--src/model/typeset.rs241
7 files changed, 846 insertions, 698 deletions
diff --git a/src/model/content.rs b/src/model/content.rs
index 5317236e..b47da62c 100644
--- a/src/model/content.rs
+++ b/src/model/content.rs
@@ -1,160 +1,144 @@
use std::any::TypeId;
use std::fmt::{self, Debug, Formatter, Write};
-use std::hash::{Hash, Hasher};
-use std::iter::{self, Sum};
-use std::ops::{Add, AddAssign, Deref};
+use std::iter::Sum;
+use std::ops::{Add, AddAssign};
use ecow::{eco_format, EcoString, EcoVec};
-use once_cell::sync::Lazy;
use super::{
- node, Behave, Behaviour, Fold, Guard, Locatable, Recipe, StableId, Style, StyleMap,
- Synthesize,
+ element, Behave, Behaviour, ElemFunc, Element, Fold, Guard, Label, Locatable,
+ Location, Recipe, Style, Styles, Synthesize,
};
use crate::diag::{SourceResult, StrResult};
use crate::doc::Meta;
-use crate::eval::{
- cast_from_value, cast_to_value, Args, Cast, Func, FuncInfo, Str, Value, Vm,
-};
+use crate::eval::{Cast, Str, Value, Vm};
use crate::syntax::Span;
use crate::util::pretty_array_like;
/// Composable representation of styled content.
#[derive(Clone, Hash)]
pub struct Content {
- id: NodeId,
- span: Span,
- fields: EcoVec<(EcoString, Value)>,
- modifiers: EcoVec<Modifier>,
+ func: ElemFunc,
+ attrs: EcoVec<Attr>,
}
-/// Modifiers that can be attached to content.
+/// Attributes that can be attached to content.
#[derive(Debug, Clone, PartialEq, Hash)]
-enum Modifier {
+enum Attr {
+ Span(Span),
+ Field(EcoString),
+ Value(Value),
+ Child(Content),
+ Styles(Styles),
Prepared,
Guard(Guard),
- Id(StableId),
+ Location(Location),
}
impl Content {
- /// Create a content of the given node kind.
- pub fn new(id: NodeId) -> Self {
- Self {
- id,
- span: Span::detached(),
- fields: EcoVec::new(),
- modifiers: EcoVec::new(),
- }
+ /// Create an empty element.
+ pub fn new(func: ElemFunc) -> Self {
+ Self { func, attrs: EcoVec::new() }
}
/// Create empty content.
pub fn empty() -> Self {
- SequenceNode::new(vec![]).pack()
+ Self::new(SequenceElem::func())
}
- /// 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::new(seq).pack(),
- }
+ /// Create a new sequence element from multiples elements.
+ pub fn sequence(iter: impl IntoIterator<Item = Self>) -> Self {
+ let mut iter = iter.into_iter();
+ let Some(first) = iter.next() else { return Self::empty() };
+ let Some(second) = iter.next() else { return first };
+ let mut content = Content::empty();
+ content.attrs.push(Attr::Child(first));
+ content.attrs.push(Attr::Child(second));
+ content.attrs.extend(iter.map(Attr::Child));
+ content
}
- /// The id of the contained node.
- pub fn id(&self) -> NodeId {
- self.id
+ /// The element function of the contained content.
+ pub fn func(&self) -> ElemFunc {
+ self.func
}
- /// Whether the content is empty.
+ /// Whether the content is an empty sequence.
pub fn is_empty(&self) -> bool {
- self.to::<SequenceNode>()
- .map_or(false, |seq| seq.children().is_empty())
+ self.is::<SequenceElem>() && self.attrs.is_empty()
}
- /// Whether the contained node is of type `T`.
- pub fn is<T>(&self) -> bool
- where
- T: Node + 'static,
- {
- self.id == NodeId::of::<T>()
+ /// Whether the contained element is of type `T`.
+ pub fn is<T: Element>(&self) -> bool {
+ self.func == T::func()
}
- /// Cast to `T` if the contained node is of type `T`.
- pub fn to<T>(&self) -> Option<&T>
- where
- T: Node + 'static,
- {
- self.is::<T>().then(|| unsafe { std::mem::transmute(self) })
+ /// Cast to `T` if the contained element is of type `T`.
+ pub fn to<T: Element>(&self) -> Option<&T> {
+ T::unpack(self)
}
- /// Whether this content has the given capability.
+ /// Access the children if this is a sequence.
+ pub fn to_sequence(&self) -> Option<impl Iterator<Item = &Self>> {
+ if !self.is::<SequenceElem>() {
+ return None;
+ }
+ Some(self.attrs.iter().filter_map(Attr::child))
+ }
+
+ /// Access the child and styles.
+ pub fn to_styled(&self) -> Option<(&Content, &Styles)> {
+ if !self.is::<StyledElem>() {
+ return None;
+ }
+ let child = self.attrs.iter().find_map(Attr::child)?;
+ let styles = self.attrs.iter().find_map(Attr::styles)?;
+ Some((child, styles))
+ }
+
+ /// Whether the contained element has the given capability.
pub fn can<C>(&self) -> bool
where
C: ?Sized + 'static,
{
- (self.id.0.vtable)(TypeId::of::<C>()).is_some()
+ (self.func.0.vtable)(TypeId::of::<C>()).is_some()
}
- /// Cast to a trait object if this content has the given capability.
+ /// Cast to a trait object if the contained element has the given
+ /// capability.
pub fn with<C>(&self) -> Option<&C>
where
C: ?Sized + 'static,
{
- let vtable = (self.id.0.vtable)(TypeId::of::<C>())?;
+ let vtable = (self.func.0.vtable)(TypeId::of::<C>())?;
let data = self as *const Self as *const ();
Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) })
}
- /// Cast to a trait object if this content has the given capability.
+ /// Cast to a mutable trait object if the contained element has the given
+ /// capability.
pub fn with_mut<C>(&mut self) -> Option<&mut C>
where
C: ?Sized + 'static,
{
- let vtable = (self.id.0.vtable)(TypeId::of::<C>())?;
+ let vtable = (self.func.0.vtable)(TypeId::of::<C>())?;
let data = self as *mut Self as *mut ();
Some(unsafe { &mut *crate::util::fat::from_raw_parts_mut(data, vtable) })
}
- /// The node's span.
+ /// The content's span.
pub fn span(&self) -> Span {
- self.span
+ self.attrs.iter().find_map(Attr::span).unwrap_or(Span::detached())
}
/// Attach a span to the content if it doesn't already have one.
pub fn spanned(mut self, span: Span) -> Self {
- if self.span.is_detached() {
- self.span = span;
+ if self.span().is_detached() {
+ self.attrs.push(Attr::Span(span));
}
self
}
- /// Access a field on the content.
- pub fn field(&self, name: &str) -> Option<&Value> {
- self.fields
- .iter()
- .find(|(field, _)| field == name)
- .map(|(_, value)| value)
- }
-
- /// Try to access a field on the content as a specified type.
- pub fn cast_field<T: Cast>(&self, name: &str) -> Option<T> {
- match self.field(name) {
- Some(value) => value.clone().cast().ok(),
- None => None,
- }
- }
-
- /// Expect a field on the content to exist as a specified type.
- #[track_caller]
- pub fn expect_field<T: Cast>(&self, name: &str) -> T {
- self.field(name).unwrap().clone().cast().unwrap()
- }
-
- /// List all fields on the content.
- pub fn fields(&self) -> &[(EcoString, Value)] {
- &self.fields
- }
-
/// Attach a field to the content.
pub fn with_field(
mut self,
@@ -168,26 +152,97 @@ impl Content {
/// Attach a field to the content.
pub fn push_field(&mut self, name: impl Into<EcoString>, value: impl Into<Value>) {
let name = name.into();
- if let Some(i) = self.fields.iter().position(|(field, _)| *field == name) {
- self.fields.make_mut()[i] = (name, value.into());
+ if let Some(i) = self.attrs.iter().position(|attr| match attr {
+ Attr::Field(field) => *field == name,
+ _ => false,
+ }) {
+ self.attrs.make_mut()[i + 1] = Attr::Value(value.into());
+ } else {
+ self.attrs.push(Attr::Field(name));
+ self.attrs.push(Attr::Value(value.into()));
+ }
+ }
+
+ /// Access a field on the content.
+ pub fn field(&self, name: &str) -> Option<Value> {
+ if let Some(iter) = self.to_sequence() {
+ (name == "children")
+ .then(|| Value::Array(iter.cloned().map(Value::Content).collect()))
+ } else if let Some((child, _)) = self.to_styled() {
+ (name == "child").then(|| Value::Content(child.clone()))
} else {
- self.fields.push((name, value.into()));
+ self.field_ref(name).cloned()
+ }
+ }
+
+ /// Access a field on the content by reference.
+ ///
+ /// Does not include synthesized fields for sequence and styled elements.
+ pub fn field_ref(&self, name: &str) -> Option<&Value> {
+ self.fields_ref()
+ .find(|&(field, _)| field == name)
+ .map(|(_, value)| value)
+ }
+
+ /// Iter over all fields on the content.
+ ///
+ /// Does not include synthesized fields for sequence and styled elements.
+ pub fn fields(&self) -> impl Iterator<Item = (&EcoString, Value)> {
+ static CHILD: EcoString = EcoString::inline("child");
+ static CHILDREN: EcoString = EcoString::inline("children");
+
+ let option = if let Some(iter) = self.to_sequence() {
+ Some((&CHILDREN, Value::Array(iter.cloned().map(Value::Content).collect())))
+ } else if let Some((child, _)) = self.to_styled() {
+ Some((&CHILD, Value::Content(child.clone())))
+ } else {
+ None
+ };
+
+ self.fields_ref()
+ .map(|(name, value)| (name, value.clone()))
+ .chain(option)
+ }
+
+ /// Iter over all fields on the content.
+ ///
+ /// Does not include synthesized fields for sequence and styled elements.
+ pub fn fields_ref(&self) -> impl Iterator<Item = (&EcoString, &Value)> {
+ let mut iter = self.attrs.iter();
+ std::iter::from_fn(move || {
+ let field = iter.find_map(Attr::field)?;
+ let value = iter.next()?.value()?;
+ Some((field, value))
+ })
+ }
+
+ /// Try to access a field on the content as a specified type.
+ pub fn cast_field<T: Cast>(&self, name: &str) -> Option<T> {
+ match self.field(name) {
+ Some(value) => value.cast().ok(),
+ None => None,
}
}
+ /// Expect a field on the content to exist as a specified type.
+ #[track_caller]
+ pub fn expect_field<T: Cast>(&self, name: &str) -> T {
+ self.field(name).unwrap().cast().unwrap()
+ }
+
/// Whether the content has the specified field.
pub fn has(&self, field: &str) -> bool {
self.field(field).is_some()
}
/// Borrow the value of the given field.
- pub fn at(&self, field: &str) -> StrResult<&Value> {
+ pub fn at(&self, field: &str) -> StrResult<Value> {
self.field(field).ok_or_else(|| missing_field(field))
}
/// The content's label.
pub fn label(&self) -> Option<&Label> {
- match self.field("label")? {
+ match self.field_ref("label")? {
Value::Label(label) => Some(label),
_ => None,
}
@@ -199,20 +254,33 @@ impl Content {
}
/// Style this content with a style entry.
- pub fn styled(self, style: impl Into<Style>) -> Self {
- self.styled_with_map(style.into().into())
+ pub fn styled(mut self, style: impl Into<Style>) -> Self {
+ if self.is::<StyledElem>() {
+ let prev =
+ self.attrs.make_mut().iter_mut().find_map(Attr::styles_mut).unwrap();
+ prev.apply_one(style.into());
+ self
+ } else {
+ self.styled_with_map(style.into().into())
+ }
}
/// Style this content with a full style map.
- pub fn styled_with_map(self, styles: StyleMap) -> Self {
+ pub fn styled_with_map(mut self, styles: Styles) -> Self {
if styles.is_empty() {
+ return self;
+ }
+
+ if self.is::<StyledElem>() {
+ let prev =
+ self.attrs.make_mut().iter_mut().find_map(Attr::styles_mut).unwrap();
+ prev.apply(styles);
self
- } else if let Some(styled) = self.to::<StyledNode>() {
- let mut map = styled.styles();
- map.apply(styles);
- StyledNode::new(map, styled.body()).pack()
} else {
- StyledNode::new(styles, self).pack()
+ let mut content = Content::new(StyledElem::func());
+ content.attrs.push(Attr::Child(self));
+ content.attrs.push(Attr::Styles(styles));
+ content
}
}
@@ -221,7 +289,7 @@ impl Content {
if recipe.selector.is_none() {
recipe.apply_vm(vm, self)
} else {
- Ok(self.styled(Style::Recipe(recipe)))
+ Ok(self.styled(recipe))
}
}
@@ -232,35 +300,34 @@ impl Content {
Ok(Self::sequence(vec![self.clone(); count]))
}
-}
-#[doc(hidden)]
-impl Content {
/// Disable a show rule recipe.
- pub fn guarded(mut self, id: Guard) -> Self {
- self.modifiers.push(Modifier::Guard(id));
+ pub fn guarded(mut self, guard: Guard) -> Self {
+ self.attrs.push(Attr::Guard(guard));
self
}
- /// Whether no show rule was executed for this node so far.
- pub(super) fn is_pristine(&self) -> bool {
- !self
- .modifiers
- .iter()
- .any(|modifier| matches!(modifier, Modifier::Guard(_)))
+ /// Check whether a show rule recipe is disabled.
+ pub fn is_guarded(&self, guard: Guard) -> bool {
+ self.attrs.contains(&Attr::Guard(guard))
}
- /// Check whether a show rule recipe is disabled.
- pub(super) fn is_guarded(&self, id: Guard) -> bool {
- self.modifiers.contains(&Modifier::Guard(id))
+ /// Whether no show rule was executed for this content so far.
+ pub fn is_pristine(&self) -> bool {
+ !self.attrs.iter().any(|modifier| matches!(modifier, Attr::Guard(_)))
}
- /// Whether this node was prepared.
+ /// Whether this content has already been prepared.
pub fn is_prepared(&self) -> bool {
- self.modifiers.contains(&Modifier::Prepared)
+ self.attrs.contains(&Attr::Prepared)
}
- /// Whether the node needs to be realized specially.
+ /// Mark this content as prepared.
+ pub fn mark_prepared(&mut self) {
+ self.attrs.push(Attr::Prepared);
+ }
+
+ /// Whether the content needs to be realized specially.
pub fn needs_preparation(&self) -> bool {
(self.can::<dyn Locatable>()
|| self.can::<dyn Synthesize>()
@@ -268,37 +335,23 @@ impl Content {
&& !self.is_prepared()
}
- /// Mark this content as prepared.
- pub fn mark_prepared(&mut self) {
- self.modifiers.push(Modifier::Prepared);
- }
-
- /// Attach a stable id to this content.
- pub fn set_stable_id(&mut self, id: StableId) {
- self.modifiers.push(Modifier::Id(id));
- }
-
- /// This content's stable identifier.
- pub fn stable_id(&self) -> Option<StableId> {
- self.modifiers.iter().find_map(|modifier| match modifier {
- Modifier::Id(id) => Some(*id),
+ /// This content's location in the document flow.
+ pub fn location(&self) -> Option<Location> {
+ self.attrs.iter().find_map(|modifier| match modifier {
+ Attr::Location(location) => Some(*location),
_ => None,
})
}
- /// Copy the modifiers from another piece of content.
- pub(super) fn copy_modifiers(&mut self, from: &Content) {
- self.span = from.span;
- self.modifiers = from.modifiers.clone();
- if let Some(label) = from.label() {
- self.push_field("label", label.clone())
- }
+ /// Attach a location to this content.
+ pub fn set_location(&mut self, location: Location) {
+ self.attrs.push(Attr::Location(location));
}
}
impl Debug for Content {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- let name = self.id.name;
+ let name = self.func.name();
if let Some(text) = item!(text_str)(self) {
f.write_char('[')?;
f.write_str(&text)?;
@@ -308,12 +361,15 @@ impl Debug for Content {
return f.write_str("[ ]");
}
- let pieces: Vec<_> = self
- .fields
- .iter()
+ let mut pieces: Vec<_> = self
+ .fields()
.map(|(name, value)| eco_format!("{name}: {value:?}"))
.collect();
+ if self.is::<StyledElem>() {
+ pieces.push(EcoString::from(".."));
+ }
+
f.write_str(name)?;
f.write_str(&pretty_array_like(&pieces, false))
}
@@ -327,31 +383,36 @@ impl Default for Content {
impl PartialEq for Content {
fn eq(&self, other: &Self) -> bool {
- self.id == other.id
- && self.fields.len() == other.fields.len()
- && self
- .fields
- .iter()
- .all(|(name, value)| other.field(name) == Some(value))
+ if let (Some(left), Some(right)) = (self.to_sequence(), other.to_sequence()) {
+ left.eq(right)
+ } else if let (Some(left), Some(right)) = (self.to_styled(), other.to_styled()) {
+ left == right
+ } else {
+ self.func == other.func && self.fields_ref().eq(other.fields_ref())
+ }
}
}
impl Add for Content {
type Output = Self;
- fn add(self, rhs: Self) -> Self::Output {
- let lhs = self;
- let seq = match (lhs.to::<SequenceNode>(), rhs.to::<SequenceNode>()) {
- (Some(lhs), Some(rhs)) => {
- lhs.children().into_iter().chain(rhs.children()).collect()
+ fn add(self, mut rhs: Self) -> Self::Output {
+ let mut lhs = self;
+ match (lhs.is::<SequenceElem>(), rhs.is::<SequenceElem>()) {
+ (true, true) => {
+ lhs.attrs.extend(rhs.attrs);
+ lhs
}
- (Some(lhs), None) => {
- lhs.children().into_iter().chain(iter::once(rhs)).collect()
+ (true, false) => {
+ lhs.attrs.push(Attr::Child(rhs));
+ lhs
}
- (None, Some(rhs)) => iter::once(lhs).chain(rhs.children()).collect(),
- (None, None) => vec![lhs, rhs],
- };
- SequenceNode::new(seq).pack()
+ (false, true) => {
+ rhs.attrs.insert(0, Attr::Child(lhs));
+ rhs
+ }
+ (false, false) => Self::sequence([lhs, rhs]),
+ }
}
}
@@ -363,154 +424,77 @@ impl AddAssign for Content {
impl Sum for Content {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
- Self::sequence(iter.collect())
+ Self::sequence(iter)
}
}
-/// A constructable, stylable content node.
-pub trait Node: Construct + Set + Sized + 'static {
- /// The node's ID.
- fn id() -> NodeId;
-
- /// Pack a node into type-erased content.
- fn pack(self) -> Content;
-}
-
-/// A unique identifier for a node.
-#[derive(Copy, Clone)]
-pub struct NodeId(pub &'static NodeMeta);
-
-impl NodeId {
- /// Get the id of a node.
- pub fn of<T: Node>() -> Self {
- T::id()
+impl Attr {
+ fn child(&self) -> Option<&Content> {
+ match self {
+ Self::Child(child) => Some(child),
+ _ => None,
+ }
}
-}
-impl Debug for NodeId {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.pad(self.name)
+ fn styles(&self) -> Option<&Styles> {
+ match self {
+ Self::Styles(styles) => Some(styles),
+ _ => None,
+ }
}
-}
-impl Hash for NodeId {
- fn hash<H: Hasher>(&self, state: &mut H) {
- state.write_usize(self.0 as *const _ as usize);
+ fn styles_mut(&mut self) -> Option<&mut Styles> {
+ match self {
+ Self::Styles(styles) => Some(styles),
+ _ => None,
+ }
}
-}
-
-impl Eq for NodeId {}
-impl PartialEq for NodeId {
- fn eq(&self, other: &Self) -> bool {
- std::ptr::eq(self.0, other.0)
+ fn field(&self) -> Option<&EcoString> {
+ match self {
+ Self::Field(field) => Some(field),
+ _ => None,
+ }
}
-}
-
-impl Deref for NodeId {
- type Target = NodeMeta;
- fn deref(&self) -> &Self::Target {
- self.0
+ fn value(&self) -> Option<&Value> {
+ match self {
+ Self::Value(value) => Some(value),
+ _ => None,
+ }
}
-}
-
-cast_from_value! {
- NodeId,
- v: Func => v.id().ok_or("this function is not an element")?
-}
-
-cast_to_value! {
- v: NodeId => Value::Func(v.into())
-}
-
-/// Static node for a node.
-pub struct NodeMeta {
- /// The node's name.
- pub name: &'static str,
- /// The node's vtable for caspability dispatch.
- pub vtable: fn(of: TypeId) -> Option<*const ()>,
- /// The node's constructor.
- pub construct: fn(&Vm, &mut Args) -> SourceResult<Content>,
- /// The node's set rule.
- pub set: fn(&mut Args) -> SourceResult<StyleMap>,
- /// Details about the function.
- pub info: Lazy<FuncInfo>,
-}
-
-/// A node's constructor function.
-pub trait Construct {
- /// 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: &Vm, args: &mut Args) -> SourceResult<Content>;
-}
-
-/// A node's set rule.
-pub trait Set {
- /// Parse relevant arguments into style properties for this node.
- fn set(args: &mut Args) -> SourceResult<StyleMap>;
-}
-
-/// Indicates that a node cannot be labelled.
-pub trait Unlabellable {}
-/// A label for a node.
-#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
-pub struct Label(pub EcoString);
-
-impl Debug for Label {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(f, "<{}>", self.0)
+ fn span(&self) -> Option<Span> {
+ match self {
+ Self::Span(span) => Some(*span),
+ _ => None,
+ }
}
}
-/// 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.
-///
/// Display: Sequence
/// Category: special
-#[node]
-pub struct SequenceNode {
- #[variadic]
- pub children: Vec<Content>,
-}
+#[element]
+struct SequenceElem {}
-/// A node with applied styles.
-///
-/// Display: Styled
+/// Display: Sequence
/// Category: special
-#[node]
-pub struct StyledNode {
- /// The styles.
- #[required]
- pub styles: StyleMap,
-
- /// The styled content.
- #[required]
- pub body: Content,
-}
-
-cast_from_value! {
- StyleMap: "style map",
-}
+#[element]
+struct StyledElem {}
-/// Host for metadata.
+/// Hosts metadata and ensures metadata is produced even for empty elements.
///
/// Display: Meta
/// Category: special
-#[node(Behave)]
-pub struct MetaNode {
+#[element(Behave)]
+pub struct MetaElem {
/// Metadata that should be attached to all elements affected by this style
/// property.
#[fold]
pub data: Vec<Meta>,
}
-impl Behave for MetaNode {
+impl Behave for MetaElem {
fn behaviour(&self) -> Behaviour {
Behaviour::Ignorant
}
diff --git a/src/model/element.rs b/src/model/element.rs
new file mode 100644
index 00000000..e25b22b4
--- /dev/null
+++ b/src/model/element.rs
@@ -0,0 +1,145 @@
+use std::any::TypeId;
+use std::fmt::{self, Debug, Formatter};
+use std::hash::{Hash, Hasher};
+
+use ecow::EcoString;
+use once_cell::sync::Lazy;
+
+use super::{Content, Selector, Styles};
+use crate::diag::SourceResult;
+use crate::eval::{
+ cast_from_value, cast_to_value, Args, Dict, Func, FuncInfo, Value, Vm,
+};
+
+/// A document element.
+pub trait Element: Construct + Set + Sized + 'static {
+ /// Pack the element into type-erased content.
+ fn pack(self) -> Content;
+
+ /// Extract this element from type-erased content.
+ fn unpack(content: &Content) -> Option<&Self>;
+
+ /// The element's function.
+ fn func() -> ElemFunc;
+}
+
+/// An element's constructor function.
+pub trait Construct {
+ /// Construct an element from the arguments.
+ ///
+ /// This is passed only the arguments that remain after execution of the
+ /// element's set rule.
+ fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult<Content>;
+}
+
+/// An element's set rule.
+pub trait Set {
+ /// Parse relevant arguments into style properties for this element.
+ fn set(args: &mut Args) -> SourceResult<Styles>;
+}
+
+/// An element's function.
+#[derive(Copy, Clone)]
+pub struct ElemFunc(pub(super) &'static NativeElemFunc);
+
+impl ElemFunc {
+ /// The function's name.
+ pub fn name(self) -> &'static str {
+ self.0.name
+ }
+
+ /// Apply the given arguments to the function.
+ pub fn with(self, args: Args) -> Func {
+ Func::from(self).with(args)
+ }
+
+ /// Extract details about the function.
+ pub fn info(&self) -> &'static FuncInfo {
+ &self.0.info
+ }
+
+ /// Construct an element.
+ pub fn construct(self, vm: &mut Vm, args: &mut Args) -> SourceResult<Content> {
+ (self.0.construct)(vm, args)
+ }
+
+ /// Create a selector for elements of this function.
+ pub fn select(self) -> Selector {
+ Selector::Elem(self, None)
+ }
+
+ /// Create a selector for elements of this function, filtering for those
+ /// whose [fields](super::Content::field) match the given arguments.
+ pub fn where_(self, fields: Dict) -> Selector {
+ Selector::Elem(self, Some(fields))
+ }
+
+ /// Execute the set rule for the element and return the resulting style map.
+ pub fn set(self, mut args: Args) -> SourceResult<Styles> {
+ let styles = (self.0.set)(&mut args)?;
+ args.finish()?;
+ Ok(styles)
+ }
+}
+
+impl Debug for ElemFunc {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(self.name())
+ }
+}
+
+impl Hash for ElemFunc {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ state.write_usize(self.0 as *const _ as usize);
+ }
+}
+
+impl Eq for ElemFunc {}
+
+impl PartialEq for ElemFunc {
+ fn eq(&self, other: &Self) -> bool {
+ std::ptr::eq(self.0, other.0)
+ }
+}
+
+cast_from_value! {
+ ElemFunc,
+ v: Func => v.element().ok_or("expected element function")?,
+}
+
+cast_to_value! {
+ v: ElemFunc => Value::Func(v.into())
+}
+
+impl From<&'static NativeElemFunc> for ElemFunc {
+ fn from(native: &'static NativeElemFunc) -> Self {
+ Self(native)
+ }
+}
+
+/// An element function backed by a Rust type.
+pub struct NativeElemFunc {
+ /// The element's name.
+ pub name: &'static str,
+ /// The element's vtable for capability dispatch.
+ pub vtable: fn(of: TypeId) -> Option<*const ()>,
+ /// The element's constructor.
+ pub construct: fn(&mut Vm, &mut Args) -> SourceResult<Content>,
+ /// The element's set rule.
+ pub set: fn(&mut Args) -> SourceResult<Styles>,
+ /// Details about the function.
+ pub info: Lazy<FuncInfo>,
+}
+
+/// A label for an element.
+#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub struct Label(pub EcoString);
+
+impl Debug for Label {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "<{}>", self.0)
+ }
+}
+
+/// Indicates that an element cannot be labelled.
+pub trait Unlabellable {}
diff --git a/src/model/introspect.rs b/src/model/introspect.rs
new file mode 100644
index 00000000..35f0e628
--- /dev/null
+++ b/src/model/introspect.rs
@@ -0,0 +1,170 @@
+use std::fmt::{self, Debug, Formatter};
+use std::hash::Hash;
+use std::num::NonZeroUsize;
+
+use super::{Content, Selector};
+use crate::doc::{Frame, FrameItem, Meta, Position};
+use crate::eval::cast_from_value;
+use crate::geom::{Point, Transform};
+use crate::util::NonZeroExt;
+
+/// Stably identifies a location in the document across multiple layout passes.
+///
+/// This struct is created by [`StabilityProvider::locate`].
+#[derive(Copy, Clone, Eq, PartialEq, Hash)]
+pub struct Location(u128, usize, usize);
+
+impl Location {
+ /// Produce a variant of this location.
+ pub fn variant(self, n: usize) -> Self {
+ Self(self.0, self.1, n)
+ }
+}
+
+impl Debug for Location {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad("..")
+ }
+}
+
+cast_from_value! {
+ Location: "location",
+}
+
+/// Provides stable identities to elements.
+#[derive(Clone)]
+pub struct StabilityProvider {
+ hashes: Vec<u128>,
+ checkpoints: Vec<usize>,
+}
+
+impl StabilityProvider {
+ /// Create a new stability provider.
+ pub fn new() -> Self {
+ Self { hashes: vec![], checkpoints: vec![] }
+ }
+}
+
+#[comemo::track]
+impl StabilityProvider {
+ /// Produce a stable identifier for this call site.
+ pub fn locate(&mut self, hash: u128) -> Location {
+ let count = self.hashes.iter().filter(|&&prev| prev == hash).count();
+ self.hashes.push(hash);
+ Location(hash, count, 0)
+ }
+
+ /// Create a checkpoint of the state that can be restored.
+ pub fn save(&mut self) {
+ self.checkpoints.push(self.hashes.len());
+ }
+
+ /// Restore the last checkpoint.
+ pub fn restore(&mut self) {
+ if let Some(checkpoint) = self.checkpoints.pop() {
+ self.hashes.truncate(checkpoint);
+ }
+ }
+}
+
+/// Can be queried for elements and their positions.
+pub struct Introspector {
+ pages: usize,
+ elems: Vec<(Content, Position)>,
+}
+
+impl Introspector {
+ /// Create a new introspector.
+ pub fn new(frames: &[Frame]) -> Self {
+ let mut introspector = Self { pages: frames.len(), elems: vec![] };
+ for (i, frame) in frames.iter().enumerate() {
+ let page = NonZeroUsize::new(1 + i).unwrap();
+ introspector.extract(frame, page, Transform::identity());
+ }
+ introspector
+ }
+
+ /// Iterate over all elements.
+ pub fn all(&self) -> impl Iterator<Item = &Content> {
+ self.elems.iter().map(|(elem, _)| elem)
+ }
+
+ /// Extract metadata from a frame.
+ fn extract(&mut self, frame: &Frame, page: NonZeroUsize, ts: Transform) {
+ for (pos, item) in frame.items() {
+ match item {
+ FrameItem::Group(group) => {
+ let ts = ts
+ .pre_concat(Transform::translate(pos.x, pos.y))
+ .pre_concat(group.transform);
+ self.extract(&group.frame, page, ts);
+ }
+ FrameItem::Meta(Meta::Elem(content), _)
+ if !self
+ .elems
+ .iter()
+ .any(|(prev, _)| prev.location() == content.location()) =>
+ {
+ let pos = pos.transform(ts);
+ self.elems.push((content.clone(), Position { page, point: pos }));
+ }
+ _ => {}
+ }
+ }
+ }
+}
+
+#[comemo::track]
+impl Introspector {
+ /// Whether this introspector is not yet initialized.
+ pub fn init(&self) -> bool {
+ self.pages > 0
+ }
+
+ /// Query for all matching elements.
+ pub fn query(&self, selector: Selector) -> Vec<Content> {
+ self.all().filter(|elem| selector.matches(elem)).cloned().collect()
+ }
+
+ /// Query for all matching element up to the given location.
+ pub fn query_before(&self, selector: Selector, location: Location) -> Vec<Content> {
+ let mut matches = vec![];
+ for elem in self.all() {
+ if selector.matches(elem) {
+ matches.push(elem.clone());
+ }
+ if elem.location() == Some(location) {
+ break;
+ }
+ }
+ matches
+ }
+
+ /// Query for all matching elements starting from the given location.
+ pub fn query_after(&self, selector: Selector, location: Location) -> Vec<Content> {
+ self.all()
+ .skip_while(|elem| elem.location() != Some(location))
+ .filter(|elem| selector.matches(elem))
+ .cloned()
+ .collect()
+ }
+
+ /// The total number pages.
+ pub fn pages(&self) -> NonZeroUsize {
+ NonZeroUsize::new(self.pages).unwrap_or(NonZeroUsize::ONE)
+ }
+
+ /// Find the page number for the given location.
+ pub fn page(&self, location: Location) -> NonZeroUsize {
+ self.position(location).page
+ }
+
+ /// Find the position for the given location.
+ pub fn position(&self, location: Location) -> Position {
+ self.elems
+ .iter()
+ .find(|(elem, _)| elem.location() == Some(location))
+ .map(|(_, loc)| *loc)
+ .unwrap_or(Position { page: NonZeroUsize::ONE, point: Point::zero() })
+ }
+}
diff --git a/src/model/mod.rs b/src/model/mod.rs
index 6015c365..7458dc3c 100644
--- a/src/model/mod.rs
+++ b/src/model/mod.rs
@@ -1,14 +1,87 @@
//! The document model.
-#[macro_use]
-mod styles;
mod content;
+mod element;
+mod introspect;
mod realize;
-mod typeset;
+mod styles;
pub use self::content::*;
+pub use self::element::*;
+pub use self::introspect::*;
pub use self::realize::*;
pub use self::styles::*;
-pub use self::typeset::*;
-pub use typst_macros::node;
+pub use typst_macros::element;
+
+use comemo::{Constraint, Track, Tracked, TrackedMut};
+
+use crate::diag::SourceResult;
+use crate::doc::Document;
+use crate::eval::Tracer;
+use crate::World;
+
+/// Typeset content into a fully layouted document.
+#[comemo::memoize]
+pub fn typeset(
+ world: Tracked<dyn World>,
+ mut tracer: TrackedMut<Tracer>,
+ content: &Content,
+) -> SourceResult<Document> {
+ let library = world.library();
+ let styles = StyleChain::new(&library.styles);
+
+ let mut document;
+ let mut iter = 0;
+ let mut introspector = Introspector::new(&[]);
+
+ // Relayout until all introspections stabilize.
+ // If that doesn't happen within five attempts, we give up.
+ loop {
+ let constraint = Constraint::new();
+ let mut provider = StabilityProvider::new();
+ let mut vt = Vt {
+ world,
+ tracer: TrackedMut::reborrow_mut(&mut tracer),
+ provider: provider.track_mut(),
+ introspector: introspector.track_with(&constraint),
+ };
+
+ document = (library.items.layout)(&mut vt, content, styles)?;
+ iter += 1;
+
+ introspector = Introspector::new(&document.pages);
+
+ if iter >= 5 || introspector.valid(&constraint) {
+ break;
+ }
+ }
+
+ Ok(document)
+}
+
+/// A virtual typesetter.
+///
+/// Holds the state needed to [typeset] content.
+pub struct Vt<'a> {
+ /// The compilation environment.
+ pub world: Tracked<'a, dyn World>,
+ /// The tracer for inspection of the values an expression produces.
+ pub tracer: TrackedMut<'a, Tracer>,
+ /// Provides stable identities to elements.
+ pub provider: TrackedMut<'a, StabilityProvider>,
+ /// Provides access to information about the document.
+ pub introspector: Tracked<'a, Introspector>,
+}
+
+impl Vt<'_> {
+ /// Mutably reborrow with a shorter lifetime.
+ pub fn reborrow_mut(&mut self) -> Vt<'_> {
+ Vt {
+ world: self.world,
+ tracer: TrackedMut::reborrow_mut(&mut self.tracer),
+ provider: TrackedMut::reborrow_mut(&mut self.provider),
+ introspector: self.introspector,
+ }
+ }
+}
diff --git a/src/model/realize.rs b/src/model/realize.rs
index 634a31fd..51d69fdc 100644
--- a/src/model/realize.rs
+++ b/src/model/realize.rs
@@ -1,4 +1,4 @@
-use super::{Content, MetaNode, Node, NodeId, Recipe, Selector, StyleChain, Vt};
+use super::{Content, ElemFunc, Element, MetaElem, Recipe, Selector, StyleChain, Vt};
use crate::diag::SourceResult;
use crate::doc::Meta;
use crate::util::hash128;
@@ -35,28 +35,28 @@ pub fn realize(
) -> SourceResult<Option<Content>> {
// Pre-process.
if target.needs_preparation() {
- let mut node = target.clone();
+ let mut elem = target.clone();
if target.can::<dyn Locatable>() || target.label().is_some() {
- let id = vt.provider.identify(hash128(target));
- node.set_stable_id(id);
+ let location = vt.provider.locate(hash128(target));
+ elem.set_location(location);
}
- if let Some(node) = node.with_mut::<dyn Synthesize>() {
- node.synthesize(vt, styles);
+ if let Some(elem) = elem.with_mut::<dyn Synthesize>() {
+ elem.synthesize(vt, styles);
}
- node.mark_prepared();
+ elem.mark_prepared();
- if node.stable_id().is_some() {
- let span = node.span();
- let meta = Meta::Node(node.clone());
+ if elem.location().is_some() {
+ let span = elem.span();
+ let meta = Meta::Elem(elem.clone());
return Ok(Some(
- (node + MetaNode::new().pack().spanned(span))
- .styled(MetaNode::set_data(vec![meta])),
+ (elem + MetaElem::new().pack().spanned(span))
+ .styled(MetaElem::set_data(vec![meta])),
));
}
- return Ok(Some(node));
+ return Ok(Some(elem));
}
// Find out how many recipes there are.
@@ -77,17 +77,17 @@ pub fn realize(
// Realize if there was no matching recipe.
if let Some(showable) = target.with::<dyn Show>() {
- let guard = Guard::Base(target.id());
+ let guard = Guard::Base(target.func());
if realized.is_none() && !target.is_guarded(guard) {
realized = Some(showable.show(vt, styles)?);
}
}
- // Finalize only if this is the first application for this node.
- if let Some(node) = target.with::<dyn Finalize>() {
+ // Finalize only if this is the first application for this element.
+ if let Some(elem) = target.with::<dyn Finalize>() {
if target.is_pristine() {
if let Some(already) = realized {
- realized = Some(node.finalize(already, styles));
+ realized = Some(elem.finalize(already, styles));
}
}
}
@@ -103,8 +103,8 @@ fn try_apply(
guard: Guard,
) -> SourceResult<Option<Content>> {
match &recipe.selector {
- Some(Selector::Node(id, _)) => {
- if target.id() != *id {
+ Some(Selector::Elem(element, _)) => {
+ if target.func() != *element {
return Ok(None);
}
@@ -124,22 +124,17 @@ fn try_apply(
return Ok(None);
};
- let make = |s| {
- let mut content = item!(text)(s);
- content.copy_modifiers(target);
- content
- };
-
+ let make = |s: &str| target.clone().with_field("text", s);
let mut result = vec![];
let mut cursor = 0;
for m in regex.find_iter(&text) {
let start = m.start();
if cursor < start {
- result.push(make(text[cursor..start].into()));
+ result.push(make(&text[cursor..start]));
}
- let piece = make(m.as_str().into()).guarded(guard);
+ let piece = make(m.as_str()).guarded(guard);
let transformed = recipe.apply_vt(vt, piece)?;
result.push(transformed);
cursor = m.end();
@@ -150,7 +145,7 @@ fn try_apply(
}
if cursor < text.len() {
- result.push(make(text[cursor..].into()));
+ result.push(make(&text[cursor..]));
}
Ok(Some(Content::sequence(result)))
@@ -163,55 +158,56 @@ fn try_apply(
}
}
-/// Makes this node locatable through `vt.locate`.
+/// Makes this element locatable through `vt.locate`.
pub trait Locatable {}
-/// Synthesize fields on a node. This happens before execution of any show rule.
+/// Synthesize fields on an element. This happens before execution of any show
+/// rule.
pub trait Synthesize {
- /// Prepare the node for show rule application.
+ /// Prepare the element for show rule application.
fn synthesize(&mut self, vt: &Vt, styles: StyleChain);
}
-/// The base recipe for a node.
+/// The base recipe for an element.
pub trait Show {
- /// Execute the base recipe for this node.
+ /// Execute the base recipe for this element.
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content>;
}
-/// Post-process a node after it was realized.
+/// Post-process an element after it was realized.
pub trait Finalize {
- /// Finalize the fully realized form of the node. Use this for effects that
+ /// Finalize the fully realized form of the element. Use this for effects that
/// should work even in the face of a user-defined show rule, for example
- /// the linking behaviour of a link node.
+ /// the linking behaviour of a link element.
fn finalize(&self, realized: Content, styles: StyleChain) -> Content;
}
-/// How a node interacts with other nodes.
+/// How the element interacts with other elements.
pub trait Behave {
- /// The node's interaction behaviour.
+ /// The element's interaction behaviour.
fn behaviour(&self) -> Behaviour;
- /// Whether this weak node is larger than a previous one and thus picked as
- /// the maximum when the levels are the same.
+ /// Whether this weak element is larger than a previous one and thus picked
+ /// as the maximum when the levels are the same.
#[allow(unused_variables)]
fn larger(&self, prev: &Content) -> bool {
false
}
}
-/// How a node interacts with other nodes in a stream.
+/// How an element interacts with other elements in a stream.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Behaviour {
- /// A weak node which only survives when a supportive node is before and
- /// after it. Furthermore, per consecutive run of weak nodes, only one
- /// survives: The one with the lowest weakness level (or the larger one if
- /// there is a tie).
+ /// A weak element which only survives when a supportive element is before
+ /// and after it. Furthermore, per consecutive run of weak elements, only
+ /// one survives: The one with the lowest weakness level (or the larger one
+ /// if there is a tie).
Weak(usize),
- /// A node that enables adjacent weak nodes to exist. The default.
+ /// An element that enables adjacent weak elements to exist. The default.
Supportive,
- /// A node that destroys adjacent weak nodes.
+ /// An element that destroys adjacent weak elements.
Destructive,
- /// A node that does not interact at all with other nodes, having the
+ /// An element that does not interact at all with other elements, having the
/// same effect as if it didn't exist.
Ignorant,
}
@@ -221,6 +217,6 @@ pub enum Behaviour {
pub enum Guard {
/// The nth recipe from the top of the chain.
Nth(usize),
- /// The [base recipe](Show) for a kind of node.
- Base(NodeId),
+ /// The [base recipe](Show) for a kind of element.
+ Base(ElemFunc),
}
diff --git a/src/model/styles.rs b/src/model/styles.rs
index b7d09774..db2b2053 100644
--- a/src/model/styles.rs
+++ b/src/model/styles.rs
@@ -1,25 +1,26 @@
use std::fmt::{self, Debug, Formatter, Write};
use std::iter;
+use std::mem;
-use ecow::{eco_format, EcoString, EcoVec};
+use ecow::{eco_format, eco_vec, EcoString, EcoVec};
-use super::{Content, Label, Node, NodeId, Vt};
+use super::{Content, ElemFunc, Element, Label, Vt};
use crate::diag::{SourceResult, Trace, Tracepoint};
use crate::eval::{cast_from_value, Args, Cast, Dict, Func, Regex, Value, Vm};
use crate::syntax::Span;
use crate::util::pretty_array_like;
-/// A map of style properties.
-#[derive(Default, Clone, Hash)]
-pub struct StyleMap(Vec<Style>);
+/// A list of style properties.
+#[derive(Default, PartialEq, Clone, Hash)]
+pub struct Styles(EcoVec<Style>);
-impl StyleMap {
- /// Create a new, empty style map.
+impl Styles {
+ /// Create a new, empty style list.
pub fn new() -> Self {
Self::default()
}
- /// Whether this map contains no styles.
+ /// Whether this contains no styles.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
@@ -39,13 +40,25 @@ impl StyleMap {
}
/// Apply outer styles. Like [`chain`](StyleChain::chain), but in-place.
- pub fn apply(&mut self, outer: Self) {
- self.0.splice(0..0, outer.0.iter().cloned());
+ pub fn apply(&mut self, mut outer: Self) {
+ outer.0.extend(mem::take(self).0.into_iter());
+ *self = outer;
+ }
+
+ /// Apply one outer styles. Like [`chain_one`](StyleChain::chain_one), but
+ /// in-place.
+ pub fn apply_one(&mut self, outer: Style) {
+ self.0.insert(0, outer);
+ }
+
+ /// Apply a slice of outer styles.
+ pub fn apply_slice(&mut self, outer: &[Style]) {
+ self.0 = outer.iter().cloned().chain(mem::take(self).0.into_iter()).collect();
}
/// Add an origin span to all contained properties.
pub fn spanned(mut self, span: Span) -> Self {
- for entry in &mut self.0 {
+ for entry in self.0.make_mut() {
if let Style::Property(property) = entry {
property.span = Some(span);
}
@@ -53,37 +66,31 @@ impl StyleMap {
self
}
- /// Returns `Some(_)` with an optional span if this map contains styles for
- /// the given `node`.
- pub fn interruption<T: Node>(&self) -> Option<Option<Span>> {
- let node = NodeId::of::<T>();
+ /// Returns `Some(_)` with an optional span if this list contains
+ /// styles for the given element.
+ pub fn interruption<T: Element>(&self) -> Option<Option<Span>> {
+ let func = T::func();
self.0.iter().find_map(|entry| match entry {
- Style::Property(property) => property.is_of(node).then(|| property.span),
- Style::Recipe(recipe) => recipe.is_of(node).then(|| Some(recipe.span)),
+ Style::Property(property) => property.is_of(func).then(|| property.span),
+ Style::Recipe(recipe) => recipe.is_of(func).then(|| Some(recipe.span)),
})
}
}
-impl From<Style> for StyleMap {
+impl From<Style> for Styles {
fn from(entry: Style) -> Self {
- Self(vec![entry])
- }
-}
-
-impl PartialEq for StyleMap {
- fn eq(&self, other: &Self) -> bool {
- crate::util::hash128(self) == crate::util::hash128(other)
+ Self(eco_vec![entry])
}
}
-impl Debug for StyleMap {
+impl Debug for Styles {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad("..")
}
}
/// A single style property or recipe.
-#[derive(Clone, Hash)]
+#[derive(Clone, PartialEq, Hash)]
pub enum Style {
/// A style property originating from a set rule or constructor.
Property(Property),
@@ -124,11 +131,17 @@ impl From<Property> for Style {
}
}
+impl From<Recipe> for Style {
+ fn from(recipe: Recipe) -> Self {
+ Self::Recipe(recipe)
+ }
+}
+
/// A style property originating from a set rule or constructor.
-#[derive(Clone, Hash)]
+#[derive(Clone, PartialEq, Hash)]
pub struct Property {
- /// The id of the node the property belongs to.
- node: NodeId,
+ /// The element the property belongs to.
+ element: ElemFunc,
/// The property's name.
name: EcoString,
/// The property's value.
@@ -139,44 +152,44 @@ pub struct Property {
impl Property {
/// Create a new property from a key-value pair.
- pub fn new(node: NodeId, name: EcoString, value: Value) -> Self {
- Self { node, name, value, span: None }
+ pub fn new(element: ElemFunc, name: EcoString, value: Value) -> Self {
+ Self { element, name, value, span: None }
}
/// Whether this property is the given one.
- pub fn is(&self, node: NodeId, name: &str) -> bool {
- self.node == node && self.name == name
+ pub fn is(&self, element: ElemFunc, name: &str) -> bool {
+ self.element == element && self.name == name
}
- /// Whether this property belongs to the node with the given id.
- pub fn is_of(&self, node: NodeId) -> bool {
- self.node == node
+ /// Whether this property belongs to the given element.
+ pub fn is_of(&self, element: ElemFunc) -> bool {
+ self.element == element
}
}
impl Debug for Property {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(f, "set {}({}: {:?})", self.node.name, self.name, self.value)?;
+ write!(f, "set {}({}: {:?})", self.element.name(), self.name, self.value)?;
Ok(())
}
}
/// A show rule recipe.
-#[derive(Clone, Hash)]
+#[derive(Clone, PartialEq, Hash)]
pub struct Recipe {
/// The span errors are reported with.
pub span: Span,
- /// Determines whether the recipe applies to a node.
+ /// Determines whether the recipe applies to an element.
pub selector: Option<Selector>,
/// The transformation to perform on the match.
pub transform: Transform,
}
impl Recipe {
- /// Whether this recipe is for the given node.
- pub fn is_of(&self, node: NodeId) -> bool {
+ /// Whether this recipe is for the given type of element.
+ pub fn is_of(&self, element: ElemFunc) -> bool {
match self.selector {
- Some(Selector::Node(id, _)) => id == node,
+ Some(Selector::Elem(own, _)) => own == element,
_ => false,
}
}
@@ -197,7 +210,7 @@ impl Recipe {
let mut result = func.call_vm(vm, args);
// For selector-less show rules, a tracepoint makes no sense.
if self.selector.is_some() {
- let point = || Tracepoint::Show(content.id().name.into());
+ let point = || Tracepoint::Show(content.func().name().into());
result = result.trace(vm.world(), point, content.span());
}
Ok(result?.display())
@@ -213,7 +226,7 @@ impl Recipe {
Transform::Func(func) => {
let mut result = func.call_vt(vt, [Value::Content(content.clone())]);
if self.selector.is_some() {
- let point = || Tracepoint::Show(content.id().name.into());
+ let point = || Tracepoint::Show(content.func().name().into());
result = result.trace(vt.world, point, content.span());
}
Ok(result?.display())
@@ -238,25 +251,20 @@ impl Debug for Recipe {
/// A selector in a show rule.
#[derive(Clone, PartialEq, Hash)]
pub enum Selector {
- /// Matches a specific type of node.
+ /// Matches a specific type of element.
///
- /// If there is a dictionary, only nodes with the fields from the
+ /// If there is a dictionary, only elements with the fields from the
/// dictionary match.
- Node(NodeId, Option<Dict>),
- /// Matches nodes with a specific label.
+ Elem(ElemFunc, Option<Dict>),
+ /// Matches elements with a specific label.
Label(Label),
- /// Matches text nodes through a regular expression.
+ /// Matches text elements through a regular expression.
Regex(Regex),
/// Matches if any of the subselectors match.
Any(EcoVec<Self>),
}
impl Selector {
- /// Define a simple node selector.
- pub fn node<T: Node>() -> Self {
- Self::Node(NodeId::of::<T>(), None)
- }
-
/// Define a simple text selector.
pub fn text(text: &str) -> Self {
Self::Regex(Regex::new(&regex::escape(text)).unwrap())
@@ -265,16 +273,16 @@ impl Selector {
/// Whether the selector matches for the target.
pub fn matches(&self, target: &Content) -> bool {
match self {
- Self::Node(id, dict) => {
- target.id() == *id
+ Self::Elem(element, dict) => {
+ target.func() == *element
&& dict
.iter()
.flat_map(|dict| dict.iter())
- .all(|(name, value)| target.field(name) == Some(value))
+ .all(|(name, value)| target.field_ref(name) == Some(value))
}
Self::Label(label) => target.label() == Some(label),
Self::Regex(regex) => {
- target.id() == item!(text_id)
+ target.func() == item!(text_func)
&& item!(text_str)(target).map_or(false, |text| regex.is_match(&text))
}
Self::Any(selectors) => selectors.iter().any(|sel| sel.matches(target)),
@@ -285,8 +293,8 @@ impl Selector {
impl Debug for Selector {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
- Self::Node(node, dict) => {
- f.write_str(node.name)?;
+ Self::Elem(elem, dict) => {
+ f.write_str(elem.name())?;
if let Some(dict) = dict {
f.write_str(".where")?;
dict.fmt(f)?;
@@ -307,21 +315,24 @@ impl Debug for Selector {
cast_from_value! {
Selector: "selector",
- text: EcoString => Self::text(&text),
+ func: Func => func
+ .element()
+ .ok_or("only element functions can be used as selectors")?
+ .select(),
label: Label => Self::Label(label),
- func: Func => func.select(None)?,
+ text: EcoString => Self::text(&text),
regex: Regex => Self::Regex(regex),
}
/// A show rule transformation that can be applied to a match.
-#[derive(Clone, Hash)]
+#[derive(Clone, PartialEq, Hash)]
pub enum Transform {
/// Replacement content.
Content(Content),
/// A function to apply to the match.
Func(Func),
/// Apply styles to the content.
- Style(StyleMap),
+ Style(Styles),
}
impl Debug for Transform {
@@ -340,11 +351,11 @@ cast_from_value! {
func: Func => Self::Func(func),
}
-/// A chain of style maps, similar to a linked list.
+/// A chain of styles, similar to a linked list.
///
-/// A style chain allows to combine properties from multiple style maps in a
-/// node hierarchy in a non-allocating way. Rather than eagerly merging the
-/// maps, each access walks the hierarchy from the innermost to the outermost
+/// A style chain allows to combine properties from multiple style lists in a
+/// element hierarchy in a non-allocating way. Rather than eagerly merging the
+/// lists, each access walks the hierarchy from the innermost to the outermost
/// map, trying to find a match and then folding it with matches further up the
/// chain.
#[derive(Default, Clone, Copy, Hash)]
@@ -356,21 +367,21 @@ pub struct StyleChain<'a> {
}
impl<'a> StyleChain<'a> {
- /// Start a new style chain with a root map.
- pub fn new(root: &'a StyleMap) -> Self {
+ /// Start a new style chain with root styles.
+ pub fn new(root: &'a Styles) -> Self {
Self { head: &root.0, tail: None }
}
- /// Make the given map the first link of this chain.
+ /// Make the given style list the first link of this chain.
///
- /// The resulting style chain contains styles from `map` as well as
- /// `self`. The ones from `map` take precedence over the ones from
- /// `self`. For folded properties `map` contributes the inner value.
- pub fn chain<'b>(&'b self, map: &'b StyleMap) -> StyleChain<'b> {
- if map.is_empty() {
+ /// The resulting style chain contains styles from `local` as well as
+ /// `self`. The ones from `local` take precedence over the ones from
+ /// `self`. For folded properties `local` contributes the inner value.
+ pub fn chain<'b>(&'b self, local: &'b Styles) -> StyleChain<'b> {
+ if local.is_empty() {
*self
} else {
- StyleChain { head: &map.0, tail: Some(self) }
+ StyleChain { head: &local.0, tail: Some(self) }
}
}
@@ -385,12 +396,12 @@ impl<'a> StyleChain<'a> {
/// Cast the first value for the given property in the chain.
pub fn get<T: Cast>(
self,
- node: NodeId,
+ func: ElemFunc,
name: &'a str,
inherent: Option<Value>,
default: impl Fn() -> T,
) -> T {
- self.properties::<T>(node, name, inherent)
+ self.properties::<T>(func, name, inherent)
.next()
.unwrap_or_else(default)
}
@@ -398,18 +409,18 @@ impl<'a> StyleChain<'a> {
/// Cast the first value for the given property in the chain.
pub fn get_resolve<T: Cast + Resolve>(
self,
- node: NodeId,
+ func: ElemFunc,
name: &'a str,
inherent: Option<Value>,
default: impl Fn() -> T,
) -> T::Output {
- self.get(node, name, inherent, default).resolve(self)
+ self.get(func, name, inherent, default).resolve(self)
}
/// Cast the first value for the given property in the chain.
pub fn get_fold<T: Cast + Fold>(
self,
- node: NodeId,
+ func: ElemFunc,
name: &'a str,
inherent: Option<Value>,
default: impl Fn() -> T::Output,
@@ -424,13 +435,13 @@ impl<'a> StyleChain<'a> {
.map(|value| value.fold(next(values, styles, default)))
.unwrap_or_else(|| default())
}
- next(self.properties::<T>(node, name, inherent), self, &default)
+ next(self.properties::<T>(func, name, inherent), self, &default)
}
/// Cast the first value for the given property in the chain.
pub fn get_resolve_fold<T>(
self,
- node: NodeId,
+ func: ElemFunc,
name: &'a str,
inherent: Option<Value>,
default: impl Fn() -> <T::Output as Fold>::Output,
@@ -453,7 +464,7 @@ impl<'a> StyleChain<'a> {
.map(|value| value.resolve(styles).fold(next(values, styles, default)))
.unwrap_or_else(|| default())
}
- next(self.properties::<T>(node, name, inherent), self, &default)
+ next(self.properties::<T>(func, name, inherent), self, &default)
}
/// Iterate over all style recipes in the chain.
@@ -464,7 +475,7 @@ impl<'a> StyleChain<'a> {
/// Iterate over all values for the given property in the chain.
pub fn properties<T: Cast + 'a>(
self,
- node: NodeId,
+ func: ElemFunc,
name: &'a str,
inherent: Option<Value>,
) -> impl Iterator<Item = T> + '_ {
@@ -473,21 +484,21 @@ impl<'a> StyleChain<'a> {
.chain(
self.entries()
.filter_map(Style::property)
- .filter(move |property| property.is(node, name))
+ .filter(move |property| property.is(func, name))
.map(|property| property.value.clone()),
)
.map(move |value| {
- value
- .cast()
- .unwrap_or_else(|err| panic!("{} (for {}.{})", err, node.name, name))
+ value.cast().unwrap_or_else(|err| {
+ panic!("{} (for {}.{})", err, func.name(), name)
+ })
})
}
/// Convert to a style map.
- pub fn to_map(self) -> StyleMap {
- let mut suffix = StyleMap::new();
+ pub fn to_map(self) -> Styles {
+ let mut suffix = Styles::new();
for link in self.links() {
- suffix.0.splice(0..0, link.iter().cloned());
+ suffix.apply_slice(link);
}
suffix
}
@@ -502,13 +513,13 @@ impl<'a> StyleChain<'a> {
Links(Some(self))
}
- /// Build a style map from the suffix (all links beyond the `len`) of the
+ /// Build owned styles from the suffix (all links beyond the `len`) of the
/// chain.
- fn suffix(self, len: usize) -> StyleMap {
- let mut suffix = StyleMap::new();
+ fn suffix(self, len: usize) -> Styles {
+ let mut suffix = Styles::new();
let take = self.links().count().saturating_sub(len);
for link in self.links().take(take) {
- suffix.0.splice(0..0, link.iter().cloned());
+ suffix.apply_slice(link);
}
suffix
}
@@ -517,6 +528,16 @@ impl<'a> StyleChain<'a> {
fn pop(&mut self) {
*self = self.tail.copied().unwrap_or_default();
}
+
+ /// Whether two style chains contain the same pointers.
+ fn ptr_eq(self, other: Self) -> bool {
+ std::ptr::eq(self.head, other.head)
+ && match (self.tail, other.tail) {
+ (Some(a), Some(b)) => std::ptr::eq(a, b),
+ (None, None) => true,
+ _ => false,
+ }
+ }
}
impl Debug for StyleChain<'_> {
@@ -530,7 +551,7 @@ impl Debug for StyleChain<'_> {
impl PartialEq for StyleChain<'_> {
fn eq(&self, other: &Self) -> bool {
- crate::util::hash128(self) == crate::util::hash128(other)
+ self.ptr_eq(*other) || crate::util::hash128(self) == crate::util::hash128(other)
}
}
@@ -574,7 +595,7 @@ impl<'a> Iterator for Links<'a> {
#[derive(Clone, Hash)]
pub struct StyleVec<T> {
items: Vec<T>,
- maps: Vec<(StyleMap, usize)>,
+ styles: Vec<(Styles, usize)>,
}
impl<T> StyleVec<T> {
@@ -588,14 +609,14 @@ impl<T> StyleVec<T> {
self.items.len()
}
- /// Insert an element in the front. The element will share the style of the
- /// current first element.
+ /// Insert an item in the front. The item will share the style of the
+ /// current first item.
///
/// This method has no effect if the vector is empty.
pub fn push_front(&mut self, item: T) {
- if !self.maps.is_empty() {
+ if !self.styles.is_empty() {
self.items.insert(0, item);
- self.maps[0].1 += 1;
+ self.styles[0].1 += 1;
}
}
@@ -606,14 +627,14 @@ impl<T> StyleVec<T> {
{
StyleVec {
items: self.items.iter().map(f).collect(),
- maps: self.maps.clone(),
+ styles: self.styles.clone(),
}
}
- /// Iterate over references to the contained items and associated style maps.
- pub fn iter(&self) -> impl Iterator<Item = (&T, &StyleMap)> + '_ {
+ /// Iterate over references to the contained items and associated styles.
+ pub fn iter(&self) -> impl Iterator<Item = (&T, &Styles)> + '_ {
self.items().zip(
- self.maps
+ self.styles
.iter()
.flat_map(|(map, count)| iter::repeat(map).take(*count)),
)
@@ -624,13 +645,13 @@ impl<T> StyleVec<T> {
self.items.iter()
}
- /// Iterate over the contained maps. Note that zipping this with `items()`
- /// does not yield the same result as calling `iter()` because this method
- /// only returns maps once that are shared by consecutive items. This method
- /// is designed for use cases where you want to check, for example, whether
- /// any of the maps fulfills a specific property.
- pub fn styles(&self) -> impl Iterator<Item = &StyleMap> {
- self.maps.iter().map(|(map, _)| map)
+ /// Iterate over the contained style lists. Note that zipping this with
+ /// `items()` does not yield the same result as calling `iter()` because
+ /// this method only returns lists once that are shared by consecutive
+ /// items. This method is designed for use cases where you want to check,
+ /// for example, whether any of the lists fulfills a specific property.
+ pub fn styles(&self) -> impl Iterator<Item = &Styles> {
+ self.styles.iter().map(|(map, _)| map)
}
}
@@ -639,35 +660,35 @@ impl StyleVec<Content> {
self.items
.into_iter()
.zip(
- self.maps
+ self.styles
.iter()
.flat_map(|(map, count)| iter::repeat(map).take(*count)),
)
- .map(|(content, map)| content.styled_with_map(map.clone()))
+ .map(|(content, styles)| content.styled_with_map(styles.clone()))
.collect()
}
}
impl<T> Default for StyleVec<T> {
fn default() -> Self {
- Self { items: vec![], maps: vec![] }
+ Self { items: vec![], styles: vec![] }
}
}
impl<T> FromIterator<T> for StyleVec<T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let items: Vec<_> = iter.into_iter().collect();
- let maps = vec![(StyleMap::new(), items.len())];
- Self { items, maps }
+ let styles = vec![(Styles::new(), items.len())];
+ Self { items, styles }
}
}
impl<T: Debug> Debug for StyleVec<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_list()
- .entries(self.iter().map(|(item, map)| {
+ .entries(self.iter().map(|(item, styles)| {
crate::util::debug(|f| {
- map.fmt(f)?;
+ styles.fmt(f)?;
item.fmt(f)
})
}))
@@ -708,7 +729,7 @@ impl<'a, T> StyleVecBuilder<'a, T> {
}
/// Iterate over the contained items.
- pub fn items(&self) -> std::slice::Iter<'_, T> {
+ pub fn elems(&self) -> std::slice::Iter<'_, T> {
self.items.iter()
}
@@ -743,13 +764,13 @@ impl<'a, T> StyleVecBuilder<'a, T> {
}
}
- let maps = self
+ let styles = self
.chains
.into_iter()
.map(|(chain, count)| (chain.suffix(shared), count))
.collect();
- (StyleVec { items: self.items, maps }, trunk)
+ (StyleVec { items: self.items, styles }, trunk)
}
}
diff --git a/src/model/typeset.rs b/src/model/typeset.rs
deleted file mode 100644
index 8216d7a8..00000000
--- a/src/model/typeset.rs
+++ /dev/null
@@ -1,241 +0,0 @@
-use std::fmt::{self, Debug, Formatter};
-use std::hash::Hash;
-use std::num::NonZeroUsize;
-
-use comemo::{Constraint, Track, Tracked, TrackedMut};
-
-use super::{Content, Selector, StyleChain};
-use crate::diag::SourceResult;
-use crate::doc::{Document, Element, Frame, Location, Meta};
-use crate::eval::{cast_from_value, Tracer};
-use crate::geom::{Point, Transform};
-use crate::util::NonZeroExt;
-use crate::World;
-
-/// Typeset content into a fully layouted document.
-#[comemo::memoize]
-pub fn typeset(
- world: Tracked<dyn World>,
- mut tracer: TrackedMut<Tracer>,
- content: &Content,
-) -> SourceResult<Document> {
- let library = world.library();
- let styles = StyleChain::new(&library.styles);
-
- let mut document;
- let mut iter = 0;
- let mut introspector = Introspector::new(&[]);
-
- // Relayout until all introspections stabilize.
- // If that doesn't happen within five attempts, we give up.
- loop {
- let constraint = Constraint::new();
- let mut provider = StabilityProvider::new();
- let mut vt = Vt {
- world,
- tracer: TrackedMut::reborrow_mut(&mut tracer),
- provider: provider.track_mut(),
- introspector: introspector.track_with(&constraint),
- };
-
- document = (library.items.layout)(&mut vt, content, styles)?;
- iter += 1;
-
- introspector = Introspector::new(&document.pages);
- introspector.init = true;
-
- if iter >= 5 || introspector.valid(&constraint) {
- break;
- }
- }
-
- Ok(document)
-}
-
-/// A virtual typesetter.
-///
-/// Holds the state needed to [typeset] content.
-pub struct Vt<'a> {
- /// The compilation environment.
- pub world: Tracked<'a, dyn World>,
- /// The tracer for inspection of the values an expression produces.
- pub tracer: TrackedMut<'a, Tracer>,
- /// Provides stable identities to nodes.
- pub provider: TrackedMut<'a, StabilityProvider>,
- /// Provides access to information about the document.
- pub introspector: Tracked<'a, Introspector>,
-}
-
-impl Vt<'_> {
- /// Mutably reborrow with a shorter lifetime.
- pub fn reborrow_mut(&mut self) -> Vt<'_> {
- Vt {
- world: self.world,
- tracer: TrackedMut::reborrow_mut(&mut self.tracer),
- provider: TrackedMut::reborrow_mut(&mut self.provider),
- introspector: self.introspector,
- }
- }
-}
-
-/// Provides stable identities to nodes.
-#[derive(Clone)]
-pub struct StabilityProvider {
- hashes: Vec<u128>,
- checkpoints: Vec<usize>,
-}
-
-impl StabilityProvider {
- /// Create a new stability provider.
- pub fn new() -> Self {
- Self { hashes: vec![], checkpoints: vec![] }
- }
-}
-
-#[comemo::track]
-impl StabilityProvider {
- /// Produce a stable identifier for this call site.
- pub fn identify(&mut self, hash: u128) -> StableId {
- let count = self.hashes.iter().filter(|&&prev| prev == hash).count();
- self.hashes.push(hash);
- StableId(hash, count, 0)
- }
-
- /// Create a checkpoint of the state that can be restored.
- pub fn save(&mut self) {
- self.checkpoints.push(self.hashes.len());
- }
-
- /// Restore the last checkpoint.
- pub fn restore(&mut self) {
- if let Some(checkpoint) = self.checkpoints.pop() {
- self.hashes.truncate(checkpoint);
- }
- }
-}
-
-/// Stably identifies a call site across multiple layout passes.
-///
-/// This struct is created by [`StabilityProvider::identify`].
-#[derive(Copy, Clone, Eq, PartialEq, Hash)]
-pub struct StableId(u128, usize, usize);
-
-impl StableId {
- /// Produce a variant of this id.
- pub fn variant(self, n: usize) -> Self {
- Self(self.0, self.1, n)
- }
-}
-
-impl Debug for StableId {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.pad("..")
- }
-}
-
-cast_from_value! {
- StableId: "stable id",
-}
-
-/// Provides access to information about the document.
-pub struct Introspector {
- init: bool,
- pages: usize,
- nodes: Vec<(Content, Location)>,
-}
-
-impl Introspector {
- /// Create a new introspector.
- pub fn new(frames: &[Frame]) -> Self {
- let mut introspector = Self { init: false, pages: frames.len(), nodes: vec![] };
- for (i, frame) in frames.iter().enumerate() {
- let page = NonZeroUsize::new(1 + i).unwrap();
- introspector.extract(frame, page, Transform::identity());
- }
- introspector
- }
-
- /// Iterate over all nodes.
- pub fn all(&self) -> impl Iterator<Item = &Content> {
- self.nodes.iter().map(|(node, _)| node)
- }
-
- /// Extract metadata from a frame.
- fn extract(&mut self, frame: &Frame, page: NonZeroUsize, ts: Transform) {
- for (pos, element) in frame.elements() {
- match element {
- Element::Group(group) => {
- let ts = ts
- .pre_concat(Transform::translate(pos.x, pos.y))
- .pre_concat(group.transform);
- self.extract(&group.frame, page, ts);
- }
- Element::Meta(Meta::Node(content), _)
- if !self
- .nodes
- .iter()
- .any(|(prev, _)| prev.stable_id() == content.stable_id()) =>
- {
- let pos = pos.transform(ts);
- self.nodes.push((content.clone(), Location { page, pos }));
- }
- _ => {}
- }
- }
- }
-}
-
-#[comemo::track]
-impl Introspector {
- /// Whether this introspector is not yet initialized.
- pub fn init(&self) -> bool {
- self.init
- }
-
- /// Query for all nodes for the given selector.
- pub fn query(&self, selector: Selector) -> Vec<Content> {
- self.all().filter(|node| selector.matches(node)).cloned().collect()
- }
-
- /// Query for all nodes up to the given id.
- pub fn query_before(&self, selector: Selector, id: StableId) -> Vec<Content> {
- let mut matches = vec![];
- for node in self.all() {
- if selector.matches(node) {
- matches.push(node.clone());
- }
- if node.stable_id() == Some(id) {
- break;
- }
- }
- matches
- }
-
- /// Query for all nodes starting from the given id.
- pub fn query_after(&self, selector: Selector, id: StableId) -> Vec<Content> {
- self.all()
- .skip_while(|node| node.stable_id() != Some(id))
- .filter(|node| selector.matches(node))
- .cloned()
- .collect()
- }
-
- /// The total number pages.
- pub fn pages(&self) -> NonZeroUsize {
- NonZeroUsize::new(self.pages).unwrap_or(NonZeroUsize::ONE)
- }
-
- /// Find the page number for the given stable id.
- pub fn page(&self, id: StableId) -> NonZeroUsize {
- self.location(id).page
- }
-
- /// Find the location for the given stable id.
- pub fn location(&self, id: StableId) -> Location {
- self.nodes
- .iter()
- .find(|(node, _)| node.stable_id() == Some(id))
- .map(|(_, loc)| *loc)
- .unwrap_or(Location { page: NonZeroUsize::ONE, pos: Point::zero() })
- }
-}