diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-03-19 22:28:49 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-03-19 22:39:19 +0100 |
| commit | ab43bd802eafe33977a91893907e67553e099569 (patch) | |
| tree | af4dead92b143348f52e2e8f869df3f7dfd7322a /src/model | |
| parent | d6aaae0cea1e79eecd85dc94ab85b9ad8eff48e8 (diff) | |
Renaming and refactoring
Diffstat (limited to 'src/model')
| -rw-r--r-- | src/model/content.rs | 538 | ||||
| -rw-r--r-- | src/model/element.rs | 145 | ||||
| -rw-r--r-- | src/model/introspect.rs | 170 | ||||
| -rw-r--r-- | src/model/mod.rs | 83 | ||||
| -rw-r--r-- | src/model/realize.rs | 96 | ||||
| -rw-r--r-- | src/model/styles.rs | 271 | ||||
| -rw-r--r-- | src/model/typeset.rs | 241 |
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(®ex::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() }) - } -} |
