diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-03-17 11:32:15 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-03-17 11:45:57 +0100 |
| commit | 312197b276748e1a17258ad21837850f582a467c (patch) | |
| tree | 3fd0c078a2673a98b74bc12b4d654a4c143b4e1f /src/model | |
| parent | e8435df5ec718e8ecc8a2ad48e4eb3ddd1f92a72 (diff) | |
Counters
Diffstat (limited to 'src/model')
| -rw-r--r-- | src/model/content.rs | 48 | ||||
| -rw-r--r-- | src/model/realize.rs | 52 | ||||
| -rw-r--r-- | src/model/styles.rs | 16 | ||||
| -rw-r--r-- | src/model/typeset.rs | 168 |
4 files changed, 156 insertions, 128 deletions
diff --git a/src/model/content.rs b/src/model/content.rs index 58b80487..11ad635f 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -8,8 +8,12 @@ use comemo::Tracked; use ecow::{eco_format, EcoString, EcoVec}; use once_cell::sync::Lazy; -use super::{node, Guard, Locatable, Recipe, StableId, Style, StyleMap, Synthesize}; +use super::{ + node, Behave, Behaviour, Fold, Guard, Locatable, Recipe, StableId, Style, StyleMap, + 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, }; @@ -35,9 +39,15 @@ enum Modifier { } impl Content { + /// Create a content of the given node kind. pub fn new<T: Node>() -> Self { + Self::new_of(T::id()) + } + + /// Create a content of the given node kind. + pub fn new_of(id: NodeId) -> Self { Self { - id: T::id(), + id, span: Span::detached(), fields: EcoVec::new(), modifiers: EcoVec::new(), @@ -133,11 +143,10 @@ impl Content { .map(|(_, value)| value) } - /// Access a field on the content as a specified type. - #[track_caller] + /// 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) => Some(value.clone().cast().unwrap()), + Some(value) => value.clone().cast().ok(), None => None, } } @@ -145,7 +154,7 @@ impl Content { /// 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.cast_field(name).unwrap() + self.field(name).unwrap().clone().cast().unwrap() } /// List all fields on the content. @@ -500,6 +509,33 @@ cast_from_value! { StyleMap: "style map", } +/// Host for metadata. +/// +/// Display: Meta +/// Category: special +#[node(Behave)] +pub struct MetaNode { + /// Metadata that should be attached to all elements affected by this style + /// property. + #[fold] + pub data: Vec<Meta>, +} + +impl Behave for MetaNode { + fn behaviour(&self) -> Behaviour { + Behaviour::Ignorant + } +} + +impl Fold for Vec<Meta> { + type Output = Self; + + fn fold(mut self, outer: Self::Output) -> Self::Output { + self.extend(outer); + self + } +} + /// The missing key access error message. #[cold] #[track_caller] diff --git a/src/model/realize.rs b/src/model/realize.rs index 7a171cfc..70c75644 100644 --- a/src/model/realize.rs +++ b/src/model/realize.rs @@ -1,6 +1,7 @@ -use super::{Content, NodeId, Recipe, Selector, StyleChain, Vt}; +use super::{Content, MetaNode, Node, NodeId, Recipe, Selector, StyleChain, Vt}; use crate::diag::SourceResult; -use crate::doc::{Meta, MetaNode}; +use crate::doc::Meta; +use crate::util::hash128; /// Whether the target is affected by show rules in the given style chain. pub fn applicable(target: &Content, styles: StyleChain) -> bool { @@ -36,7 +37,7 @@ pub fn realize( if target.needs_preparation() { let mut node = target.clone(); if target.can::<dyn Locatable>() || target.label().is_some() { - let id = vt.identify(target); + let id = vt.provider.identify(hash128(target)); node.set_stable_id(id); } @@ -47,8 +48,12 @@ pub fn realize( node.mark_prepared(); if node.stable_id().is_some() { + let span = node.span(); let meta = Meta::Node(node.clone()); - return Ok(Some(node.styled(MetaNode::set_data(vec![meta])))); + return Ok(Some( + (node + MetaNode::new().pack().spanned(span)) + .styled(MetaNode::set_data(vec![meta])), + )); } return Ok(Some(node)); @@ -103,7 +108,7 @@ fn try_apply( return Ok(None); } - recipe.apply(vt.world(), target.clone().guarded(guard)).map(Some) + recipe.apply(vt.world, target.clone().guarded(guard)).map(Some) } Some(Selector::Label(label)) => { @@ -111,7 +116,7 @@ fn try_apply( return Ok(None); } - recipe.apply(vt.world(), target.clone().guarded(guard)).map(Some) + recipe.apply(vt.world, target.clone().guarded(guard)).map(Some) } Some(Selector::Regex(regex)) => { @@ -135,7 +140,7 @@ fn try_apply( } let piece = make(m.as_str().into()).guarded(guard); - let transformed = recipe.apply(vt.world(), piece)?; + let transformed = recipe.apply(vt.world, piece)?; result.push(transformed); cursor = m.end(); } @@ -151,6 +156,9 @@ fn try_apply( Ok(Some(Content::sequence(result))) } + // Not supported here. + Some(Selector::Any(_)) => Ok(None), + None => Ok(None), } } @@ -178,6 +186,36 @@ pub trait Finalize { fn finalize(&self, realized: Content, styles: StyleChain) -> Content; } +/// How a node interacts with other nodes. +pub trait Behave { + /// The node'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. + #[allow(unused_variables)] + fn larger(&self, prev: &Content) -> bool { + false + } +} + +/// How a node interacts with other nodes 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). + Weak(usize), + /// A node that enables adjacent weak nodes to exist. The default. + Supportive, + /// A node that destroys adjacent weak nodes. + Destructive, + /// A node that does not interact at all with other nodes, having the + /// same effect as if it didn't exist. + Ignorant, +} + /// Guards content against being affected by the same show rule multiple times. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum Guard { diff --git a/src/model/styles.rs b/src/model/styles.rs index 9a562c75..359f1461 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -2,7 +2,7 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::iter; use comemo::Tracked; -use ecow::{eco_format, EcoString}; +use ecow::{eco_format, EcoString, EcoVec}; use super::{Content, Label, Node, NodeId}; use crate::diag::{SourceResult, Trace, Tracepoint}; @@ -31,8 +31,8 @@ impl StyleMap { /// If the property needs folding and the value is already contained in the /// style map, `self` contributes the outer values and `value` is the inner /// one. - pub fn set(&mut self, property: Property) { - self.0.push(Style::Property(property)); + pub fn set(&mut self, style: impl Into<Style>) { + self.0.push(style.into()); } /// Remove the style that was last set. @@ -243,6 +243,8 @@ pub enum Selector { Label(Label), /// Matches text nodes through a regular expression. Regex(Regex), + /// Matches if any of the subselectors match. + Any(EcoVec<Self>), } impl Selector { @@ -271,6 +273,7 @@ impl Selector { target.id() == item!(text_id) && item!(text_str)(target).map_or(false, |text| regex.is_match(&text)) } + Self::Any(selectors) => selectors.iter().any(|sel| sel.matches(target)), } } } @@ -288,6 +291,12 @@ impl Debug for Selector { } Self::Label(label) => label.fmt(f), Self::Regex(regex) => regex.fmt(f), + Self::Any(selectors) => { + f.write_str("any")?; + let pieces: Vec<_> = + selectors.iter().map(|sel| eco_format!("{sel:?}")).collect(); + f.write_str(&pretty_array_like(&pieces, false)) + } } } } @@ -659,6 +668,7 @@ impl<T: Debug> Debug for StyleVec<T> { } /// Assists in the construction of a [`StyleVec`]. +#[derive(Debug)] pub struct StyleVecBuilder<'a, T> { items: Vec<T>, chains: Vec<(StyleChain<'a>, usize)>, diff --git a/src/model/typeset.rs b/src/model/typeset.rs index f68d337d..4c8be135 100644 --- a/src/model/typeset.rs +++ b/src/model/typeset.rs @@ -1,15 +1,13 @@ -use std::cell::RefCell; -use std::collections::HashMap; use std::hash::Hash; use std::num::NonZeroUsize; -use comemo::{Track, Tracked, TrackedMut}; +use comemo::{Constraint, Track, Tracked, TrackedMut}; -use super::{Content, Node, Selector, StyleChain}; +use super::{Content, Selector, StyleChain}; use crate::diag::SourceResult; use crate::doc::{Document, Element, Frame, Location, Meta}; -use crate::geom::Transform; -use crate::util::hash128; +use crate::geom::{Point, Transform}; +use crate::util::NonZeroExt; use crate::World; /// Typeset content into a fully layouted document. @@ -25,17 +23,21 @@ pub fn typeset(world: Tracked<dyn World>, content: &Content) -> SourceResult<Doc // 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, provider: provider.track_mut(), - introspector: introspector.track(), + introspector: introspector.track_with(&constraint), }; document = (library.items.layout)(&mut vt, content, styles)?; iter += 1; - if iter >= 5 || introspector.update(&document.pages) { + introspector = Introspector::new(&document.pages); + introspector.init = true; + + if iter >= 5 || introspector.valid(&constraint) { break; } } @@ -56,137 +58,86 @@ pub struct Vt<'a> { pub introspector: Tracked<'a, Introspector>, } -impl<'a> Vt<'a> { - /// Access the underlying world. - pub fn world(&self) -> Tracked<'a, dyn World> { - self.world +/// 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. - /// - /// The key should be something that identifies the call site, but is not - /// necessarily unique. The stable marker incorporates the key's hash plus - /// additional disambiguation from other call sites with the same key. - /// - /// The returned id can be attached to content as metadata is the then - /// locatable through [`locate`](Self::locate). - pub fn identify<T: Hash>(&mut self, key: &T) -> StableId { - self.provider.identify(hash128(key)) + 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) } - /// Whether things are locatable already. - pub fn locatable(&self) -> bool { - self.introspector.init() + /// Create a checkpoint of the state that can be restored. + pub fn save(&mut self) { + self.checkpoints.push(self.hashes.len()); } - /// Locate all metadata matches for the given node. - pub fn query_node<T: Node>(&self) -> impl Iterator<Item = &T> { - self.introspector - .query(Selector::node::<T>()) - .into_iter() - .map(|content| content.to::<T>().unwrap()) + /// 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 [`Vt::identify`]. +/// This struct is created by [`StabilityProvider::identify`]. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct StableId(u128, u64, u64); +pub struct StableId(u128, usize, usize); impl StableId { /// Produce a variant of this id. - pub fn variant(self, n: u64) -> Self { + pub fn variant(self, n: usize) -> Self { Self(self.0, self.1, n) } } -/// Provides stable identities to nodes. -#[derive(Clone)] -pub struct StabilityProvider(HashMap<u128, u64>); - -impl StabilityProvider { - /// Create a new stability provider. - fn new() -> Self { - Self(HashMap::new()) - } -} - -#[comemo::track] -impl StabilityProvider { - /// Produce a stable identifier for this call site. - fn identify(&mut self, hash: u128) -> StableId { - let slot = self.0.entry(hash).or_default(); - let id = StableId(hash, *slot, 0); - *slot += 1; - id - } -} - /// Provides access to information about the document. pub struct Introspector { init: bool, nodes: Vec<(Content, Location)>, - queries: RefCell<Vec<(Selector, u128)>>, } impl Introspector { /// Create a new introspector. pub fn new(frames: &[Frame]) -> Self { - let mut introspector = Self { - init: false, - nodes: vec![], - queries: RefCell::new(vec![]), - }; - introspector.extract_from_frames(frames); - introspector - } - - /// Update the information given new frames and return whether we can stop - /// layouting. - pub fn update(&mut self, frames: &[Frame]) -> bool { - self.nodes.clear(); - self.extract_from_frames(frames); - - let was_init = std::mem::replace(&mut self.init, true); - let queries = std::mem::take(&mut self.queries).into_inner(); - - for (selector, hash) in &queries { - let nodes = self.query_impl(selector); - if hash128(&nodes) != *hash { - return false; - } - } - - if !was_init && !queries.is_empty() { - return false; + let mut introspector = Self { init: false, nodes: vec![] }; + for (i, frame) in frames.iter().enumerate() { + let page = NonZeroUsize::new(1 + i).unwrap(); + introspector.extract(frame, page, Transform::identity()); } - - true + introspector } /// Iterate over all nodes. - pub fn nodes(&self) -> impl Iterator<Item = &Content> { + pub fn all(&self) -> impl Iterator<Item = &Content> { self.nodes.iter().map(|(node, _)| node) } - /// Extract metadata from frames. - fn extract_from_frames(&mut self, frames: &[Frame]) { - for (i, frame) in frames.iter().enumerate() { - let page = NonZeroUsize::new(1 + i).unwrap(); - self.extract_from_frame(frame, page, Transform::identity()); - } - } - /// Extract metadata from a frame. - fn extract_from_frame(&mut self, frame: &Frame, page: NonZeroUsize, ts: Transform) { + 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_from_frame(&group.frame, page, ts); + self.extract(&group.frame, page, ts); } Element::Meta(Meta::Node(content), _) if !self @@ -212,27 +163,20 @@ impl Introspector { /// Query for all metadata matches for the given selector. pub fn query(&self, selector: Selector) -> Vec<&Content> { - let nodes = self.query_impl(&selector); - let mut queries = self.queries.borrow_mut(); - if !queries.iter().any(|(prev, _)| prev == &selector) { - queries.push((selector, hash128(&nodes))); - } - nodes + self.all().filter(|node| selector.matches(node)).collect() } /// Find the page number for the given stable id. - pub fn page(&self, id: StableId) -> Option<NonZeroUsize> { - Some(self.location(id)?.page) + 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) -> Option<Location> { - Some(self.nodes.iter().find(|(node, _)| node.stable_id() == Some(id))?.1) - } -} - -impl Introspector { - fn query_impl(&self, selector: &Selector) -> Vec<&Content> { - self.nodes().filter(|node| selector.matches(node)).collect() + 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() }) } } |
