summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-06-09 15:23:56 +0200
committerGitHub <noreply@github.com>2024-06-09 13:23:56 +0000
commitf91cad7d7829556e24d219e55db7da56a966523f (patch)
treeadce387eb0008198852dd243693512237d3b5f2a
parentcc3e9c86022c5ae5a82938b0b65e3d2dca93b1ed (diff)
Pure location assignment (#4352)
-rw-r--r--crates/typst-ide/src/analyze.rs4
-rw-r--r--crates/typst-timing/src/lib.rs2
-rw-r--r--crates/typst-utils/src/hash.rs19
-rw-r--r--crates/typst/src/diag.rs3
-rw-r--r--crates/typst/src/engine.rs8
-rw-r--r--crates/typst/src/eval/call.rs5
-rw-r--r--crates/typst/src/eval/mod.rs6
-rw-r--r--crates/typst/src/foundations/content.rs4
-rw-r--r--crates/typst/src/foundations/func.rs1
-rw-r--r--crates/typst/src/introspection/counter.rs10
-rw-r--r--crates/typst/src/introspection/introspector.rs65
-rw-r--r--crates/typst/src/introspection/location.rs32
-rw-r--r--crates/typst/src/introspection/locator.rs399
-rw-r--r--crates/typst/src/introspection/mod.rs37
-rw-r--r--crates/typst/src/introspection/state.rs6
-rw-r--r--crates/typst/src/layout/columns.rs9
-rw-r--r--crates/typst/src/layout/container.rs27
-rw-r--r--crates/typst/src/layout/flow.rs68
-rw-r--r--crates/typst/src/layout/frame.rs10
-rw-r--r--crates/typst/src/layout/grid/cells.rs72
-rw-r--r--crates/typst/src/layout/grid/layout.rs116
-rw-r--r--crates/typst/src/layout/grid/lines.rs11
-rw-r--r--crates/typst/src/layout/grid/mod.rs13
-rw-r--r--crates/typst/src/layout/grid/repeated.rs42
-rw-r--r--crates/typst/src/layout/grid/rowspans.rs50
-rw-r--r--crates/typst/src/layout/inline/mod.rs44
-rw-r--r--crates/typst/src/layout/layout.rs34
-rw-r--r--crates/typst/src/layout/measure.rs22
-rw-r--r--crates/typst/src/layout/mod.rs47
-rw-r--r--crates/typst/src/layout/pad.rs8
-rw-r--r--crates/typst/src/layout/page.rs13
-rw-r--r--crates/typst/src/layout/place.rs4
-rw-r--r--crates/typst/src/layout/repeat.rs9
-rw-r--r--crates/typst/src/layout/stack.rs30
-rw-r--r--crates/typst/src/layout/transform.rs29
-rw-r--r--crates/typst/src/lib.rs4
-rw-r--r--crates/typst/src/math/ctx.rs27
-rw-r--r--crates/typst/src/math/equation.rs22
-rw-r--r--crates/typst/src/math/mod.rs6
-rw-r--r--crates/typst/src/model/cite.rs15
-rw-r--r--crates/typst/src/model/document.rs12
-rw-r--r--crates/typst/src/model/enum.rs18
-rw-r--r--crates/typst/src/model/heading.rs17
-rw-r--r--crates/typst/src/model/list.rs19
-rw-r--r--crates/typst/src/model/par.rs3
-rw-r--r--crates/typst/src/model/table.rs13
-rw-r--r--crates/typst/src/realize/mod.rs21
-rw-r--r--crates/typst/src/realize/process.rs21
-rw-r--r--crates/typst/src/visualize/image/mod.rs5
-rw-r--r--crates/typst/src/visualize/line.rs6
-rw-r--r--crates/typst/src/visualize/path.rs6
-rw-r--r--crates/typst/src/visualize/pattern.rs4
-rw-r--r--crates/typst/src/visualize/polygon.rs6
-rw-r--r--crates/typst/src/visualize/shape.rs156
-rw-r--r--tests/ref/issue-2480-counter-reset-2.pngbin0 -> 576 bytes
-rw-r--r--tests/ref/issue-2480-counter-reset.pngbin0 -> 355 bytes
-rw-r--r--tests/ref/measure-citation-deeply-nested.pngbin0 -> 706 bytes
-rw-r--r--tests/ref/measure-citation-in-flow.pngbin0 -> 743 bytes
-rw-r--r--tests/ref/measure-counter-multiple-times.pngbin0 -> 471 bytes
-rw-r--r--tests/ref/measure-counter-width.pngbin0 -> 2754 bytes
-rw-r--r--tests/ref/table-contextual-measurement.pngbin0 -> 453 bytes
-rw-r--r--tests/ref/table-header-citation.pngbin0 -> 624 bytes
-rw-r--r--tests/ref/table-header-counter.pngbin0 -> 359 bytes
-rw-r--r--tests/ref/table-header-footer-madness.pngbin0 -> 613 bytes
-rw-r--r--tests/suite/introspection/counter.typ28
-rw-r--r--tests/suite/layout/measure.typ72
-rw-r--r--tests/suite/layout/table.typ49
67 files changed, 1276 insertions, 513 deletions
diff --git a/crates/typst-ide/src/analyze.rs b/crates/typst-ide/src/analyze.rs
index 748970b8..d27ce176 100644
--- a/crates/typst-ide/src/analyze.rs
+++ b/crates/typst-ide/src/analyze.rs
@@ -3,7 +3,7 @@ use ecow::{eco_vec, EcoString, EcoVec};
use typst::engine::{Engine, Route};
use typst::eval::{Tracer, Vm};
use typst::foundations::{Context, Label, Scopes, Styles, Value};
-use typst::introspection::{Introspector, Locator};
+use typst::introspection::Introspector;
use typst::model::{BibliographyElem, Document};
use typst::syntax::{ast, LinkedNode, Span, SyntaxKind};
use typst::World;
@@ -58,14 +58,12 @@ pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option<Value> {
return Some(source);
}
- let mut locator = Locator::default();
let introspector = Introspector::default();
let mut tracer = Tracer::new();
let engine = Engine {
world: world.track(),
route: Route::default(),
introspector: introspector.track(),
- locator: &mut locator,
tracer: tracer.track_mut(),
};
diff --git a/crates/typst-timing/src/lib.rs b/crates/typst-timing/src/lib.rs
index a972cf72..4e711d5f 100644
--- a/crates/typst-timing/src/lib.rs
+++ b/crates/typst-timing/src/lib.rs
@@ -59,6 +59,8 @@ enum EventKind {
/// Enable the timer.
#[inline]
pub fn enable() {
+ // We only need atomicity and no synchronization of other
+ // operations, so `Relaxed` is fine.
ENABLED.store(true, Relaxed);
}
diff --git a/crates/typst-utils/src/hash.rs b/crates/typst-utils/src/hash.rs
index 82bb76af..dabae24c 100644
--- a/crates/typst-utils/src/hash.rs
+++ b/crates/typst-utils/src/hash.rs
@@ -2,8 +2,9 @@ use std::any::Any;
use std::fmt::{self, Debug};
use std::hash::{Hash, Hasher};
use std::ops::{Deref, DerefMut};
+use std::sync::atomic::Ordering;
-use portable_atomic::{AtomicU128, Ordering};
+use portable_atomic::AtomicU128;
use siphasher::sip128::{Hasher128, SipHasher13};
/// A wrapper type with lazily-computed hash.
@@ -70,7 +71,9 @@ impl<T: ?Sized> LazyHash<T> {
/// Get the hash, returns zero if not computed yet.
#[inline]
fn load_hash(&self) -> u128 {
- self.hash.load(Ordering::SeqCst)
+ // We only need atomicity and no synchronization of other operations, so
+ // `Relaxed` is fine.
+ self.hash.load(Ordering::Relaxed)
}
}
@@ -78,20 +81,18 @@ impl<T: Hash + ?Sized + 'static> LazyHash<T> {
/// Get the hash or compute it if not set yet.
#[inline]
fn load_or_compute_hash(&self) -> u128 {
- let hash = self.load_hash();
+ let mut hash = self.load_hash();
if hash == 0 {
- let hashed = hash_item(&self.value);
- self.hash.store(hashed, Ordering::SeqCst);
- hashed
- } else {
- hash
+ hash = hash_item(&self.value);
+ self.hash.store(hash, Ordering::Relaxed);
}
+ hash
}
/// Reset the hash to zero.
#[inline]
fn reset_hash(&mut self) {
- // Because we have a mutable reference, we can skip the atomic
+ // Because we have a mutable reference, we can skip the atomic.
*self.hash.get_mut() = 0;
}
}
diff --git a/crates/typst/src/diag.rs b/crates/typst/src/diag.rs
index 15e6a49f..db8a93fd 100644
--- a/crates/typst/src/diag.rs
+++ b/crates/typst/src/diag.rs
@@ -366,13 +366,12 @@ where
}
}
-impl<T> At<T> for Result<T, HintedString> {
+impl<T> At<T> for HintedStrResult<T> {
fn at(self, span: Span) -> SourceResult<T> {
self.map_err(|err| {
let mut components = err.0.into_iter();
let message = components.next().unwrap();
let diag = SourceDiagnostic::error(span, message).with_hints(components);
-
eco_vec![diag]
})
}
diff --git a/crates/typst/src/engine.rs b/crates/typst/src/engine.rs
index 03e4957c..a57a8f1c 100644
--- a/crates/typst/src/engine.rs
+++ b/crates/typst/src/engine.rs
@@ -6,7 +6,7 @@ use comemo::{Track, Tracked, TrackedMut, Validate};
use crate::diag::SourceResult;
use crate::eval::Tracer;
-use crate::introspection::{Introspector, Locator};
+use crate::introspection::Introspector;
use crate::syntax::FileId;
use crate::World;
@@ -19,8 +19,6 @@ pub struct Engine<'a> {
/// The route the engine took during compilation. This is used to detect
/// cyclic imports and excessive nesting.
pub route: Route<'a>,
- /// Provides stable identities to elements.
- pub locator: &'a mut Locator<'a>,
/// The tracer for inspection of the values an expression produces.
pub tracer: TrackedMut<'a, Tracer>,
}
@@ -148,6 +146,8 @@ impl<'a> Route<'a> {
/// Whether the route's depth is less than or equal to the given depth.
pub fn within(&self, depth: usize) -> bool {
+ // We only need atomicity and no synchronization of other operations, so
+ // `Relaxed` is fine.
use Ordering::Relaxed;
let upper = self.upper.load(Relaxed);
@@ -183,8 +183,6 @@ impl Clone for Route<'_> {
outer: self.outer,
id: self.id,
len: self.len,
- // The ordering doesn't really matter since it's the upper bound
- // is only an optimization.
upper: AtomicUsize::new(self.upper.load(Ordering::Relaxed)),
}
}
diff --git a/crates/typst/src/eval/call.rs b/crates/typst/src/eval/call.rs
index 61e5f318..d4b81f02 100644
--- a/crates/typst/src/eval/call.rs
+++ b/crates/typst/src/eval/call.rs
@@ -8,7 +8,7 @@ use crate::foundations::{
call_method_mut, is_mutating_method, Arg, Args, Bytes, Capturer, Closure, Content,
Context, Func, IntoValue, NativeElement, Scope, Scopes, Value,
};
-use crate::introspection::{Introspector, Locator};
+use crate::introspection::Introspector;
use crate::math::{Accent, AccentElem, LrElem};
use crate::symbols::Symbol;
use crate::syntax::ast::{self, AstNode};
@@ -276,7 +276,6 @@ pub(crate) fn call_closure(
world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>,
route: Tracked<Route>,
- locator: Tracked<Locator>,
tracer: TrackedMut<Tracer>,
context: Tracked<Context>,
mut args: Args,
@@ -292,12 +291,10 @@ pub(crate) fn call_closure(
scopes.top = closure.captured.clone();
// Prepare the engine.
- let mut locator = Locator::chained(locator);
let engine = Engine {
world,
introspector,
route: Route::extend(route),
- locator: &mut locator,
tracer,
};
diff --git a/crates/typst/src/eval/mod.rs b/crates/typst/src/eval/mod.rs
index 3622c012..2e5eeafd 100644
--- a/crates/typst/src/eval/mod.rs
+++ b/crates/typst/src/eval/mod.rs
@@ -28,7 +28,7 @@ use comemo::{Track, Tracked, TrackedMut};
use crate::diag::{bail, SourceResult};
use crate::engine::{Engine, Route};
use crate::foundations::{Cast, Context, Module, NativeElement, Scope, Scopes, Value};
-use crate::introspection::{Introspector, Locator};
+use crate::introspection::Introspector;
use crate::math::EquationElem;
use crate::syntax::{ast, parse, parse_code, parse_math, Source, Span};
use crate::World;
@@ -49,13 +49,11 @@ pub fn eval(
}
// Prepare the engine.
- let mut locator = Locator::new();
let introspector = Introspector::default();
let engine = Engine {
world,
route: Route::extend(route).with_id(id),
introspector: introspector.track(),
- locator: &mut locator,
tracer,
};
@@ -118,13 +116,11 @@ pub fn eval_string(
// Prepare the engine.
let mut tracer = Tracer::new();
- let mut locator = Locator::new();
let introspector = Introspector::default();
let engine = Engine {
world,
introspector: introspector.track(),
route: Route::default(),
- locator: &mut locator,
tracer: tracer.track_mut(),
};
diff --git a/crates/typst/src/foundations/content.rs b/crates/typst/src/foundations/content.rs
index 4cae8db7..8a3252fb 100644
--- a/crates/typst/src/foundations/content.rs
+++ b/crates/typst/src/foundations/content.rs
@@ -18,7 +18,7 @@ use crate::foundations::{
NativeElement, Recipe, RecipeIndex, Repr, Selector, Str, Style, StyleChain, Styles,
Value,
};
-use crate::introspection::{Location, TagElem};
+use crate::introspection::Location;
use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides};
use crate::model::{Destination, EmphElem, LinkElem, StrongElem};
use crate::realize::{Behave, Behaviour};
@@ -494,7 +494,7 @@ impl Content {
pub fn backlinked(self, loc: Location) -> Self {
let mut backlink = Content::empty().spanned(self.span());
backlink.set_location(loc);
- TagElem::packed(backlink) + self
+ self
}
/// Set alignments for this content.
diff --git a/crates/typst/src/foundations/func.rs b/crates/typst/src/foundations/func.rs
index 21ded1f2..f5b4b3ca 100644
--- a/crates/typst/src/foundations/func.rs
+++ b/crates/typst/src/foundations/func.rs
@@ -298,7 +298,6 @@ impl Func {
engine.world,
engine.introspector,
engine.route.track(),
- engine.locator.track(),
TrackedMut::reborrow_mut(&mut engine.tracer),
context,
args,
diff --git a/crates/typst/src/introspection/counter.rs b/crates/typst/src/introspection/counter.rs
index 2a7e9ea7..df16026f 100644
--- a/crates/typst/src/introspection/counter.rs
+++ b/crates/typst/src/introspection/counter.rs
@@ -13,7 +13,7 @@ use crate::foundations::{
Element, Func, IntoValue, Label, LocatableSelector, NativeElement, Packed, Repr,
Selector, Show, Smart, Str, StyleChain, Value,
};
-use crate::introspection::{Introspector, Locatable, Location, Locator};
+use crate::introspection::{Introspector, Locatable, Location};
use crate::layout::{Frame, FrameItem, PageElem};
use crate::math::EquationElem;
use crate::model::{FigureElem, HeadingElem, Numbering, NumberingPattern};
@@ -282,7 +282,6 @@ impl Counter {
engine.world,
engine.introspector,
engine.route.track(),
- engine.locator.track(),
TrackedMut::reborrow_mut(&mut engine.tracer),
)
}
@@ -294,15 +293,12 @@ impl Counter {
world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>,
route: Tracked<Route>,
- locator: Tracked<Locator>,
tracer: TrackedMut<Tracer>,
) -> SourceResult<EcoVec<(CounterState, NonZeroUsize)>> {
- let mut locator = Locator::chained(locator);
let mut engine = Engine {
world,
introspector,
route: Route::extend(route).unnested(),
- locator: &mut locator,
tracer,
};
@@ -815,8 +811,8 @@ impl ManualPageCounter {
for (_, item) in page.items() {
match item {
FrameItem::Group(group) => self.visit(engine, &group.frame)?,
- FrameItem::Tag(elem) => {
- let Some(elem) = elem.to_packed::<CounterUpdateElem>() else {
+ FrameItem::Tag(tag) => {
+ let Some(elem) = tag.elem.to_packed::<CounterUpdateElem>() else {
continue;
};
if *elem.key() == CounterKey::Page {
diff --git a/crates/typst/src/introspection/introspector.rs b/crates/typst/src/introspection/introspector.rs
index ebd787fe..fe59cb00 100644
--- a/crates/typst/src/introspection/introspector.rs
+++ b/crates/typst/src/introspection/introspector.rs
@@ -16,7 +16,7 @@ use crate::model::Numbering;
use crate::utils::NonZeroExt;
/// Can be queried for elements and their positions.
-#[derive(Clone)]
+#[derive(Default, Clone)]
pub struct Introspector {
/// The number of pages in the document.
pages: usize,
@@ -25,6 +25,9 @@ pub struct Introspector {
/// Maps labels to their indices in the element list. We use a smallvec such
/// that if the label is unique, we don't need to allocate.
labels: HashMap<Label, SmallVec<[usize; 1]>>,
+ /// Maps from element keys to the locations of all elements that had this
+ /// key. Used for introspector-assisted location assignment.
+ keys: HashMap<u128, SmallVec<[Location; 1]>>,
/// The page numberings, indexed by page number minus 1.
page_numberings: Vec<Option<Numbering>>,
/// Caches queries done on the introspector. This is important because
@@ -41,6 +44,7 @@ impl Introspector {
self.pages = pages.len();
self.elems.clear();
self.labels.clear();
+ self.keys.clear();
self.page_numberings.clear();
self.queries.clear();
@@ -61,18 +65,21 @@ impl Introspector {
.pre_concat(group.transform);
self.extract(&group.frame, page, ts);
}
- FrameItem::Tag(elem)
- if !self.elems.contains_key(&elem.location().unwrap()) =>
+ FrameItem::Tag(tag)
+ if !self.elems.contains_key(&tag.elem.location().unwrap()) =>
{
let pos = pos.transform(ts);
- let ret = self.elems.insert(
- elem.location().unwrap(),
- (elem.clone(), Position { page, point: pos }),
- );
+ let loc = tag.elem.location().unwrap();
+ let ret = self
+ .elems
+ .insert(loc, (tag.elem.clone(), Position { page, point: pos }));
assert!(ret.is_none(), "duplicate locations");
+ // Build the key map.
+ self.keys.entry(tag.key).or_default().push(loc);
+
// Build the label cache.
- if let Some(label) = elem.label() {
+ if let Some(label) = tag.elem.label() {
self.labels.entry(label).or_default().push(self.elems.len() - 1);
}
}
@@ -86,21 +93,24 @@ impl Introspector {
self.elems.values().map(|(c, _)| c)
}
+ /// Perform a binary search for `elem` among the `list`.
+ fn binary_search(&self, list: &[Content], elem: &Content) -> Result<usize, usize> {
+ list.binary_search_by_key(&self.elem_index(elem), |elem| self.elem_index(elem))
+ }
+
/// Get an element by its location.
fn get(&self, location: &Location) -> Option<&Content> {
self.elems.get(location).map(|(elem, _)| elem)
}
/// Get the index of this element among all.
- fn index(&self, elem: &Content) -> usize {
- self.elems
- .get_index_of(&elem.location().unwrap())
- .unwrap_or(usize::MAX)
+ fn elem_index(&self, elem: &Content) -> usize {
+ self.loc_index(&elem.location().unwrap())
}
- /// Perform a binary search for `elem` among the `list`.
- fn binary_search(&self, list: &[Content], elem: &Content) -> Result<usize, usize> {
- list.binary_search_by_key(&self.index(elem), |elem| self.index(elem))
+ /// Get the index of the element with this location among all.
+ fn loc_index(&self, location: &Location) -> usize {
+ self.elems.get_index_of(location).unwrap_or(usize::MAX)
}
}
@@ -183,7 +193,7 @@ impl Introspector {
Selector::Or(selectors) => selectors
.iter()
.flat_map(|sel| self.query(sel))
- .map(|elem| self.index(&elem))
+ .map(|elem| self.elem_index(&elem))
.collect::<BTreeSet<usize>>()
.into_iter()
.map(|index| self.elems[index].0.clone())
@@ -283,17 +293,20 @@ impl Introspector {
.map(|&(_, pos)| pos)
.unwrap_or(Position { page: NonZeroUsize::ONE, point: Point::zero() })
}
-}
-impl Default for Introspector {
- fn default() -> Self {
- Self {
- pages: 0,
- elems: IndexMap::new(),
- labels: HashMap::new(),
- page_numberings: vec![],
- queries: QueryCache::default(),
- }
+ /// Try to find a location for an element with the given `key` hash
+ /// that is closest after the `anchor`.
+ ///
+ /// This is used for introspector-assisted location assignment during
+ /// measurement. See the "Dealing with Measurement" section of the
+ /// [`Locator`](crate::introspection::Locator) docs for more details.
+ pub fn locator(&self, key: u128, anchor: Location) -> Option<Location> {
+ let anchor = self.loc_index(&anchor);
+ self.keys
+ .get(&key)?
+ .iter()
+ .copied()
+ .min_by_key(|loc| self.loc_index(loc).wrapping_sub(anchor))
}
}
diff --git a/crates/typst/src/introspection/location.rs b/crates/typst/src/introspection/location.rs
index 7b279b39..5cd6aae0 100644
--- a/crates/typst/src/introspection/location.rs
+++ b/crates/typst/src/introspection/location.rs
@@ -1,3 +1,4 @@
+use std::fmt::{self, Debug, Formatter};
use std::num::NonZeroUsize;
use ecow::EcoString;
@@ -21,26 +22,27 @@ use crate::model::Numbering;
/// elements, but you will find only those that have an explicit label attached
/// to them. This limitation will be resolved in the future.
#[ty(scope)]
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct Location {
- /// The hash of the element.
- pub hash: u128,
- /// An unique number among elements with the same hash. This is the reason
- /// we need a `Locator` everywhere.
- pub disambiguator: usize,
-}
+#[derive(Copy, Clone, Eq, PartialEq, Hash)]
+pub struct Location(u128);
impl Location {
+ /// Create a new location from a unique hash.
+ pub fn new(hash: u128) -> Self {
+ Self(hash)
+ }
+
+ /// Extract the raw hash.
+ pub fn hash(self) -> u128 {
+ self.0
+ }
+
/// Produces a well-known variant of this location.
///
/// This is a synthetic location created from another one and is used, for
/// example, in bibliography management to create individual linkable
/// locations for reference entries from the bibliography's location.
pub fn variant(self, n: usize) -> Self {
- Self {
- hash: crate::utils::hash128(&(self.hash, n)),
- ..self
- }
+ Self(crate::utils::hash128(&(self.0, n)))
}
}
@@ -91,6 +93,12 @@ impl Location {
}
}
+impl Debug for Location {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "Location({})", self.0)
+ }
+}
+
impl Repr for Location {
fn repr(&self) -> EcoString {
"..".into()
diff --git a/crates/typst/src/introspection/locator.rs b/crates/typst/src/introspection/locator.rs
index 9c02f6db..4df94a2c 100644
--- a/crates/typst/src/introspection/locator.rs
+++ b/crates/typst/src/introspection/locator.rs
@@ -1,117 +1,350 @@
-use std::cell::RefCell;
use std::collections::HashMap;
+use std::fmt::{self, Debug, Formatter};
+use std::hash::Hash;
+use std::sync::OnceLock;
-use comemo::{Track, Tracked, Validate};
+use comemo::{Tracked, Validate};
-use crate::introspection::Location;
-use crate::layout::{Frame, FrameItem};
+use crate::introspection::{Introspector, Location};
/// Provides locations for elements in the document.
///
-/// A [`Location`] consists of an element's hash plus a disambiguator. Just the
-/// hash is not enough because we can have multiple equal elements with the same
-/// hash (not a hash collision, just equal elements!). Between these, we
-/// disambiguate with an increasing number. In principle, the disambiguator
-/// could just be counted up. However, counting is an impure operation and as
-/// such we can't count across a memoization boundary. [^1]
-///
-/// Instead, we only mutate within a single "layout run" and combine the results
-/// with disambiguators from an outer tracked locator. Thus, the locators form a
-/// "tracked chain". When a layout run ends, its mutations are discarded and, on
-/// the other side of the memoization boundary, we
-/// [reconstruct](Self::visit_frame) them from the resulting [frames](Frame).
-///
-/// [^1]: Well, we could with [`TrackedMut`](comemo::TrackedMut), but the
-/// overhead is quite high, especially since we need to save & undo the counting
-/// when only measuring.
-#[derive(Default, Clone)]
+/// A [`Location`] is a unique ID for an element generated during realization.
+///
+/// # How to use this
+/// The same content may yield different results when laid out in different
+/// parts of the document. To reflect this, every layout operation receives a
+/// locator and every layout operation requires a locator. In code:
+///
+/// - all layouters receive an owned `Locator`
+/// - all layout functions take an owned `Locator`
+///
+/// When a layouter only requires a single sublayout call, it can simply pass on
+/// its locator. When a layouter needs to call multiple sublayouters, we need to
+/// make an explicit decision:
+///
+/// - Split: When we're layouting multiple distinct children (or other pieces of
+/// content), we need to split up the locator with [`Locator::split`]. This
+/// allows us to produce multiple new `Locator`s for the sublayouts. When we
+/// split the locator, each sublocator will be a distinct entity and using it
+/// to e.g. layout the same piece of figure content will yield distinctly
+/// numbered figures.
+///
+/// - Relayout: When we're layouting the same content multiple times (e.g. when
+/// measuring something), we can call [`Locator::relayout`] to use the same
+/// locator multiple times. This indicates to the compiler that it's actually
+/// the same content. Using it to e.g. layout the same piece of figure content
+/// will yield the same figure number both times. Typically, when we layout
+/// something multiple times using `relayout`, only one of the outputs
+/// actually ends up in the document, while the other outputs are only used
+/// for measurement and then discarded.
+///
+/// The `Locator` intentionally does not implement `Copy` and `Clone` so that it
+/// can only be used once. This ensures that whenever we are layouting multiple
+/// things, we make an explicit decision whether we want to split or relayout.
+///
+/// # How it works
+/// There are two primary considerations for the assignment of locations:
+///
+/// 1. Locations should match up over multiple layout iterations, so that
+/// elements can be identified as being the same: That's the whole point of
+/// them.
+///
+/// 2. Locations should be as stable as possible across document edits, so that
+/// incremental compilation is effective.
+///
+/// 3. We want to assign them with as little long-lived state as possible to
+/// enable parallelization of the layout process.
+///
+/// Let's look at a few different assignment strategies to get a feeling for
+/// these requirements:
+///
+/// - A very simple way to generate unique IDs would be to just increase a
+/// counter for each element. In this setup, (1) is somewhat satisfied: In
+/// principle, the counter will line up across iterations, but things start to
+/// break down once we generate content dependant on introspection since the
+/// IDs generated for that new content will shift the IDs for all following
+/// elements in the document. (2) is not satisfied since an edit in the middle
+/// of the document shifts all later IDs. (3) is obviously not satisfied.
+/// Conclusion: Not great.
+///
+/// - To make things more robust, we can incorporate some stable knowledge about
+/// the element into the ID. For this, we can use the element's span since it
+/// is already mostly unique: Elements resulting from different source code
+/// locations are guaranteed to have different spans. However, we can also
+/// have multiple distinct elements generated from the same source location:
+/// e.g. `#for _ in range(5) { figure(..) }`. To handle this case, we can then
+/// disambiguate elements with the same span with an increasing counter. In
+/// this setup, (1) is mostly satisfied: Unless we do stuff like generating
+/// colliding counter updates dependant on introspection, things will line up.
+/// (2) is also reasonably well satisfied, as typical edits will only affect
+/// the single element at the currently edited span. Only if we edit inside of
+/// a function, loop, or similar construct, we will affect multiple elements.
+/// (3) is still a problem though, since we count up.
+///
+/// - What's left is to get rid of the mutable state. Note that layout is a
+/// recursive process and has a tree-shaped execution graph. Thus, we can try
+/// to determine an element's ID based on the path of execution taken in this
+/// graph. Something like "3rd element in layer 1, 7th element in layer 2,
+/// ..". This is basically the first approach, but on a per-layer basis. Thus,
+/// we can again apply our trick from the second approach, and use the span +
+/// disambiguation strategy on a per-layer basis: "1st element with span X in
+/// layer 1, 3rd element with span Y in layer 2". The chance for a collision
+/// is now pretty low and our state is wholly local to each level. So, if we
+/// want to parallelize layout within a layer, we can generate the IDs for
+/// that layer upfront and then start forking out. The final remaining
+/// question is how we can compactly encode this information: For this, as
+/// always, we use hashing! We incorporate the ID information from each layer
+/// into a single hash and thanks to the collision resistence of 128-bit
+/// SipHash, we get almost guaranteed unique locations. We don't even store
+/// the full layer information at all, but rather hash _hierarchically:_ Let
+/// `k_x` be our local per-layer ID for layer `x` and `h_x` be the full
+/// combined hash for layer `x`. We compute `h_n = hash(h_(n-1), k_n)`.
+///
+/// So that's what's going on conceptually in this type. For efficient
+/// memoization, we do all of this in a tracked fashion, such that we only
+/// observe the hash for all the layers above us, if we actually need to
+/// generate a [`Location`]. Thus, if we have a piece of content that does not
+/// contain any locatable elements, we can cache its layout even if it occurs in
+/// different places.
+///
+/// # Dealing with measurement
+/// As explained above, any kind of measurement the compiler performs requires a
+/// locator that matches the one used during real layout. This ensures that the
+/// locations assigned during measurement match up exactly with the locations of
+/// real document elements. Without this guarantee, many introspection-driven
+/// features (like counters, state, and citations) don't work correctly (since
+/// they perform queries dependant on concrete locations).
+///
+/// This is all fine and good, but things get really tricky when the _user_
+/// measures such introspecting content since the user isn't kindly managing
+/// locators for us. Our standard `Locator` workflow assigns locations that
+/// depend a lot on the exact placement in the hierarchy of elements. For this
+/// reason, something that is measured, but then placed into something like a
+/// grid will get a location influenced by the grid. Without a locator, we can't
+/// make the connection between the measured content and the real content, so we
+/// can't ensure that the locations match up.
+///
+/// One possible way to deal with this is to force the user to uniquely identify
+/// content before being measured after all. This would mean that the user needs
+/// to come up with an identifier that is unique within the surrounding context
+/// block and attach it to the content in some way. However, after careful
+/// consideration, I have concluded that this is simply too big of an ask from
+/// users: Understanding why this is even necessary is pretty complicated and
+/// how to best come up with a unique ID is even more so.
+///
+/// For this reason, I chose an alternative best-effort approach: The locator
+/// has a custom "measurement mode" (entered through [`LocatorLink::measure`]),
+/// in which it does its best to assign locations that match up. Specifically,
+/// it uses the key hashes of the individual locatable elements in the measured
+/// content (which may not be unique if content is reused) and combines them
+/// with the context's location to find the most likely matching real element.
+/// This approach works correctly almost all of the time (especially for
+/// "normal" hand-written content where the key hashes rarely collide, as
+/// opposed to code-heavy things where they do).
+///
+/// Support for enhancing this with user-provided uniqueness can still be added
+/// in the future. It will most likely anyway be added simply because it's
+/// automatically included when we add a way to "freeze" content for things like
+/// slidehows. But it will be opt-in because it's just too much complication.
pub struct Locator<'a> {
- /// Maps from a hash to the maximum number we've seen for this hash. This
- /// number becomes the `disambiguator`.
- hashes: RefCell<HashMap<u128, usize>>,
- /// An outer `Locator`, from which we can get disambiguator for hashes
- /// outside of the current "layout run".
- ///
- /// We need to override the constraint's lifetime here so that `Tracked` is
- /// covariant over the constraint. If it becomes invariant, we're in for a
- /// world of lifetime pain.
- outer: Option<Tracked<'a, Self, <Locator<'static> as Validate>::Constraint>>,
+ /// A local hash that incorporates all layers since the last memoization
+ /// boundary.
+ local: u128,
+ /// A pointer to an outer cached locator, which contributes the information
+ /// for all the layers beyond the memoization boundary on-demand.
+ outer: Option<&'a LocatorLink<'a>>,
}
impl<'a> Locator<'a> {
- /// Create a new locator.
- pub fn new() -> Self {
- Self::default()
+ /// Create a new root-level locator.
+ ///
+ /// Should typically only be created at the document level, though there
+ /// are a few places where we use it as well that just don't support
+ /// introspection (e.g. drawable patterns).
+ pub fn root() -> Self {
+ Self { local: 0, outer: None }
}
- /// Create a new chained locator.
- pub fn chained(outer: Tracked<'a, Self>) -> Self {
- Self { outer: Some(outer), ..Default::default() }
+ /// Creates a new synthetic locator.
+ ///
+ /// This can be used to create a new dependant layout based on an element.
+ /// This is used for layouting footnote entries based on the location
+ /// of the associated footnote.
+ pub fn synthesize(location: Location) -> Self {
+ Self { local: location.hash(), outer: None }
}
- /// Start tracking this locator.
+ /// Creates a new locator that points to the given link.
+ pub fn link(link: &'a LocatorLink<'a>) -> Self {
+ Self { local: 0, outer: Some(link) }
+ }
+}
+
+impl<'a> Locator<'a> {
+ /// Returns a type that can be used to generate `Locator`s for multiple
+ /// child elements. See the type-level docs for more details.
+ pub fn split(self) -> SplitLocator<'a> {
+ SplitLocator {
+ local: self.local,
+ outer: self.outer,
+ disambiguators: HashMap::new(),
+ }
+ }
+
+ /// Creates a copy of this locator for measurement or relayout of the same
+ /// content. See the type-level docs for more details.
///
- /// In comparison to [`Track::track`], this method skips this chain link
- /// if it does not contribute anything.
- pub fn track(&self) -> Tracked<'_, Self> {
+ /// This is effectively just `Clone`, but the `Locator` doesn't implement
+ /// `Clone` to make this operation explicit.
+ pub fn relayout(&self) -> Self {
+ Self { local: self.local, outer: self.outer }
+ }
+}
+
+#[comemo::track]
+impl<'a> Locator<'a> {
+ /// Resolves the locator based on its local and the outer information.
+ fn resolve(&self) -> Resolved {
match self.outer {
- Some(outer) if self.hashes.borrow().is_empty() => outer,
- _ => Track::track(self),
+ None => Resolved::Hash(self.local),
+ Some(outer) => match outer.resolve() {
+ Resolved::Hash(outer) => {
+ Resolved::Hash(crate::utils::hash128(&(self.local, outer)))
+ }
+ Resolved::Measure(anchor) => Resolved::Measure(anchor),
+ },
}
}
+}
- /// Produce a stable identifier for this call site.
- pub fn locate(&mut self, hash: u128) -> Location {
- // Get the current disambiguator for this hash.
- let disambiguator = self.disambiguator_impl(hash);
+impl Debug for Locator<'_> {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "Locator({:?})", self.resolve())
+ }
+}
- // Bump the next disambiguator up by one.
- self.hashes.get_mut().insert(hash, disambiguator + 1);
+/// The fully resolved value of a locator.
+#[derive(Debug, Copy, Clone, Hash)]
+enum Resolved {
+ /// The full hash, incorporating the local and all outer information.
+ Hash(u128),
+ /// Indicates that the locator is in measurement mode, with the given anchor
+ /// location.
+ Measure(Location),
+}
- // Create the location in its default variant.
- Location { hash, disambiguator }
+/// A type that generates unique sublocators.
+pub struct SplitLocator<'a> {
+ /// A local hash that incorporates all layers since the last memoization
+ /// boundary.
+ local: u128,
+ /// A pointer to an outer cached locator, which contributes the information
+ /// for all the layers beyond the memoization boundary on-demand.
+ outer: Option<&'a LocatorLink<'a>>,
+ /// Simply counts up the number of times we've seen each local hash.
+ disambiguators: HashMap<u128, usize>,
+}
+
+impl<'a> SplitLocator<'a> {
+ /// Produces a sublocator for a subtree keyed by `key`. The keys do *not*
+ /// need to be unique among the `next()` calls on this split locator. (They
+ /// can even all be `&()`.)
+ ///
+ /// However, stable & mostly unique keys lead to more stable locations
+ /// throughout edits, improving incremental compilation performance.
+ ///
+ /// A common choice for a key is the span of the content that will be
+ /// layouted with this locator.
+ pub fn next<K: Hash>(&mut self, key: &K) -> Locator<'a> {
+ self.next_inner(crate::utils::hash128(key))
}
- /// Advance past a frame.
- pub fn visit_frame(&mut self, frame: &Frame) {
- for (_, item) in frame.items() {
- match item {
- FrameItem::Group(group) => self.visit_frame(&group.frame),
- FrameItem::Tag(elem) => {
- let hashes = self.hashes.get_mut();
- let loc = elem.location().unwrap();
- let entry = hashes.entry(loc.hash).or_default();
-
- // Next disambiguator needs to be at least one larger than
- // the maximum we've seen so far.
- *entry = (*entry).max(loc.disambiguator + 1);
- }
- _ => {}
+ /// Produces a sublocator for a subtree.
+ pub fn next_inner(&mut self, key: u128) -> Locator<'a> {
+ // Produce a locator disambiguator, for elements with the same key
+ // within this `SplitLocator`.
+ let disambiguator = {
+ let slot = self.disambiguators.entry(key).or_default();
+ std::mem::replace(slot, *slot + 1)
+ };
+
+ // Combine the key, disambiguator and local hash into a sub-local hash.
+ // The outer information is not yet merged into this, it is added
+ // on-demand in `Locator::resolve`.
+ let local = crate::utils::hash128(&(key, disambiguator, self.local));
+
+ Locator { outer: self.outer, local }
+ }
+
+ /// Produces a unique location for an element.
+ pub fn next_location(
+ &mut self,
+ introspector: Tracked<Introspector>,
+ key: u128,
+ ) -> Location {
+ match self.next_inner(key).resolve() {
+ Resolved::Hash(hash) => Location::new(hash),
+ Resolved::Measure(anchor) => {
+ // If we aren't able to find a matching element in the document,
+ // default to the anchor, so that it's at least remotely in
+ // the right area (so that counters can be resolved).
+ introspector.locator(key, anchor).unwrap_or(anchor)
}
}
}
+}
+
+/// A locator can be linked to this type to only access information across the
+/// memoization boundary on-demand, improving the cache hit chance.
+pub struct LocatorLink<'a> {
+ /// The link itself.
+ kind: LinkKind<'a>,
+ /// The cached resolved link.
+ resolved: OnceLock<Resolved>,
+}
+
+/// The different kinds of locator links.
+enum LinkKind<'a> {
+ /// An outer `Locator`, which we can resolved if necessary.
+ ///
+ /// We need to override the constraint's lifetime here so that `Tracked` is
+ /// covariant over the constraint. If it becomes invariant, we're in for a
+ /// world of lifetime pain.
+ Outer(Tracked<'a, Locator<'a>, <Locator<'static> as Validate>::Constraint>),
+ /// A link which indicates that we are in measurement mode.
+ Measure(Location),
+}
- /// Advance past a number of frames.
- pub fn visit_frames<'b>(&mut self, frames: impl IntoIterator<Item = &'b Frame>) {
- for frame in frames {
- self.visit_frame(frame);
+impl<'a> LocatorLink<'a> {
+ /// Create a locator link.
+ pub fn new(outer: Tracked<'a, Locator<'a>>) -> Self {
+ LocatorLink {
+ kind: LinkKind::Outer(outer),
+ resolved: OnceLock::new(),
}
}
- /// The current disambiguator for the given hash.
- fn disambiguator_impl(&self, hash: u128) -> usize {
- *self
- .hashes
- .borrow_mut()
- .entry(hash)
- .or_insert_with(|| self.outer.map_or(0, |outer| outer.disambiguator(hash)))
+ /// Creates a link that puts any linked downstream locator into measurement
+ /// mode.
+ ///
+ /// Read the "Dealing with measurement" section of the [`Locator`] docs for
+ /// more details.
+ pub fn measure(anchor: Location) -> Self {
+ LocatorLink {
+ kind: LinkKind::Measure(anchor),
+ resolved: OnceLock::new(),
+ }
}
-}
-#[comemo::track]
-impl<'a> Locator<'a> {
- /// The current disambiguator for the hash.
- fn disambiguator(&self, hash: u128) -> usize {
- self.disambiguator_impl(hash)
+ /// Resolve the link.
+ ///
+ /// The result is cached in this link, so that we don't traverse the link
+ /// chain over and over again.
+ fn resolve(&self) -> Resolved {
+ *self.resolved.get_or_init(|| match self.kind {
+ LinkKind::Outer(outer) => outer.resolve(),
+ LinkKind::Measure(anchor) => Resolved::Measure(anchor),
+ })
}
}
diff --git a/crates/typst/src/introspection/mod.rs b/crates/typst/src/introspection/mod.rs
index 1a7c0239..c9dba244 100644
--- a/crates/typst/src/introspection/mod.rs
+++ b/crates/typst/src/introspection/mod.rs
@@ -23,6 +23,8 @@ pub use self::metadata::*;
pub use self::query_::*;
pub use self::state::*;
+use std::fmt::{self, Debug, Formatter};
+
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
@@ -56,7 +58,7 @@ pub fn define(global: &mut Scope) {
global.define_func::<locate>();
}
-/// Holds a locatable element that was realized.
+/// Holds a tag for a locatable element that was realized.
///
/// The `TagElem` is handled by all layouters. The held element becomes
/// available for introspection in the next compiler iteration.
@@ -65,14 +67,13 @@ pub struct TagElem {
/// The introspectible element.
#[required]
#[internal]
- pub elem: Content,
+ pub tag: Tag,
}
impl TagElem {
/// Create a packed tag element.
- pub fn packed(elem: Content) -> Content {
- let span = elem.span();
- let mut content = Self::new(elem).pack().spanned(span);
+ pub fn packed(tag: Tag) -> Content {
+ let mut content = Self::new(tag).pack();
// We can skip preparation for the `TagElem`.
content.mark_prepared();
content
@@ -92,3 +93,29 @@ impl Behave for Packed<TagElem> {
Behaviour::Invisible
}
}
+
+/// Holds a locatable element that was realized.
+#[derive(Clone, PartialEq, Hash)]
+pub struct Tag {
+ /// The introspectible element.
+ pub elem: Content,
+ /// The element's key hash, which forms the base of its location (but is
+ /// locally disambiguated and combined with outer hashes).
+ ///
+ /// We need to retain this for introspector-assisted location assignment
+ /// during measurement.
+ pub(crate) key: u128,
+}
+
+impl Tag {
+ /// Create a tag from an element and its key hash.
+ pub fn new(elem: Content, key: u128) -> Self {
+ Self { elem, key }
+ }
+}
+
+impl Debug for Tag {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "Tag({:?})", self.elem)
+ }
+}
diff --git a/crates/typst/src/introspection/state.rs b/crates/typst/src/introspection/state.rs
index 5cbd2fc3..bf97c874 100644
--- a/crates/typst/src/introspection/state.rs
+++ b/crates/typst/src/introspection/state.rs
@@ -9,7 +9,7 @@ use crate::foundations::{
LocatableSelector, NativeElement, Packed, Repr, Selector, Show, Str, StyleChain,
Value,
};
-use crate::introspection::{Introspector, Locatable, Location, Locator};
+use crate::introspection::{Introspector, Locatable, Location};
use crate::syntax::Span;
use crate::World;
@@ -215,7 +215,6 @@ impl State {
engine.world,
engine.introspector,
engine.route.track(),
- engine.locator.track(),
TrackedMut::reborrow_mut(&mut engine.tracer),
)
}
@@ -227,15 +226,12 @@ impl State {
world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>,
route: Tracked<Route>,
- locator: Tracked<Locator>,
tracer: TrackedMut<Tracer>,
) -> SourceResult<EcoVec<Value>> {
- let mut locator = Locator::chained(locator);
let mut engine = Engine {
world,
introspector,
route: Route::extend(route).unnested(),
- locator: &mut locator,
tracer,
};
let mut state = self.init.clone();
diff --git a/crates/typst/src/layout/columns.rs b/crates/typst/src/layout/columns.rs
index c249a227..503cb857 100644
--- a/crates/typst/src/layout/columns.rs
+++ b/crates/typst/src/layout/columns.rs
@@ -3,6 +3,7 @@ use std::num::NonZeroUsize;
use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{elem, Content, NativeElement, Packed, Show, StyleChain};
+use crate::introspection::Locator;
use crate::layout::{
Abs, Axes, BlockElem, Dir, Fragment, Frame, Length, Point, Ratio, Regions, Rel, Size,
};
@@ -62,7 +63,8 @@ impl Show for Packed<ColumnsElem> {
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
Ok(BlockElem::multi_layouter(self.clone(), layout_columns)
.with_rootable(true)
- .pack())
+ .pack()
+ .spanned(self.span()))
}
}
@@ -71,6 +73,7 @@ impl Show for Packed<ColumnsElem> {
fn layout_columns(
elem: &Packed<ColumnsElem>,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
@@ -79,7 +82,7 @@ fn layout_columns(
// Separating the infinite space into infinite columns does not make
// much sense.
if !regions.size.x.is_finite() {
- return body.layout(engine, styles, regions);
+ return body.layout(engine, locator, styles, regions);
}
// Determine the width of the gutter and each column.
@@ -104,7 +107,7 @@ fn layout_columns(
};
// Layout the children.
- let mut frames = body.layout(engine, styles, pod)?.into_iter();
+ let mut frames = body.layout(engine, locator, styles, pod)?.into_iter();
let mut finished = vec![];
let dir = TextElem::dir_in(styles);
diff --git a/crates/typst/src/layout/container.rs b/crates/typst/src/layout/container.rs
index 26db64b6..908a193e 100644
--- a/crates/typst/src/layout/container.rs
+++ b/crates/typst/src/layout/container.rs
@@ -7,6 +7,7 @@ use crate::foundations::{
cast, elem, Args, AutoValue, Construct, Content, NativeElement, Packed, Resolve,
Smart, StyleChain, Value,
};
+use crate::introspection::Locator;
use crate::layout::{
Abs, Axes, Corners, Em, Fr, Fragment, Frame, FrameKind, Length, Region, Regions, Rel,
Sides, Size, Spacing, VElem,
@@ -120,6 +121,7 @@ impl Packed<BoxElem> {
pub fn layout(
&self,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
region: Size,
) -> SourceResult<Frame> {
@@ -140,7 +142,7 @@ impl Packed<BoxElem> {
// If we have a child, layout it into the body. Boxes are boundaries
// for gradient relativeness, so we set the `FrameKind` to `Hard`.
Some(body) => body
- .layout(engine, styles, pod.into_regions())?
+ .layout(engine, locator, styles, pod.into_regions())?
.into_frame()
.with_kind(FrameKind::Hard),
};
@@ -251,6 +253,7 @@ impl InlineElem {
callback: fn(
content: &Packed<T>,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
region: Size,
) -> SourceResult<Vec<InlineItem>>,
@@ -264,10 +267,11 @@ impl Packed<InlineElem> {
pub fn layout(
&self,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
region: Size,
) -> SourceResult<Vec<InlineItem>> {
- self.body().call(engine, styles, region)
+ self.body().call(engine, locator, styles, region)
}
}
@@ -460,6 +464,7 @@ impl BlockElem {
f: fn(
content: &Packed<T>,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
region: Region,
) -> SourceResult<Frame>,
@@ -477,6 +482,7 @@ impl BlockElem {
f: fn(
content: &Packed<T>,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment>,
@@ -493,6 +499,7 @@ impl Packed<BlockElem> {
pub fn layout(
&self,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
@@ -530,7 +537,8 @@ impl Packed<BlockElem> {
// If we have content as our body, just layout it.
Some(BlockChild::Content(body)) => {
- let mut fragment = body.measure(engine, styles, pod)?;
+ let mut fragment =
+ body.layout(engine, locator.relayout(), styles, pod)?;
// If the body is automatically sized and produced more than one
// fragment, ensure that the width was consistent across all
@@ -551,11 +559,7 @@ impl Packed<BlockElem> {
expand: Axes::new(true, pod.expand.y),
..pod
};
- fragment = body.layout(engine, styles, pod)?;
- } else {
- // Apply the side effect to turn the `measure` into a
- // `layout`.
- engine.locator.visit_frames(&fragment);
+ fragment = body.layout(engine, locator, styles, pod)?;
}
fragment
@@ -565,7 +569,7 @@ impl Packed<BlockElem> {
// base region, give it that.
Some(BlockChild::SingleLayouter(callback)) => {
let pod = Region::new(pod.base(), pod.expand);
- callback.call(engine, styles, pod).map(Fragment::frame)?
+ callback.call(engine, locator, styles, pod).map(Fragment::frame)?
}
// If we have a child that wants to layout with full region access,
@@ -577,7 +581,7 @@ impl Packed<BlockElem> {
Some(BlockChild::MultiLayouter(callback)) => {
let expand = (pod.expand | regions.expand) & pod.size.map(Abs::is_finite);
let pod = Regions { expand, ..pod };
- callback.call(engine, styles, pod)?
+ callback.call(engine, locator, styles, pod)?
}
};
@@ -927,6 +931,7 @@ mod callbacks {
callback! {
InlineCallback = (
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
region: Size,
) -> SourceResult<Vec<InlineItem>>
@@ -935,6 +940,7 @@ mod callbacks {
callback! {
BlockSingleCallback = (
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
region: Region,
) -> SourceResult<Frame>
@@ -943,6 +949,7 @@ mod callbacks {
callback! {
BlockMultiCallback = (
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment>
diff --git a/crates/typst/src/layout/flow.rs b/crates/typst/src/layout/flow.rs
index 8ae68183..cdf6034d 100644
--- a/crates/typst/src/layout/flow.rs
+++ b/crates/typst/src/layout/flow.rs
@@ -11,7 +11,7 @@ use crate::engine::Engine;
use crate::foundations::{
elem, Args, Construct, Content, NativeElement, Packed, Resolve, Smart, StyleChain,
};
-use crate::introspection::TagElem;
+use crate::introspection::{Locator, SplitLocator, Tag, TagElem};
use crate::layout::{
Abs, AlignElem, Axes, BlockElem, ColbreakElem, FixedAlignment, FlushElem, Fr,
Fragment, Frame, FrameItem, PlaceElem, Point, Regions, Rel, Size, Spacing, VElem,
@@ -43,6 +43,7 @@ impl Packed<FlowElem> {
pub fn layout(
&self,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
@@ -66,7 +67,7 @@ impl Packed<FlowElem> {
alone = child.is::<BlockElem>();
}
- let mut layouter = FlowLayouter::new(regions, styles, alone);
+ let mut layouter = FlowLayouter::new(locator, styles, regions, alone);
for (child, styles) in self.children().chain(&styles) {
if let Some(elem) = child.to_packed::<TagElem>() {
layouter.layout_tag(elem);
@@ -105,10 +106,12 @@ impl Debug for FlowElem {
struct FlowLayouter<'a> {
/// Whether this is the root flow.
root: bool,
- /// The regions to layout children into.
- regions: Regions<'a>,
+ /// Provides unique locations to the flow's children.
+ locator: SplitLocator<'a>,
/// The shared styles.
styles: StyleChain<'a>,
+ /// The regions to layout children into.
+ regions: Regions<'a>,
/// Whether the flow should expand to fill the region.
expand: Axes<bool>,
/// The initial size of `regions.size` that was available before we started
@@ -121,7 +124,7 @@ struct FlowLayouter<'a> {
/// Spacing and layouted blocks for the current region.
items: Vec<FlowItem>,
/// A queue of tags that will be attached to the next frame.
- pending_tags: Vec<Content>,
+ pending_tags: Vec<Tag>,
/// A queue of floating elements.
pending_floats: Vec<FlowItem>,
/// Whether we have any footnotes in the current region.
@@ -192,7 +195,12 @@ impl FlowItem {
impl<'a> FlowLayouter<'a> {
/// Create a new flow layouter.
- fn new(mut regions: Regions<'a>, styles: StyleChain<'a>, alone: bool) -> Self {
+ fn new(
+ locator: Locator<'a>,
+ styles: StyleChain<'a>,
+ mut regions: Regions<'a>,
+ alone: bool,
+ ) -> Self {
let expand = regions.expand;
let root = std::mem::replace(&mut regions.root, false);
@@ -204,8 +212,9 @@ impl<'a> FlowLayouter<'a> {
Self {
root,
- regions,
+ locator: locator.split(),
styles,
+ regions,
expand,
initial: regions.size,
last_was_par: false,
@@ -223,8 +232,8 @@ impl<'a> FlowLayouter<'a> {
}
/// Place explicit metadata into the flow.
- fn layout_tag(&mut self, tag: &Packed<TagElem>) {
- self.pending_tags.push(tag.elem.clone());
+ fn layout_tag(&mut self, elem: &Packed<TagElem>) {
+ self.pending_tags.push(elem.tag.clone());
}
/// Layout vertical spacing.
@@ -259,6 +268,7 @@ impl<'a> FlowLayouter<'a> {
let lines = par
.layout(
engine,
+ self.locator.next(&par.span()),
styles,
consecutive,
self.regions.base(),
@@ -330,7 +340,12 @@ impl<'a> FlowLayouter<'a> {
// Layout the block itself.
let sticky = block.sticky(styles);
- let fragment = block.layout(engine, styles, self.regions)?;
+ let fragment = block.layout(
+ engine,
+ self.locator.next(&block.span()),
+ styles,
+ self.regions,
+ )?;
// How to align the block.
let align = AlignElem::alignment_in(styles).resolve(styles);
@@ -378,7 +393,14 @@ impl<'a> FlowLayouter<'a> {
align.x().unwrap_or_default().resolve(styles)
});
let y_align = alignment.map(|align| align.y().map(|y| y.resolve(styles)));
- let mut frame = placed.layout(engine, styles, self.regions.base())?.into_frame();
+ let mut frame = placed
+ .layout(
+ engine,
+ self.locator.next(&placed.span()),
+ styles,
+ self.regions.base(),
+ )?
+ .into_frame();
frame.post_process(styles);
let item = FlowItem::Placed { frame, x_align, y_align, delta, float, clearance };
self.layout_item(engine, item)
@@ -390,7 +412,7 @@ impl<'a> FlowLayouter<'a> {
frame.prepend_multiple(
self.pending_tags
.drain(..)
- .map(|elem| (Point::zero(), FrameItem::Tag(elem))),
+ .map(|tag| (Point::zero(), FrameItem::Tag(tag))),
);
}
}
@@ -631,7 +653,7 @@ impl<'a> FlowLayouter<'a> {
if force && !self.pending_tags.is_empty() {
let pos = Point::with_y(offset);
output.push_multiple(
- self.pending_tags.drain(..).map(|elem| (pos, FrameItem::Tag(elem))),
+ self.pending_tags.drain(..).map(|tag| (pos, FrameItem::Tag(tag))),
);
}
@@ -718,7 +740,6 @@ impl FlowLayouter<'_> {
let prev_items_len = self.items.len();
let prev_size = self.regions.size;
let prev_has_footnotes = self.has_footnotes;
- let prev_locator = engine.locator.clone();
// Process footnotes one at a time.
let mut k = 0;
@@ -735,7 +756,12 @@ impl FlowLayouter<'_> {
self.regions.size.y -= self.footnote_config.gap;
let frames = FootnoteEntry::new(notes[k].clone())
.pack()
- .layout(engine, self.styles, self.regions.with_root(false))?
+ .layout(
+ engine,
+ Locator::synthesize(notes[k].location().unwrap()),
+ self.styles,
+ self.regions.with_root(false),
+ )?
.into_frames();
// If the entries didn't fit, abort (to keep footnote and entry
@@ -749,7 +775,6 @@ impl FlowLayouter<'_> {
self.items.truncate(prev_items_len);
self.regions.size = prev_size;
self.has_footnotes = prev_has_footnotes;
- *engine.locator = prev_locator;
return Ok(false);
}
@@ -784,7 +809,10 @@ impl FlowLayouter<'_> {
let pod = Regions::one(self.regions.base(), expand);
let separator = &self.footnote_config.separator;
- let mut frame = separator.layout(engine, self.styles, pod)?.into_frame();
+ // FIXME: Shouldn't use `root()` here.
+ let mut frame = separator
+ .layout(engine, Locator::root(), self.styles, pod)?
+ .into_frame();
frame.size_mut().y += self.footnote_config.clearance;
frame.translate(Point::with_y(self.footnote_config.clearance));
@@ -801,10 +829,10 @@ fn find_footnotes(notes: &mut Vec<Packed<FootnoteElem>>, frame: &Frame) {
for (_, item) in frame.items() {
match item {
FrameItem::Group(group) => find_footnotes(notes, &group.frame),
- FrameItem::Tag(elem)
- if !notes.iter().any(|note| note.location() == elem.location()) =>
+ FrameItem::Tag(tag)
+ if !notes.iter().any(|note| note.location() == tag.elem.location()) =>
{
- let Some(footnote) = elem.to_packed::<FootnoteElem>() else {
+ let Some(footnote) = tag.elem.to_packed::<FootnoteElem>() else {
continue;
};
notes.push(footnote.clone());
diff --git a/crates/typst/src/layout/frame.rs b/crates/typst/src/layout/frame.rs
index 42fc1d72..09a4362d 100644
--- a/crates/typst/src/layout/frame.rs
+++ b/crates/typst/src/layout/frame.rs
@@ -6,7 +6,8 @@ use std::sync::Arc;
use smallvec::SmallVec;
-use crate::foundations::{cast, dict, Content, Dict, StyleChain, Value};
+use crate::foundations::{cast, dict, Dict, StyleChain, Value};
+use crate::introspection::Tag;
use crate::layout::{
Abs, Axes, Corners, FixedAlignment, HideElem, Length, Point, Rel, Sides, Size,
Transform,
@@ -521,8 +522,9 @@ pub enum FrameItem {
Image(Image, Size, Span),
/// An internal or external link to a destination.
Link(Destination, Size),
- /// An introspectable element that produced something within this frame.
- Tag(Content),
+ /// An introspectable element that produced something within this frame
+ /// alongside its key.
+ Tag(Tag),
}
impl Debug for FrameItem {
@@ -533,7 +535,7 @@ impl Debug for FrameItem {
Self::Shape(shape, _) => write!(f, "{shape:?}"),
Self::Image(image, _, _) => write!(f, "{image:?}"),
Self::Link(dest, _) => write!(f, "Link({dest:?})"),
- Self::Tag(elem) => write!(f, "Tag({elem:?})"),
+ Self::Tag(tag) => write!(f, "{tag:?}"),
}
}
}
diff --git a/crates/typst/src/layout/grid/cells.rs b/crates/typst/src/layout/grid/cells.rs
index efa7bb6e..201bf108 100644
--- a/crates/typst/src/layout/grid/cells.rs
+++ b/crates/typst/src/layout/grid/cells.rs
@@ -12,7 +12,10 @@ use crate::foundations::{
Array, CastInfo, Content, Context, Fold, FromValue, Func, IntoValue, Reflect,
Resolve, Smart, StyleChain, Value,
};
-use crate::layout::{Abs, Alignment, Axes, Length, LinePosition, Rel, Sides, Sizing};
+use crate::introspection::Locator;
+use crate::layout::{
+ Abs, Alignment, Axes, Fragment, Length, LinePosition, Regions, Rel, Sides, Sizing,
+};
use crate::syntax::Span;
use crate::utils::NonZeroExt;
use crate::visualize::{Paint, Stroke};
@@ -155,10 +158,11 @@ where
}
/// Represents a cell in CellGrid, to be laid out by GridLayouter.
-#[derive(Clone)]
-pub struct Cell {
+pub struct Cell<'a> {
/// The cell's body.
pub body: Content,
+ /// The cell's locator.
+ pub locator: Locator<'a>,
/// The cell's fill.
pub fill: Option<Paint>,
/// The amount of columns spanned by the cell.
@@ -184,11 +188,12 @@ pub struct Cell {
pub breakable: bool,
}
-impl From<Content> for Cell {
- /// Create a simple cell given its body.
- fn from(body: Content) -> Self {
+impl<'a> Cell<'a> {
+ /// Create a simple cell given its body and its locator.
+ pub fn new(body: Content, locator: Locator<'a>) -> Self {
Self {
body,
+ locator,
fill: None,
colspan: NonZeroUsize::ONE,
rowspan: NonZeroUsize::ONE,
@@ -197,13 +202,32 @@ impl From<Content> for Cell {
breakable: true,
}
}
+
+ /// Layout the cell into the given regions.
+ ///
+ /// The `disambiguator` indicates which instance of this cell this should be
+ /// layouted as. For normal cells, it is always `0`, but for headers and
+ /// footers, it indicates the index of the header/footer among all. See the
+ /// [`Locator`] docs for more details on the concepts behind this.
+ pub fn layout(
+ &self,
+ engine: &mut Engine,
+ disambiguator: usize,
+ styles: StyleChain,
+ regions: Regions,
+ ) -> SourceResult<Fragment> {
+ let mut locator = self.locator.relayout();
+ if disambiguator > 0 {
+ locator = locator.split().next_inner(disambiguator as u128);
+ }
+ self.body.layout(engine, locator, styles, regions)
+ }
}
/// A grid entry.
-#[derive(Clone)]
-pub(super) enum Entry {
+pub(super) enum Entry<'a> {
/// An entry which holds a cell.
- Cell(Cell),
+ Cell(Cell<'a>),
/// An entry which is merged with another cell.
Merged {
/// The index of the cell this entry is merged with.
@@ -211,9 +235,9 @@ pub(super) enum Entry {
},
}
-impl Entry {
+impl<'a> Entry<'a> {
/// Obtains the cell inside this entry, if this is not a merged cell.
- fn as_cell(&self) -> Option<&Cell> {
+ fn as_cell(&self) -> Option<&Cell<'a>> {
match self {
Self::Cell(cell) => Some(cell),
Self::Merged { .. } => None,
@@ -269,7 +293,7 @@ pub trait ResolvableCell {
/// the `breakable` field.
/// Returns a final Cell.
#[allow(clippy::too_many_arguments)]
- fn resolve_cell(
+ fn resolve_cell<'a>(
self,
x: usize,
y: usize,
@@ -278,8 +302,9 @@ pub trait ResolvableCell {
inset: Sides<Option<Rel<Length>>>,
stroke: Sides<Option<Option<Arc<Stroke<Abs>>>>>,
breakable: bool,
+ locator: Locator<'a>,
styles: StyleChain,
- ) -> Cell;
+ ) -> Cell<'a>;
/// Returns this cell's column override.
fn x(&self, styles: StyleChain) -> Smart<usize>;
@@ -298,9 +323,9 @@ pub trait ResolvableCell {
}
/// A grid of cells, including the columns, rows, and cell data.
-pub struct CellGrid {
+pub struct CellGrid<'a> {
/// The grid cells.
- pub(super) entries: Vec<Entry>,
+ pub(super) entries: Vec<Entry<'a>>,
/// The column tracks including gutter tracks.
pub(super) cols: Vec<Sizing>,
/// The row tracks including gutter tracks.
@@ -321,12 +346,12 @@ pub struct CellGrid {
pub(super) has_gutter: bool,
}
-impl CellGrid {
+impl<'a> CellGrid<'a> {
/// Generates the cell grid, given the tracks and cells.
pub fn new(
tracks: Axes<&[Sizing]>,
gutter: Axes<&[Sizing]>,
- cells: impl IntoIterator<Item = Cell>,
+ cells: impl IntoIterator<Item = Cell<'a>>,
) -> Self {
let entries = cells.into_iter().map(Entry::Cell).collect();
Self::new_internal(tracks, gutter, vec![], vec![], None, None, entries)
@@ -342,6 +367,7 @@ impl CellGrid {
pub fn resolve<T, C, I>(
tracks: Axes<&[Sizing]>,
gutter: Axes<&[Sizing]>,
+ locator: Locator<'a>,
children: C,
fill: &Celled<Option<Paint>>,
align: &Celled<Smart<Alignment>>,
@@ -357,6 +383,8 @@ impl CellGrid {
C: IntoIterator<Item = ResolvableGridChild<T, I>>,
C::IntoIter: ExactSizeIterator,
{
+ let mut locator = locator.split();
+
// Number of content columns: Always at least one.
let c = tracks.x.len().max(1);
@@ -660,6 +688,7 @@ impl CellGrid {
inset.resolve(engine, styles, x, y)?,
stroke.resolve(engine, styles, x, y)?,
resolve_breakable(y, rowspan),
+ locator.next(&cell_span),
styles,
);
@@ -687,7 +716,7 @@ impl CellGrid {
// (they can be overridden later); however, if no cells
// occupy them as we finish building the grid, then such
// positions will be replaced by empty cells.
- resolved_cells.resize(new_len, None);
+ resolved_cells.resize_with(new_len, || None);
}
// The vector is large enough to contain the cell, so we can
@@ -921,6 +950,7 @@ impl CellGrid {
inset.resolve(engine, styles, x, y)?,
stroke.resolve(engine, styles, x, y)?,
resolve_breakable(y, 1),
+ locator.next(&()),
styles,
);
Ok(Entry::Cell(new_cell))
@@ -1101,7 +1131,7 @@ impl CellGrid {
hlines: Vec<Vec<Line>>,
header: Option<Repeatable<Header>>,
footer: Option<Repeatable<Footer>>,
- entries: Vec<Entry>,
+ entries: Vec<Entry<'a>>,
) -> Self {
let mut cols = vec![];
let mut rows = vec![];
@@ -1163,7 +1193,7 @@ impl CellGrid {
///
/// Returns `None` if it's a gutter cell.
#[track_caller]
- pub(super) fn entry(&self, x: usize, y: usize) -> Option<&Entry> {
+ pub(super) fn entry(&self, x: usize, y: usize) -> Option<&Entry<'a>> {
assert!(x < self.cols.len());
assert!(y < self.rows.len());
@@ -1185,7 +1215,7 @@ impl CellGrid {
///
/// Returns `None` if it's a gutter cell or merged position.
#[track_caller]
- pub(super) fn cell(&self, x: usize, y: usize) -> Option<&Cell> {
+ pub(super) fn cell(&self, x: usize, y: usize) -> Option<&Cell<'a>> {
self.entry(x, y).and_then(Entry::as_cell)
}
diff --git a/crates/typst/src/layout/grid/layout.rs b/crates/typst/src/layout/grid/layout.rs
index ec9d1e15..9c5d7c21 100644
--- a/crates/typst/src/layout/grid/layout.rs
+++ b/crates/typst/src/layout/grid/layout.rs
@@ -21,7 +21,7 @@ use crate::visualize::Geometry;
/// Performs grid layout.
pub struct GridLayouter<'a> {
/// The grid of cells.
- pub(super) grid: &'a CellGrid,
+ pub(super) grid: &'a CellGrid<'a>,
/// The regions to layout children into.
pub(super) regions: Regions<'a>,
/// The inherited styles.
@@ -78,8 +78,8 @@ pub(super) enum Row {
/// where this row is laid out, and it can only be false when a row uses
/// `layout_multi_row`, which in turn is only used by breakable auto rows.
Frame(Frame, usize, bool),
- /// Fractional row with y index.
- Fr(Fr, usize),
+ /// Fractional row with y index and disambiguator.
+ Fr(Fr, usize, usize),
}
impl Row {
@@ -87,7 +87,7 @@ impl Row {
fn index(&self) -> usize {
match self {
Self::Frame(_, y, _) => *y,
- Self::Fr(_, y) => *y,
+ Self::Fr(_, y, _) => *y,
}
}
}
@@ -97,7 +97,7 @@ impl<'a> GridLayouter<'a> {
///
/// This prepares grid layout by unifying content and gutter tracks.
pub fn new(
- grid: &'a CellGrid,
+ grid: &'a CellGrid<'a>,
regions: Regions<'a>,
styles: StyleChain<'a>,
span: Span,
@@ -133,7 +133,7 @@ impl<'a> GridLayouter<'a> {
if let Some(Repeatable::Repeated(footer)) = &self.grid.footer {
// Ensure rows in the first region will be aware of the possible
// presence of the footer.
- self.prepare_footer(footer, engine)?;
+ self.prepare_footer(footer, engine, 0)?;
if matches!(self.grid.header, None | Some(Repeatable::NotRepeated(_))) {
// No repeatable header, so we won't subtract it later.
self.regions.size.y -= self.footer_height;
@@ -144,7 +144,7 @@ impl<'a> GridLayouter<'a> {
if let Some(Repeatable::Repeated(header)) = &self.grid.header {
if y < header.end {
if y == 0 {
- self.layout_header(header, engine)?;
+ self.layout_header(header, engine, 0)?;
self.regions.size.y -= self.footer_height;
}
// Skip header rows during normal layout.
@@ -155,16 +155,16 @@ impl<'a> GridLayouter<'a> {
if let Some(Repeatable::Repeated(footer)) = &self.grid.footer {
if y >= footer.start {
if y == footer.start {
- self.layout_footer(footer, engine)?;
+ self.layout_footer(footer, engine, self.finished.len())?;
}
continue;
}
}
- self.layout_row(y, engine)?;
+ self.layout_row(y, engine, 0)?;
}
- self.finish_region(engine)?;
+ self.finish_region(engine, true)?;
// Layout any missing rowspans.
// There are only two possibilities for rowspans not yet laid out
@@ -189,27 +189,30 @@ impl<'a> GridLayouter<'a> {
&mut self,
y: usize,
engine: &mut Engine,
+ disambiguator: usize,
) -> SourceResult<()> {
// Skip to next region if current one is full, but only for content
// rows, not for gutter rows, and only if we aren't laying out an
// unbreakable group of rows.
let is_content_row = !self.grid.is_gutter_track(y);
if self.unbreakable_rows_left == 0 && self.regions.is_full() && is_content_row {
- self.finish_region(engine)?;
+ self.finish_region(engine, false)?;
}
if is_content_row {
// Gutter rows have no rowspans or possibly unbreakable cells.
- self.check_for_rowspans(y);
+ self.check_for_rowspans(disambiguator, y);
self.check_for_unbreakable_rows(y, engine)?;
}
// Don't layout gutter rows at the top of a region.
if is_content_row || !self.lrows.is_empty() {
match self.grid.rows[y] {
- Sizing::Auto => self.layout_auto_row(engine, y)?,
- Sizing::Rel(v) => self.layout_relative_row(engine, v, y)?,
- Sizing::Fr(v) => self.lrows.push(Row::Fr(v, y)),
+ Sizing::Auto => self.layout_auto_row(engine, disambiguator, y)?,
+ Sizing::Rel(v) => {
+ self.layout_relative_row(engine, disambiguator, v, y)?
+ }
+ Sizing::Fr(v) => self.lrows.push(Row::Fr(v, y, disambiguator)),
}
}
@@ -841,7 +844,7 @@ impl<'a> GridLayouter<'a> {
let size = Size::new(available, height);
let pod = Regions::one(size, Axes::splat(false));
- let frame = cell.body.measure(engine, self.styles, pod)?.into_frame();
+ let frame = cell.layout(engine, 0, self.styles, pod)?.into_frame();
resolved.set_max(frame.width() - already_covered_width);
}
@@ -901,11 +904,17 @@ impl<'a> GridLayouter<'a> {
/// Layout a row with automatic height. Such a row may break across multiple
/// regions.
- fn layout_auto_row(&mut self, engine: &mut Engine, y: usize) -> SourceResult<()> {
+ fn layout_auto_row(
+ &mut self,
+ engine: &mut Engine,
+ disambiguator: usize,
+ y: usize,
+ ) -> SourceResult<()> {
// Determine the size for each region of the row. If the first region
// ends up empty for some column, skip the region and remeasure.
let mut resolved = match self.measure_auto_row(
engine,
+ disambiguator,
y,
true,
self.unbreakable_rows_left,
@@ -913,9 +922,16 @@ impl<'a> GridLayouter<'a> {
)? {
Some(resolved) => resolved,
None => {
- self.finish_region(engine)?;
- self.measure_auto_row(engine, y, false, self.unbreakable_rows_left, None)?
- .unwrap()
+ self.finish_region(engine, false)?;
+ self.measure_auto_row(
+ engine,
+ disambiguator,
+ y,
+ false,
+ self.unbreakable_rows_left,
+ None,
+ )?
+ .unwrap()
}
};
@@ -926,7 +942,7 @@ impl<'a> GridLayouter<'a> {
// Layout into a single region.
if let &[first] = resolved.as_slice() {
- let frame = self.layout_single_row(engine, first, y)?;
+ let frame = self.layout_single_row(engine, disambiguator, first, y)?;
self.push_row(frame, y, true);
if self
@@ -966,12 +982,12 @@ impl<'a> GridLayouter<'a> {
}
// Layout into multiple regions.
- let fragment = self.layout_multi_row(engine, &resolved, y)?;
+ let fragment = self.layout_multi_row(engine, disambiguator, &resolved, y)?;
let len = fragment.len();
for (i, frame) in fragment.into_iter().enumerate() {
self.push_row(frame, y, i + 1 == len);
if i + 1 < len {
- self.finish_region(engine)?;
+ self.finish_region(engine, false)?;
}
}
@@ -989,6 +1005,7 @@ impl<'a> GridLayouter<'a> {
pub(super) fn measure_auto_row(
&self,
engine: &mut Engine,
+ disambiguator: usize,
y: usize,
can_skip: bool,
unbreakable_rows_left: usize,
@@ -1069,7 +1086,8 @@ impl<'a> GridLayouter<'a> {
pod
};
- let frames = cell.body.measure(engine, self.styles, pod)?.into_frames();
+ let frames =
+ cell.layout(engine, disambiguator, self.styles, pod)?.into_frames();
// Skip the first region if one cell in it is empty. Then,
// remeasure.
@@ -1145,6 +1163,7 @@ impl<'a> GridLayouter<'a> {
&pending_rowspans,
unbreakable_rows_left,
row_group_data,
+ disambiguator,
engine,
)?;
}
@@ -1159,11 +1178,12 @@ impl<'a> GridLayouter<'a> {
fn layout_relative_row(
&mut self,
engine: &mut Engine,
+ disambiguator: usize,
v: Rel<Length>,
y: usize,
) -> SourceResult<()> {
let resolved = v.resolve(self.styles).relative_to(self.regions.base().y);
- let frame = self.layout_single_row(engine, resolved, y)?;
+ let frame = self.layout_single_row(engine, disambiguator, resolved, y)?;
if self
.grid
@@ -1185,7 +1205,7 @@ impl<'a> GridLayouter<'a> {
&& !self.regions.size.y.fits(height)
&& !in_last_with_offset(self.regions, self.header_height + self.footer_height)
{
- self.finish_region(engine)?;
+ self.finish_region(engine, false)?;
// Don't skip multiple regions for gutter and don't push a row.
if self.grid.is_gutter_track(y) {
@@ -1202,6 +1222,7 @@ impl<'a> GridLayouter<'a> {
fn layout_single_row(
&mut self,
engine: &mut Engine,
+ disambiguator: usize,
height: Abs,
y: usize,
) -> SourceResult<Frame> {
@@ -1232,7 +1253,9 @@ impl<'a> GridLayouter<'a> {
// rows.
pod.full = self.regions.full;
}
- let frame = cell.body.layout(engine, self.styles, pod)?.into_frame();
+ let frame = cell
+ .layout(engine, disambiguator, self.styles, pod)?
+ .into_frame();
let mut pos = pos;
if self.is_rtl {
// In the grid, cell colspans expand to the right,
@@ -1261,6 +1284,7 @@ impl<'a> GridLayouter<'a> {
fn layout_multi_row(
&mut self,
engine: &mut Engine,
+ disambiguator: usize,
heights: &[Abs],
y: usize,
) -> SourceResult<Fragment> {
@@ -1286,7 +1310,8 @@ impl<'a> GridLayouter<'a> {
pod.size.x = width;
// Push the layouted frames into the individual output frames.
- let fragment = cell.body.layout(engine, self.styles, pod)?;
+ let fragment =
+ cell.layout(engine, disambiguator, self.styles, pod)?;
for (output, frame) in outputs.iter_mut().zip(fragment) {
let mut pos = pos;
if self.is_rtl {
@@ -1314,7 +1339,11 @@ impl<'a> GridLayouter<'a> {
}
/// Finish rows for one region.
- pub(super) fn finish_region(&mut self, engine: &mut Engine) -> SourceResult<()> {
+ pub(super) fn finish_region(
+ &mut self,
+ engine: &mut Engine,
+ last: bool,
+ ) -> SourceResult<()> {
if self
.lrows
.last()
@@ -1369,7 +1398,7 @@ impl<'a> GridLayouter<'a> {
&& self.lrows.iter().all(|row| row.index() < footer.start)
{
laid_out_footer_start = Some(footer.start);
- self.layout_footer(footer, engine)?;
+ self.layout_footer(footer, engine, self.finished.len())?;
}
}
@@ -1379,7 +1408,7 @@ impl<'a> GridLayouter<'a> {
for row in &self.lrows {
match row {
Row::Frame(frame, _, _) => used += frame.height(),
- Row::Fr(v, _) => fr += *v,
+ Row::Fr(v, _, _) => fr += *v,
}
}
@@ -1400,10 +1429,10 @@ impl<'a> GridLayouter<'a> {
for row in std::mem::take(&mut self.lrows) {
let (frame, y, is_last) = match row {
Row::Frame(frame, y, is_last) => (frame, y, is_last),
- Row::Fr(v, y) => {
+ Row::Fr(v, y, disambiguator) => {
let remaining = self.regions.full - used;
let height = v.share(fr, remaining);
- (self.layout_single_row(engine, height, y)?, y, true)
+ (self.layout_single_row(engine, disambiguator, height, y)?, y, true)
}
};
@@ -1499,17 +1528,20 @@ impl<'a> GridLayouter<'a> {
self.finish_region_internal(output, rrows);
- if let Some(Repeatable::Repeated(footer)) = &self.grid.footer {
- self.prepare_footer(footer, engine)?;
- }
+ if !last {
+ let disambiguator = self.finished.len();
+ if let Some(Repeatable::Repeated(footer)) = &self.grid.footer {
+ self.prepare_footer(footer, engine, disambiguator)?;
+ }
- if let Some(Repeatable::Repeated(header)) = &self.grid.header {
- // Add a header to the new region.
- self.layout_header(header, engine)?;
- }
+ if let Some(Repeatable::Repeated(header)) = &self.grid.header {
+ // Add a header to the new region.
+ self.layout_header(header, engine, disambiguator)?;
+ }
- // Ensure rows don't try to overrun the footer.
- self.regions.size.y -= self.footer_height;
+ // Ensure rows don't try to overrun the footer.
+ self.regions.size.y -= self.footer_height;
+ }
Ok(())
}
diff --git a/crates/typst/src/layout/grid/lines.rs b/crates/typst/src/layout/grid/lines.rs
index 469f5305..660811c7 100644
--- a/crates/typst/src/layout/grid/lines.rs
+++ b/crates/typst/src/layout/grid/lines.rs
@@ -602,12 +602,14 @@ mod test {
use super::super::cells::Entry;
use super::*;
use crate::foundations::Content;
+ use crate::introspection::Locator;
use crate::layout::{Axes, Cell, Sides, Sizing};
use crate::utils::NonZeroExt;
- fn sample_cell() -> Cell {
+ fn sample_cell() -> Cell<'static> {
Cell {
body: Content::default(),
+ locator: Locator::root(),
fill: None,
colspan: NonZeroUsize::ONE,
rowspan: NonZeroUsize::ONE,
@@ -617,9 +619,10 @@ mod test {
}
}
- fn cell_with_colspan_rowspan(colspan: usize, rowspan: usize) -> Cell {
+ fn cell_with_colspan_rowspan(colspan: usize, rowspan: usize) -> Cell<'static> {
Cell {
body: Content::default(),
+ locator: Locator::root(),
fill: None,
colspan: NonZeroUsize::try_from(colspan).unwrap(),
rowspan: NonZeroUsize::try_from(rowspan).unwrap(),
@@ -629,7 +632,7 @@ mod test {
}
}
- fn sample_grid_for_vlines(gutters: bool) -> CellGrid {
+ fn sample_grid_for_vlines(gutters: bool) -> CellGrid<'static> {
const COLS: usize = 4;
const ROWS: usize = 6;
let entries = vec![
@@ -1152,7 +1155,7 @@ mod test {
}
}
- fn sample_grid_for_hlines(gutters: bool) -> CellGrid {
+ fn sample_grid_for_hlines(gutters: bool) -> CellGrid<'static> {
const COLS: usize = 4;
const ROWS: usize = 9;
let entries = vec![
diff --git a/crates/typst/src/layout/grid/mod.rs b/crates/typst/src/layout/grid/mod.rs
index 57e28730..326e356a 100644
--- a/crates/typst/src/layout/grid/mod.rs
+++ b/crates/typst/src/layout/grid/mod.rs
@@ -22,6 +22,7 @@ use crate::foundations::{
cast, elem, scope, Array, Content, Fold, NativeElement, Packed, Show, Smart,
StyleChain, Value,
};
+use crate::introspection::Locator;
use crate::layout::{
Abs, Alignment, Axes, BlockElem, Dir, Fragment, Length, OuterHAlignment,
OuterVAlignment, Regions, Rel, Sides, Sizing,
@@ -338,7 +339,9 @@ impl GridElem {
impl Show for Packed<GridElem> {
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::multi_layouter(self.clone(), layout_grid).pack())
+ Ok(BlockElem::multi_layouter(self.clone(), layout_grid)
+ .pack()
+ .spanned(self.span()))
}
}
@@ -347,6 +350,7 @@ impl Show for Packed<GridElem> {
fn layout_grid(
elem: &Packed<GridElem>,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
@@ -380,6 +384,7 @@ fn layout_grid(
let grid = CellGrid::resolve(
tracks,
gutter,
+ locator,
children,
fill,
align,
@@ -854,7 +859,7 @@ impl Default for Packed<GridCell> {
}
impl ResolvableCell for Packed<GridCell> {
- fn resolve_cell(
+ fn resolve_cell<'a>(
mut self,
x: usize,
y: usize,
@@ -863,8 +868,9 @@ impl ResolvableCell for Packed<GridCell> {
inset: Sides<Option<Rel<Length>>>,
stroke: Sides<Option<Option<Arc<Stroke<Abs>>>>>,
breakable: bool,
+ locator: Locator<'a>,
styles: StyleChain,
- ) -> Cell {
+ ) -> Cell<'a> {
let cell = &mut *self;
let colspan = cell.colspan(styles);
let rowspan = cell.rowspan(styles);
@@ -916,6 +922,7 @@ impl ResolvableCell for Packed<GridCell> {
cell.push_breakable(Smart::Custom(breakable));
Cell {
body: self.pack(),
+ locator,
fill,
colspan,
rowspan,
diff --git a/crates/typst/src/layout/grid/repeated.rs b/crates/typst/src/layout/grid/repeated.rs
index f0e9a4c7..acb00f04 100644
--- a/crates/typst/src/layout/grid/repeated.rs
+++ b/crates/typst/src/layout/grid/repeated.rs
@@ -50,8 +50,10 @@ impl<'a> GridLayouter<'a> {
&mut self,
header: &Header,
engine: &mut Engine,
+ disambiguator: usize,
) -> SourceResult<()> {
- let header_rows = self.simulate_header(header, &self.regions, engine)?;
+ let header_rows =
+ self.simulate_header(header, &self.regions, engine, disambiguator)?;
let mut skipped_region = false;
while self.unbreakable_rows_left == 0
&& !self.regions.size.y.fits(header_rows.height + self.footer_height)
@@ -71,8 +73,9 @@ impl<'a> GridLayouter<'a> {
if skipped_region {
// Simulate the footer again; the region's 'full' might have
// changed.
- self.footer_height =
- self.simulate_footer(footer, &self.regions, engine)?.height;
+ self.footer_height = self
+ .simulate_footer(footer, &self.regions, engine, disambiguator)?
+ .height;
}
}
@@ -81,7 +84,7 @@ impl<'a> GridLayouter<'a> {
// within 'layout_row'.
self.unbreakable_rows_left += header.end;
for y in 0..header.end {
- self.layout_row(y, engine)?;
+ self.layout_row(y, engine, disambiguator)?;
}
Ok(())
}
@@ -92,16 +95,20 @@ impl<'a> GridLayouter<'a> {
header: &Header,
regions: &Regions<'_>,
engine: &mut Engine,
+ disambiguator: usize,
) -> SourceResult<UnbreakableRowGroup> {
// Note that we assume the invariant that any rowspan in a header is
// fully contained within that header. Therefore, there won't be any
// unbreakable rowspans exceeding the header's rows, and we can safely
// assume that the amount of unbreakable rows following the first row
// in the header will be precisely the rows in the header.
- let header_row_group =
- self.simulate_unbreakable_row_group(0, Some(header.end), regions, engine)?;
-
- Ok(header_row_group)
+ self.simulate_unbreakable_row_group(
+ 0,
+ Some(header.end),
+ regions,
+ engine,
+ disambiguator,
+ )
}
/// Updates `self.footer_height` by simulating the footer, and skips to fitting region.
@@ -109,8 +116,11 @@ impl<'a> GridLayouter<'a> {
&mut self,
footer: &Footer,
engine: &mut Engine,
+ disambiguator: usize,
) -> SourceResult<()> {
- let footer_height = self.simulate_footer(footer, &self.regions, engine)?.height;
+ let footer_height = self
+ .simulate_footer(footer, &self.regions, engine, disambiguator)?
+ .height;
let mut skipped_region = false;
while self.unbreakable_rows_left == 0
&& !self.regions.size.y.fits(footer_height)
@@ -125,7 +135,8 @@ impl<'a> GridLayouter<'a> {
self.footer_height = if skipped_region {
// Simulate the footer again; the region's 'full' might have
// changed.
- self.simulate_footer(footer, &self.regions, engine)?.height
+ self.simulate_footer(footer, &self.regions, engine, disambiguator)?
+ .height
} else {
footer_height
};
@@ -139,6 +150,7 @@ impl<'a> GridLayouter<'a> {
&mut self,
footer: &Footer,
engine: &mut Engine,
+ disambiguator: usize,
) -> SourceResult<()> {
// Ensure footer rows have their own height available.
// Won't change much as we're creating an unbreakable row group
@@ -148,7 +160,7 @@ impl<'a> GridLayouter<'a> {
let footer_len = self.grid.rows.len() - footer.start;
self.unbreakable_rows_left += footer_len;
for y in footer.start..self.grid.rows.len() {
- self.layout_row(y, engine)?;
+ self.layout_row(y, engine, disambiguator)?;
}
Ok(())
@@ -160,19 +172,19 @@ impl<'a> GridLayouter<'a> {
footer: &Footer,
regions: &Regions<'_>,
engine: &mut Engine,
+ disambiguator: usize,
) -> SourceResult<UnbreakableRowGroup> {
// Note that we assume the invariant that any rowspan in a footer is
// fully contained within that footer. Therefore, there won't be any
// unbreakable rowspans exceeding the footer's rows, and we can safely
// assume that the amount of unbreakable rows following the first row
// in the footer will be precisely the rows in the footer.
- let footer_row_group = self.simulate_unbreakable_row_group(
+ self.simulate_unbreakable_row_group(
footer.start,
Some(self.grid.rows.len() - footer.start),
regions,
engine,
- )?;
-
- Ok(footer_row_group)
+ disambiguator,
+ )
}
}
diff --git a/crates/typst/src/layout/grid/rowspans.rs b/crates/typst/src/layout/grid/rowspans.rs
index 282616ad..85ec49c9 100644
--- a/crates/typst/src/layout/grid/rowspans.rs
+++ b/crates/typst/src/layout/grid/rowspans.rs
@@ -12,6 +12,8 @@ pub(super) struct Rowspan {
pub(super) x: usize,
/// First row of this rowspan.
pub(super) y: usize,
+ /// The disambiguator for laying out the cells.
+ pub(super) disambiguator: usize,
/// Amount of rows spanned by the cell at (x, y).
pub(super) rowspan: usize,
/// Whether all rows of the rowspan are part of an unbreakable row group.
@@ -100,6 +102,7 @@ impl<'a> GridLayouter<'a> {
let Rowspan {
x,
y,
+ disambiguator,
rowspan,
is_effectively_unbreakable,
dx,
@@ -136,7 +139,7 @@ impl<'a> GridLayouter<'a> {
}
// Push the layouted frames directly into the finished frames.
- let fragment = cell.body.layout(engine, self.styles, pod)?;
+ let fragment = cell.layout(engine, disambiguator, self.styles, pod)?;
let (current_region, current_rrows) = current_region_data.unzip();
for ((i, finished), frame) in self
.finished
@@ -179,7 +182,7 @@ impl<'a> GridLayouter<'a> {
/// Checks if a row contains the beginning of one or more rowspan cells.
/// If so, adds them to the rowspans vector.
- pub(super) fn check_for_rowspans(&mut self, y: usize) {
+ pub(super) fn check_for_rowspans(&mut self, disambiguator: usize, y: usize) {
// We will compute the horizontal offset of each rowspan in advance.
// For that reason, we must reverse the column order when using RTL.
let offsets = points(self.rcols.iter().copied().rev_if(self.is_rtl));
@@ -193,6 +196,7 @@ impl<'a> GridLayouter<'a> {
self.rowspans.push(Rowspan {
x,
y,
+ disambiguator,
rowspan,
// The field below will be updated in
// 'check_for_unbreakable_rows'.
@@ -241,6 +245,7 @@ impl<'a> GridLayouter<'a> {
amount_unbreakable_rows,
&self.regions,
engine,
+ 0,
)?;
// Skip to fitting region.
@@ -250,7 +255,7 @@ impl<'a> GridLayouter<'a> {
self.header_height + self.footer_height,
)
{
- self.finish_region(engine)?;
+ self.finish_region(engine, false)?;
}
// Update unbreakable rows left.
@@ -291,6 +296,7 @@ impl<'a> GridLayouter<'a> {
amount_unbreakable_rows: Option<usize>,
regions: &Regions<'_>,
engine: &mut Engine,
+ disambiguator: usize,
) -> SourceResult<UnbreakableRowGroup> {
let mut row_group = UnbreakableRowGroup::default();
let mut unbreakable_rows_left = amount_unbreakable_rows.unwrap_or(0);
@@ -319,6 +325,7 @@ impl<'a> GridLayouter<'a> {
Sizing::Auto => self
.measure_auto_row(
engine,
+ disambiguator,
y,
false,
unbreakable_rows_left,
@@ -657,6 +664,7 @@ impl<'a> GridLayouter<'a> {
/// auto row will have to expand, given the current sizes of the auto row
/// in each region and the pending rowspans' data (parent Y, rowspan amount
/// and vector of requested sizes).
+ #[allow(clippy::too_many_arguments)]
pub(super) fn simulate_and_measure_rowspans_in_auto_row(
&self,
y: usize,
@@ -664,6 +672,7 @@ impl<'a> GridLayouter<'a> {
pending_rowspans: &[(usize, usize, Vec<Abs>)],
unbreakable_rows_left: usize,
row_group_data: Option<&UnbreakableRowGroup>,
+ mut disambiguator: usize,
engine: &mut Engine,
) -> SourceResult<()> {
// To begin our simulation, we have to unify the sizes demanded by
@@ -726,6 +735,7 @@ impl<'a> GridLayouter<'a> {
// expand) because we popped the last resolved size from the
// resolved vector, above.
simulated_regions.next();
+ disambiguator += 1;
// Subtract the initial header and footer height, since that's the
// height we used when subtracting from the region backlog's
@@ -749,6 +759,7 @@ impl<'a> GridLayouter<'a> {
engine,
last_resolved_size,
unbreakable_rows_left,
+ disambiguator,
)?;
if !simulations_stabilized {
@@ -839,6 +850,7 @@ impl<'a> GridLayouter<'a> {
engine: &mut Engine,
last_resolved_size: Option<Abs>,
unbreakable_rows_left: usize,
+ mut disambiguator: usize,
) -> SourceResult<bool> {
// The max amount this row can expand will be the total size requested
// by rowspans which was not yet resolved. It is worth noting that,
@@ -861,6 +873,7 @@ impl<'a> GridLayouter<'a> {
// of the requested rowspan height, we give up.
for _attempt in 0..5 {
let rowspan_simulator = RowspanSimulator::new(
+ disambiguator,
simulated_regions,
self.header_height,
self.footer_height,
@@ -947,6 +960,7 @@ impl<'a> GridLayouter<'a> {
extra_amount_to_grow -= simulated_regions.size.y.max(Abs::zero());
simulated_regions.next();
simulated_regions.size.y -= self.header_height + self.footer_height;
+ disambiguator += 1;
}
simulated_regions.size.y -= extra_amount_to_grow;
}
@@ -958,6 +972,8 @@ impl<'a> GridLayouter<'a> {
/// Auxiliary structure holding state during rowspan simulation.
struct RowspanSimulator<'a> {
+ /// The number of finished regions.
+ finished: usize,
/// The state of regions during the simulation.
regions: Regions<'a>,
/// The height of the header in the currently simulated region.
@@ -974,8 +990,14 @@ struct RowspanSimulator<'a> {
impl<'a> RowspanSimulator<'a> {
/// Creates new rowspan simulation state with the given regions and initial
/// header and footer heights. Other fields should always start as zero.
- fn new(regions: Regions<'a>, header_height: Abs, footer_height: Abs) -> Self {
+ fn new(
+ finished: usize,
+ regions: Regions<'a>,
+ header_height: Abs,
+ footer_height: Abs,
+ ) -> Self {
Self {
+ finished,
regions,
header_height,
footer_height,
@@ -1024,6 +1046,7 @@ impl<'a> RowspanSimulator<'a> {
None,
&self.regions,
engine,
+ 0,
)?;
while !self.regions.size.y.fits(row_group.height)
&& !in_last_with_offset(
@@ -1099,16 +1122,21 @@ impl<'a> RowspanSimulator<'a> {
// backlog to consider the initial header and footer heights; however,
// our simulation checks what happens AFTER the auto row, so we can
// just use the original backlog from `self.regions`.
+ let disambiguator = self.finished;
let header_height =
if let Some(Repeatable::Repeated(header)) = &layouter.grid.header {
- layouter.simulate_header(header, &self.regions, engine)?.height
+ layouter
+ .simulate_header(header, &self.regions, engine, disambiguator)?
+ .height
} else {
Abs::zero()
};
let footer_height =
if let Some(Repeatable::Repeated(footer)) = &layouter.grid.footer {
- layouter.simulate_footer(footer, &self.regions, engine)?.height
+ layouter
+ .simulate_footer(footer, &self.regions, engine, disambiguator)?
+ .height
} else {
Abs::zero()
};
@@ -1120,6 +1148,7 @@ impl<'a> RowspanSimulator<'a> {
&& !self.regions.in_last()
{
self.regions.next();
+ self.finished += 1;
skipped_region = true;
}
@@ -1127,7 +1156,9 @@ impl<'a> RowspanSimulator<'a> {
self.header_height = if skipped_region {
// Simulate headers again, at the new region, as
// the full region height may change.
- layouter.simulate_header(header, &self.regions, engine)?.height
+ layouter
+ .simulate_header(header, &self.regions, engine, disambiguator)?
+ .height
} else {
header_height
};
@@ -1137,7 +1168,9 @@ impl<'a> RowspanSimulator<'a> {
self.footer_height = if skipped_region {
// Simulate footers again, at the new region, as
// the full region height may change.
- layouter.simulate_footer(footer, &self.regions, engine)?.height
+ layouter
+ .simulate_footer(footer, &self.regions, engine, disambiguator)?
+ .height
} else {
footer_height
};
@@ -1162,6 +1195,7 @@ impl<'a> RowspanSimulator<'a> {
self.total_spanned_height -= self.latest_spanned_gutter_height;
self.latest_spanned_gutter_height = Abs::zero();
self.regions.next();
+ self.finished += 1;
self.simulate_header_footer_layout(layouter, engine)
}
diff --git a/crates/typst/src/layout/inline/mod.rs b/crates/typst/src/layout/inline/mod.rs
index 4fffa7eb..e6e3d88c 100644
--- a/crates/typst/src/layout/inline/mod.rs
+++ b/crates/typst/src/layout/inline/mod.rs
@@ -1,7 +1,7 @@
mod linebreak;
mod shaping;
-use comemo::{Tracked, TrackedMut};
+use comemo::{Track, Tracked, TrackedMut};
use unicode_bidi::{BidiInfo, Level as BidiLevel};
use unicode_script::{Script, UnicodeScript};
@@ -14,7 +14,7 @@ use crate::diag::{bail, SourceResult};
use crate::engine::{Engine, Route};
use crate::eval::Tracer;
use crate::foundations::{Packed, Resolve, Smart, StyleChain};
-use crate::introspection::{Introspector, Locator, TagElem};
+use crate::introspection::{Introspector, Locator, LocatorLink, Tag, TagElem};
use crate::layout::{
Abs, AlignElem, BoxElem, Dir, Em, FixedAlignment, Fr, Fragment, Frame, FrameItem,
HElem, InlineElem, InlineItem, Point, Size, Sizing, Spacing,
@@ -33,6 +33,7 @@ use crate::World;
pub(crate) fn layout_inline(
children: &StyleVec,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
consecutive: bool,
region: Size,
@@ -45,25 +46,25 @@ pub(crate) fn layout_inline(
world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>,
route: Tracked<Route>,
- locator: Tracked<Locator>,
tracer: TrackedMut<Tracer>,
+ locator: Tracked<Locator>,
styles: StyleChain,
consecutive: bool,
region: Size,
expand: bool,
) -> SourceResult<Fragment> {
- let mut locator = Locator::chained(locator);
+ let link = LocatorLink::new(locator);
+ let locator = Locator::link(&link);
let mut engine = Engine {
world,
introspector,
route: Route::extend(route),
- locator: &mut locator,
tracer,
};
// Collect all text into one string for BiDi analysis.
let (text, segments, spans) =
- collect(children, &mut engine, &styles, region, consecutive)?;
+ collect(children, &mut engine, locator, &styles, region, consecutive)?;
// Perform BiDi analysis and then prepare paragraph layout by building a
// representation on which we can do line breaking without layouting
@@ -78,21 +79,18 @@ pub(crate) fn layout_inline(
finalize(&mut engine, &p, &lines, region, expand, shrink)
}
- let fragment = cached(
+ cached(
children,
engine.world,
engine.introspector,
engine.route.track(),
- engine.locator.track(),
TrackedMut::reborrow_mut(&mut engine.tracer),
+ locator.track(),
styles,
consecutive,
region,
expand,
- )?;
-
- engine.locator.visit_frames(&fragment);
- Ok(fragment)
+ )
}
/// Range of a substring of text.
@@ -223,11 +221,11 @@ enum Item<'a> {
/// Absolute spacing between other items, and whether it is weak.
Absolute(Abs, bool),
/// Fractional spacing between other items.
- Fractional(Fr, Option<(&'a Packed<BoxElem>, StyleChain<'a>)>),
+ Fractional(Fr, Option<(&'a Packed<BoxElem>, Locator<'a>, StyleChain<'a>)>),
/// Layouted inline-level content.
Frame(Frame, StyleChain<'a>),
/// A tag.
- Tag(&'a Packed<TagElem>),
+ Tag(&'a Tag),
/// An item that is invisible and needs to be skipped, e.g. a Unicode
/// isolate.
Skip(&'static str),
@@ -431,12 +429,14 @@ impl<'a> Line<'a> {
fn collect<'a>(
children: &'a StyleVec,
engine: &mut Engine<'_>,
+ locator: Locator<'a>,
styles: &'a StyleChain<'a>,
region: Size,
consecutive: bool,
) -> SourceResult<(String, Vec<Segment<'a>>, SpanMapper)> {
let mut collector = Collector::new(2 + children.len());
let mut iter = children.chain(styles).peekable();
+ let mut locator = locator.split();
let first_line_indent = ParElem::first_line_indent_in(*styles);
if !first_line_indent.is_zero()
@@ -535,7 +535,7 @@ fn collect<'a>(
} else if let Some(elem) = child.to_packed::<InlineElem>() {
collector.push_item(Item::Skip(LTR_ISOLATE));
- for item in elem.layout(engine, styles, region)? {
+ for item in elem.layout(engine, locator.next(&elem.span()), styles, region)? {
match item {
InlineItem::Space(space, weak) => {
collector.push_item(Item::Absolute(space, weak));
@@ -548,14 +548,15 @@ fn collect<'a>(
collector.push_item(Item::Skip(POP_ISOLATE));
} else if let Some(elem) = child.to_packed::<BoxElem>() {
+ let loc = locator.next(&elem.span());
if let Sizing::Fr(v) = elem.width(styles) {
- collector.push_item(Item::Fractional(v, Some((elem, styles))));
+ collector.push_item(Item::Fractional(v, Some((elem, loc, styles))));
} else {
- let frame = elem.layout(engine, styles, region)?;
+ let frame = elem.layout(engine, loc, styles, region)?;
collector.push_item(Item::Frame(frame, styles));
}
} else if let Some(elem) = child.to_packed::<TagElem>() {
- collector.push_item(Item::Tag(elem));
+ collector.push_item(Item::Tag(&elem.tag));
} else {
bail!(child.span(), "unexpected paragraph child");
};
@@ -1408,9 +1409,10 @@ fn commit(
}
Item::Fractional(v, elem) => {
let amount = v.share(fr, remaining);
- if let Some((elem, styles)) = elem {
+ if let Some((elem, loc, styles)) = elem {
let region = Size::new(amount, full);
- let mut frame = elem.layout(engine, *styles, region)?;
+ let mut frame =
+ elem.layout(engine, loc.relayout(), *styles, region)?;
frame.post_process(*styles);
frame.translate(Point::with_y(TextElem::baseline_in(*styles)));
push(&mut offset, frame);
@@ -1432,7 +1434,7 @@ fn commit(
}
Item::Tag(tag) => {
let mut frame = Frame::soft(Size::zero());
- frame.push(Point::zero(), FrameItem::Tag(tag.elem.clone()));
+ frame.push(Point::zero(), FrameItem::Tag((*tag).clone()));
frames.push((offset, frame));
}
Item::Skip(_) => {}
diff --git a/crates/typst/src/layout/layout.rs b/crates/typst/src/layout/layout.rs
index b7293640..efe5d124 100644
--- a/crates/typst/src/layout/layout.rs
+++ b/crates/typst/src/layout/layout.rs
@@ -76,18 +76,26 @@ struct LayoutElem {
impl Show for Packed<LayoutElem> {
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::multi_layouter(self.clone(), |elem, engine, styles, regions| {
- // Gets the current region's base size, which will be the size of the
- // outer container, or of the page if there is no such container.
- let Size { x, y } = regions.base();
- let loc = elem.location().unwrap();
- let context = Context::new(Some(loc), Some(styles));
- let result = elem
- .func()
- .call(engine, context.track(), [dict! { "width" => x, "height" => y }])?
- .display();
- result.layout(engine, styles, regions)
- })
- .pack())
+ Ok(BlockElem::multi_layouter(
+ self.clone(),
+ |elem, engine, locator, styles, regions| {
+ // Gets the current region's base size, which will be the size of the
+ // outer container, or of the page if there is no such container.
+ let Size { x, y } = regions.base();
+ let loc = elem.location().unwrap();
+ let context = Context::new(Some(loc), Some(styles));
+ let result = elem
+ .func()
+ .call(
+ engine,
+ context.track(),
+ [dict! { "width" => x, "height" => y }],
+ )?
+ .display();
+ result.layout(engine, locator, styles, regions)
+ },
+ )
+ .pack()
+ .spanned(self.span()))
}
}
diff --git a/crates/typst/src/layout/measure.rs b/crates/typst/src/layout/measure.rs
index cae9d9e0..af093f24 100644
--- a/crates/typst/src/layout/measure.rs
+++ b/crates/typst/src/layout/measure.rs
@@ -5,6 +5,7 @@ use crate::engine::Engine;
use crate::foundations::{
dict, func, Content, Context, Dict, Resolve, Smart, StyleChain, Styles,
};
+use crate::introspection::{Locator, LocatorLink};
use crate::layout::{Abs, Axes, Length, Regions, Size};
use crate::syntax::Span;
@@ -85,13 +86,24 @@ pub fn measure(
None => context.styles().at(span)?,
};
- let available = Axes::new(
- width.resolve(styles).unwrap_or(Abs::inf()),
- height.resolve(styles).unwrap_or(Abs::inf()),
+ // Create a pod region with the available space.
+ let pod = Regions::one(
+ Axes::new(
+ width.resolve(styles).unwrap_or(Abs::inf()),
+ height.resolve(styles).unwrap_or(Abs::inf()),
+ ),
+ Axes::splat(false),
);
- let pod = Regions::one(available, Axes::splat(false));
- let frame = content.measure(engine, styles, pod)?.into_frame();
+ // We put the locator into a special "measurement mode" to ensure that
+ // introspection-driven features within the content continue to work. Read
+ // the "Dealing with measurement" section of the [`Locator`] docs for more
+ // details.
+ let here = context.location().at(span)?;
+ let link = LocatorLink::measure(here);
+ let locator = Locator::link(&link);
+
+ let frame = content.layout(engine, locator, styles, pod)?.into_frame();
let Size { x, y } = frame.size();
Ok(dict! { "width" => x, "height" => y })
}
diff --git a/crates/typst/src/layout/mod.rs b/crates/typst/src/layout/mod.rs
index ac1452ca..843b43f5 100644
--- a/crates/typst/src/layout/mod.rs
+++ b/crates/typst/src/layout/mod.rs
@@ -69,13 +69,13 @@ pub use self::transform::*;
pub(crate) use self::inline::*;
-use comemo::{Tracked, TrackedMut};
+use comemo::{Track, Tracked, TrackedMut};
use crate::diag::{bail, SourceResult};
use crate::engine::{Engine, Route};
use crate::eval::Tracer;
use crate::foundations::{category, Category, Content, Scope, StyleChain};
-use crate::introspection::{Introspector, Locator};
+use crate::introspection::{Introspector, Locator, LocatorLink};
use crate::model::Document;
use crate::realize::{realize_doc, realize_flow, Arenas};
use crate::World;
@@ -138,21 +138,20 @@ impl Content {
world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>,
route: Tracked<Route>,
- locator: Tracked<Locator>,
tracer: TrackedMut<Tracer>,
styles: StyleChain,
) -> SourceResult<Document> {
- let mut locator = Locator::chained(locator);
+ let mut locator = Locator::root().split();
let mut engine = Engine {
world,
introspector,
route: Route::extend(route).unnested(),
- locator: &mut locator,
tracer,
};
let arenas = Arenas::default();
- let (document, styles) = realize_doc(&mut engine, &arenas, content, styles)?;
- document.layout(&mut engine, styles)
+ let (document, styles) =
+ realize_doc(&mut engine, locator.next(&()), &arenas, content, styles)?;
+ document.layout(&mut engine, locator.next(&()), styles)
}
cached(
@@ -160,7 +159,6 @@ impl Content {
engine.world,
engine.introspector,
engine.route.track(),
- engine.locator.track(),
TrackedMut::reborrow_mut(&mut engine.tracer),
styles,
)
@@ -170,22 +168,7 @@ impl Content {
pub fn layout(
&self,
engine: &mut Engine,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- let fragment = self.measure(engine, styles, regions)?;
- engine.locator.visit_frames(&fragment);
- Ok(fragment)
- }
-
- /// Layout without side effects.
- ///
- /// For the results to be valid, the element must either be layouted again
- /// or the measurement must be confirmed through a call to
- /// `engine.locator.visit_frames(&fragment)`.
- pub fn measure(
- &self,
- engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
@@ -196,17 +179,17 @@ impl Content {
world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>,
route: Tracked<Route>,
- locator: Tracked<Locator>,
tracer: TrackedMut<Tracer>,
+ locator: Tracked<Locator>,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
- let mut locator = Locator::chained(locator);
+ let link = LocatorLink::new(locator);
+ let locator = Locator::link(&link);
let mut engine = Engine {
world,
introspector,
route: Route::extend(route),
- locator: &mut locator,
tracer,
};
@@ -219,14 +202,16 @@ impl Content {
// If we are in a `PageElem`, this might already be a realized flow.
if let Some(flow) = content.to_packed::<FlowElem>() {
- return flow.layout(&mut engine, styles, regions);
+ return flow.layout(&mut engine, locator, styles, regions);
}
// Layout the content by first turning it into a `FlowElem` and then
// layouting that.
+ let mut locator = locator.split();
let arenas = Arenas::default();
- let (flow, styles) = realize_flow(&mut engine, &arenas, content, styles)?;
- flow.layout(&mut engine, styles, regions)
+ let (flow, styles) =
+ realize_flow(&mut engine, locator.next(&()), &arenas, content, styles)?;
+ flow.layout(&mut engine, locator.next(&()), styles, regions)
}
cached(
@@ -234,8 +219,8 @@ impl Content {
engine.world,
engine.introspector,
engine.route.track(),
- engine.locator.track(),
TrackedMut::reborrow_mut(&mut engine.tracer),
+ locator.track(),
styles,
regions,
)
diff --git a/crates/typst/src/layout/pad.rs b/crates/typst/src/layout/pad.rs
index 6e1e6258..814ccdc5 100644
--- a/crates/typst/src/layout/pad.rs
+++ b/crates/typst/src/layout/pad.rs
@@ -3,6 +3,7 @@ use crate::engine::Engine;
use crate::foundations::{
elem, Content, NativeElement, Packed, Resolve, Show, StyleChain,
};
+use crate::introspection::Locator;
use crate::layout::{
Abs, BlockElem, Fragment, Frame, Length, Point, Regions, Rel, Sides, Size,
};
@@ -64,7 +65,9 @@ pub struct PadElem {
impl Show for Packed<PadElem> {
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::multi_layouter(self.clone(), layout_pad).pack())
+ Ok(BlockElem::multi_layouter(self.clone(), layout_pad)
+ .pack()
+ .spanned(self.span()))
}
}
@@ -73,6 +76,7 @@ impl Show for Packed<PadElem> {
fn layout_pad(
elem: &Packed<PadElem>,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
@@ -87,7 +91,7 @@ fn layout_pad(
let pod = regions.map(&mut backlog, |size| shrink(size, &padding));
// Layout child into padded regions.
- let mut fragment = elem.body().layout(engine, styles, pod)?;
+ let mut fragment = elem.body().layout(engine, locator, styles, pod)?;
for frame in &mut fragment {
grow(frame, &padding);
diff --git a/crates/typst/src/layout/page.rs b/crates/typst/src/layout/page.rs
index 42c267ed..1c1e0515 100644
--- a/crates/typst/src/layout/page.rs
+++ b/crates/typst/src/layout/page.rs
@@ -12,7 +12,9 @@ use crate::foundations::{
cast, elem, AutoValue, Cast, Content, Context, Dict, Fold, Func, NativeElement,
Packed, Resolve, Smart, StyleChain, Value,
};
-use crate::introspection::{Counter, CounterDisplayElem, CounterKey, ManualPageCounter};
+use crate::introspection::{
+ Counter, CounterDisplayElem, CounterKey, Locator, ManualPageCounter,
+};
use crate::layout::{
Abs, AlignElem, Alignment, Axes, ColumnsElem, Dir, Frame, HAlignment, Length,
OuterVAlignment, Point, Ratio, Regions, Rel, Sides, Size, SpecificAlignment,
@@ -349,10 +351,13 @@ impl Packed<PageElem> {
pub fn layout(
&self,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
page_counter: &mut ManualPageCounter,
extend_to: Option<Parity>,
) -> SourceResult<Vec<Page>> {
+ let mut locator = locator.split();
+
// When one of the lengths is infinite the page fits its content along
// that axis.
let width = self.width(styles).unwrap_or(Abs::inf());
@@ -400,7 +405,9 @@ impl Packed<PageElem> {
regions.root = true;
// Layout the child.
- let mut frames = child.layout(engine, styles, regions)?.into_frames();
+ let mut frames = child
+ .layout(engine, locator.next(&self.span()), styles, regions)?
+ .into_frames();
// Align the child to the pagebreak's parity.
// Check for page count after adding the pending frames
@@ -504,7 +511,7 @@ impl Packed<PageElem> {
let sub = content
.clone()
.styled(AlignElem::set_alignment(align))
- .layout(engine, styles, pod)?
+ .layout(engine, locator.next(&content.span()), styles, pod)?
.into_frame();
if ptr::eq(marginal, header) || ptr::eq(marginal, background) {
diff --git a/crates/typst/src/layout/place.rs b/crates/typst/src/layout/place.rs
index d8791327..78922c1b 100644
--- a/crates/typst/src/layout/place.rs
+++ b/crates/typst/src/layout/place.rs
@@ -1,6 +1,7 @@
use crate::diag::{bail, At, Hint, SourceResult};
use crate::engine::Engine;
use crate::foundations::{elem, scope, Content, Packed, Smart, StyleChain, Unlabellable};
+use crate::introspection::Locator;
use crate::layout::{
Alignment, Axes, Em, Fragment, Length, Regions, Rel, Size, VAlignment,
};
@@ -108,6 +109,7 @@ impl Packed<PlaceElem> {
pub fn layout(
&self,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
base: Size,
) -> SourceResult<Fragment> {
@@ -134,7 +136,7 @@ impl Packed<PlaceElem> {
.aligned(alignment.unwrap_or_else(|| Alignment::CENTER));
let pod = Regions::one(base, Axes::splat(false));
- let frame = child.layout(engine, styles, pod)?.into_frame();
+ let frame = child.layout(engine, locator, styles, pod)?.into_frame();
Ok(Fragment::frame(frame))
}
}
diff --git a/crates/typst/src/layout/repeat.rs b/crates/typst/src/layout/repeat.rs
index 08905466..4a0911e4 100644
--- a/crates/typst/src/layout/repeat.rs
+++ b/crates/typst/src/layout/repeat.rs
@@ -3,6 +3,7 @@ use crate::engine::Engine;
use crate::foundations::{
elem, Content, NativeElement, Packed, Resolve, Show, StyleChain,
};
+use crate::introspection::Locator;
use crate::layout::{
Abs, AlignElem, Axes, BlockElem, Fragment, Frame, Point, Regions, Size,
};
@@ -38,7 +39,9 @@ pub struct RepeatElem {
impl Show for Packed<RepeatElem> {
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::multi_layouter(self.clone(), layout_repeat).pack())
+ Ok(BlockElem::multi_layouter(self.clone(), layout_repeat)
+ .pack()
+ .spanned(self.span()))
}
}
@@ -47,11 +50,13 @@ impl Show for Packed<RepeatElem> {
fn layout_repeat(
elem: &Packed<RepeatElem>,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
let pod = Regions::one(regions.size, Axes::new(false, false));
- let piece = elem.body().layout(engine, styles, pod)?.into_frame();
+ let piece = elem.body().layout(engine, locator, styles, pod)?.into_frame();
+
let align = AlignElem::alignment_in(styles).resolve(styles);
let fill = regions.size.x;
diff --git a/crates/typst/src/layout/stack.rs b/crates/typst/src/layout/stack.rs
index 8271ff00..3d8002ab 100644
--- a/crates/typst/src/layout/stack.rs
+++ b/crates/typst/src/layout/stack.rs
@@ -6,6 +6,7 @@ use crate::engine::Engine;
use crate::foundations::{
cast, elem, Content, NativeElement, Packed, Resolve, Show, StyleChain, StyledElem,
};
+use crate::introspection::{Locator, SplitLocator};
use crate::layout::{
Abs, AlignElem, Axes, Axis, BlockElem, Dir, FixedAlignment, Fr, Fragment, Frame,
HElem, Point, Regions, Size, Spacing, VElem,
@@ -56,7 +57,9 @@ pub struct StackElem {
impl Show for Packed<StackElem> {
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::multi_layouter(self.clone(), layout_stack).pack())
+ Ok(BlockElem::multi_layouter(self.clone(), layout_stack)
+ .pack()
+ .spanned(self.span()))
}
}
@@ -93,10 +96,13 @@ cast! {
fn layout_stack(
elem: &Packed<StackElem>,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
- let mut layouter = StackLayouter::new(elem.span(), elem.dir(styles), regions, styles);
+ let mut layouter =
+ StackLayouter::new(elem.span(), elem.dir(styles), locator, styles, regions);
+
let axis = layouter.dir.axis();
// Spacing to insert before the next block.
@@ -145,10 +151,12 @@ struct StackLayouter<'a> {
dir: Dir,
/// The axis of the stacking direction.
axis: Axis,
- /// The regions to layout children into.
- regions: Regions<'a>,
+ /// Provides unique locations to the stack's children.
+ locator: SplitLocator<'a>,
/// The inherited styles.
styles: StyleChain<'a>,
+ /// The regions to layout children into.
+ regions: Regions<'a>,
/// Whether the stack itself should expand to fill the region.
expand: Axes<bool>,
/// The initial size of the current region before we started subtracting.
@@ -179,8 +187,9 @@ impl<'a> StackLayouter<'a> {
fn new(
span: Span,
dir: Dir,
- mut regions: Regions<'a>,
+ locator: Locator<'a>,
styles: StyleChain<'a>,
+ mut regions: Regions<'a>,
) -> Self {
let axis = dir.axis();
let expand = regions.expand;
@@ -192,8 +201,9 @@ impl<'a> StackLayouter<'a> {
span,
dir,
axis,
- regions,
+ locator: locator.split(),
styles,
+ regions,
expand,
initial: regions.size,
used: GenericSize::zero(),
@@ -247,7 +257,13 @@ impl<'a> StackLayouter<'a> {
}
.resolve(styles);
- let fragment = block.layout(engine, styles, self.regions)?;
+ let fragment = block.layout(
+ engine,
+ self.locator.next(&block.span()),
+ styles,
+ self.regions,
+ )?;
+
let len = fragment.len();
for (i, frame) in fragment.into_iter().enumerate() {
// Grow our size, shrink the region and save the frame for later.
diff --git a/crates/typst/src/layout/transform.rs b/crates/typst/src/layout/transform.rs
index 0e9b0ca6..7172466f 100644
--- a/crates/typst/src/layout/transform.rs
+++ b/crates/typst/src/layout/transform.rs
@@ -3,6 +3,7 @@ use crate::engine::Engine;
use crate::foundations::{
elem, Content, NativeElement, Packed, Resolve, Show, StyleChain,
};
+use crate::introspection::Locator;
use crate::layout::{
Abs, Alignment, Angle, Axes, BlockElem, FixedAlignment, Frame, HAlignment, Length,
Point, Ratio, Region, Regions, Rel, Size, VAlignment,
@@ -41,7 +42,9 @@ pub struct MoveElem {
impl Show for Packed<MoveElem> {
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(self.clone(), layout_move).pack())
+ Ok(BlockElem::single_layouter(self.clone(), layout_move)
+ .pack()
+ .spanned(self.span()))
}
}
@@ -50,12 +53,13 @@ impl Show for Packed<MoveElem> {
fn layout_move(
elem: &Packed<MoveElem>,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
region: Region,
) -> SourceResult<Frame> {
let mut frame = elem
.body()
- .layout(engine, styles, region.into_regions())?
+ .layout(engine, locator, styles, region.into_regions())?
.into_frame();
let delta = Axes::new(elem.dx(styles), elem.dy(styles)).resolve(styles);
let delta = delta.zip_map(region.size, Rel::relative_to);
@@ -126,7 +130,9 @@ pub struct RotateElem {
impl Show for Packed<RotateElem> {
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(self.clone(), layout_rotate).pack())
+ Ok(BlockElem::single_layouter(self.clone(), layout_rotate)
+ .pack()
+ .spanned(self.span()))
}
}
@@ -135,6 +141,7 @@ impl Show for Packed<RotateElem> {
fn layout_rotate(
elem: &Packed<RotateElem>,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
region: Region,
) -> SourceResult<Frame> {
@@ -151,6 +158,7 @@ fn layout_rotate(
measure_and_layout(
engine,
+ locator,
region,
size,
styles,
@@ -219,7 +227,9 @@ pub struct ScaleElem {
impl Show for Packed<ScaleElem> {
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(self.clone(), layout_scale).pack())
+ Ok(BlockElem::single_layouter(self.clone(), layout_scale)
+ .pack()
+ .spanned(self.span()))
}
}
@@ -228,6 +238,7 @@ impl Show for Packed<ScaleElem> {
fn layout_scale(
elem: &Packed<ScaleElem>,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
region: Region,
) -> SourceResult<Frame> {
@@ -240,6 +251,7 @@ fn layout_scale(
measure_and_layout(
engine,
+ locator,
region,
size,
styles,
@@ -379,6 +391,7 @@ impl Default for Transform {
#[allow(clippy::too_many_arguments)]
fn measure_and_layout(
engine: &mut Engine,
+ locator: Locator,
region: Region,
size: Size,
styles: StyleChain,
@@ -390,11 +403,11 @@ fn measure_and_layout(
if reflow {
// Measure the size of the body.
let pod = Regions::one(size, Axes::splat(false));
- let frame = body.measure(engine, styles, pod)?.into_frame();
+ let frame = body.layout(engine, locator.relayout(), styles, pod)?.into_frame();
// Actually perform the layout.
let pod = Regions::one(frame.size(), Axes::splat(true));
- let mut frame = body.layout(engine, styles, pod)?.into_frame();
+ let mut frame = body.layout(engine, locator, styles, pod)?.into_frame();
let Axes { x, y } = align.zip_map(frame.size(), FixedAlignment::position);
// Compute the transform.
@@ -410,7 +423,9 @@ fn measure_and_layout(
Ok(frame)
} else {
// Layout the body.
- let mut frame = body.layout(engine, styles, region.into_regions())?.into_frame();
+ let mut frame = body
+ .layout(engine, locator, styles, region.into_regions())?
+ .into_frame();
let Axes { x, y } = align.zip_map(frame.size(), FixedAlignment::position);
// Compute the transform.
diff --git a/crates/typst/src/lib.rs b/crates/typst/src/lib.rs
index 49f2c32b..6d445edd 100644
--- a/crates/typst/src/lib.rs
+++ b/crates/typst/src/lib.rs
@@ -69,7 +69,7 @@ use crate::eval::Tracer;
use crate::foundations::{
Array, Bytes, Content, Datetime, Dict, Module, Scope, StyleChain, Styles, Value,
};
-use crate::introspection::{Introspector, Locator};
+use crate::introspection::Introspector;
use crate::layout::{Alignment, Dir};
use crate::model::Document;
use crate::syntax::package::PackageSpec;
@@ -129,12 +129,10 @@ fn typeset(
tracer.delayed();
let constraint = <Introspector as Validate>::Constraint::new();
- let mut locator = Locator::new();
let mut engine = Engine {
world,
route: Route::default(),
tracer: tracer.track_mut(),
- locator: &mut locator,
introspector: document.introspector.track_with(&constraint),
};
diff --git a/crates/typst/src/math/ctx.rs b/crates/typst/src/math/ctx.rs
index b5ed6682..ac3fbca8 100644
--- a/crates/typst/src/math/ctx.rs
+++ b/crates/typst/src/math/ctx.rs
@@ -12,6 +12,7 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{Content, Packed, StyleChain};
+use crate::introspection::{Locator, SplitLocator};
use crate::layout::{Abs, Axes, BoxElem, Em, Frame, Regions, Size};
use crate::math::{
scaled_font_size, styled_char, EquationElem, FrameFragment, GlyphFragment,
@@ -49,6 +50,7 @@ macro_rules! percent {
pub struct MathContext<'a, 'b, 'v> {
// External.
pub engine: &'v mut Engine<'b>,
+ pub locator: SplitLocator<'v>,
pub regions: Regions<'static>,
// Font-related.
pub font: &'a Font,
@@ -65,6 +67,7 @@ pub struct MathContext<'a, 'b, 'v> {
impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
pub fn new(
engine: &'v mut Engine<'b>,
+ locator: Locator<'v>,
styles: StyleChain<'a>,
base: Size,
font: &'a Font,
@@ -103,6 +106,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
Self {
engine,
+ locator: locator.split(),
regions: Regions::one(base, Axes::splat(false)),
font,
ttf: font.ttf(),
@@ -174,7 +178,12 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
) -> SourceResult<Frame> {
let local =
TextElem::set_size(TextSize(scaled_font_size(self, styles).into())).wrap();
- boxed.layout(self.engine, styles.chain(&local), self.regions.base())
+ boxed.layout(
+ self.engine,
+ self.locator.next(&boxed.span()),
+ styles.chain(&local),
+ self.regions.base(),
+ )
}
/// Layout the given [`Content`] into a [`Frame`].
@@ -186,7 +195,12 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
let local =
TextElem::set_size(TextSize(scaled_font_size(self, styles).into())).wrap();
Ok(content
- .layout(self.engine, styles.chain(&local), self.regions)?
+ .layout(
+ self.engine,
+ self.locator.next(&content.span()),
+ styles.chain(&local),
+ self.regions,
+ )?
.into_frame())
}
@@ -290,7 +304,14 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
let par = ParElem::new(StyleVec::wrap(eco_vec![text]));
let frame = Packed::new(par)
.spanned(span)
- .layout(self.engine, styles, false, Size::splat(Abs::inf()), false)?
+ .layout(
+ self.engine,
+ self.locator.next(&span),
+ styles,
+ false,
+ Size::splat(Abs::inf()),
+ false,
+ )?
.into_frame();
Ok(FrameFragment::new(self, styles, frame)
diff --git a/crates/typst/src/math/equation.rs b/crates/typst/src/math/equation.rs
index d6a43d75..054b2823 100644
--- a/crates/typst/src/math/equation.rs
+++ b/crates/typst/src/math/equation.rs
@@ -8,7 +8,7 @@ use crate::foundations::{
elem, Content, NativeElement, Packed, Resolve, Show, ShowSet, Smart, StyleChain,
Styles, Synthesize,
};
-use crate::introspection::{Count, Counter, CounterUpdate, Locatable};
+use crate::introspection::{Count, Counter, CounterUpdate, Locatable, Locator};
use crate::layout::{
Abs, AlignElem, Alignment, Axes, BlockElem, Em, FixedAlignment, Fragment, Frame,
InlineElem, InlineItem, OuterHAlignment, Point, Regions, Size, SpecificAlignment,
@@ -166,9 +166,13 @@ impl Synthesize for Packed<EquationElem> {
impl Show for Packed<EquationElem> {
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
if self.block(styles) {
- Ok(BlockElem::multi_layouter(self.clone(), layout_equation_block).pack())
+ Ok(BlockElem::multi_layouter(self.clone(), layout_equation_block)
+ .pack()
+ .spanned(self.span()))
} else {
- Ok(InlineElem::layouter(self.clone(), layout_equation_inline).pack())
+ Ok(InlineElem::layouter(self.clone(), layout_equation_inline)
+ .pack()
+ .spanned(self.span()))
}
}
}
@@ -265,7 +269,8 @@ impl LayoutMath for Packed<EquationElem> {
#[typst_macros::time(span = elem.span())]
fn layout_equation_inline(
elem: &Packed<EquationElem>,
- engine: &mut Engine<'_>,
+ engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
region: Size,
) -> SourceResult<Vec<InlineItem>> {
@@ -273,7 +278,7 @@ fn layout_equation_inline(
let font = find_math_font(engine, styles, elem.span())?;
- let mut ctx = MathContext::new(engine, styles, region, &font);
+ let mut ctx = MathContext::new(engine, locator, styles, region, &font);
let run = ctx.layout_into_run(elem, styles)?;
let mut items = if run.row_count() == 1 {
@@ -311,6 +316,7 @@ fn layout_equation_inline(
fn layout_equation_block(
elem: &Packed<EquationElem>,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
@@ -319,7 +325,9 @@ fn layout_equation_block(
let span = elem.span();
let font = find_math_font(engine, styles, span)?;
- let mut ctx = MathContext::new(engine, styles, regions.base(), &font);
+ let mut locator = locator.split();
+ let mut ctx =
+ MathContext::new(engine, locator.next(&()), styles, regions.base(), &font);
let full_equation_builder = ctx
.layout_into_run(elem, styles)?
.multiline_frame_builder(&ctx, styles);
@@ -395,7 +403,7 @@ fn layout_equation_block(
let number = Counter::of(EquationElem::elem())
.display_at_loc(engine, elem.location().unwrap(), styles, numbering)?
.spanned(span)
- .layout(engine, styles, pod)?
+ .layout(engine, locator.next(&()), styles, pod)?
.into_frame();
static NUMBER_GUTTER: Em = Em::new(0.5);
diff --git a/crates/typst/src/math/mod.rs b/crates/typst/src/math/mod.rs
index 3b493b81..6ef3df9a 100644
--- a/crates/typst/src/math/mod.rs
+++ b/crates/typst/src/math/mod.rs
@@ -232,7 +232,7 @@ impl LayoutMath for Content {
return elem.layout_math(ctx, styles);
}
- if let Some(realized) = process(ctx.engine, self, styles)? {
+ if let Some(realized) = process(ctx.engine, &mut ctx.locator, self, styles)? {
return realized.layout_math(ctx, styles);
}
@@ -296,9 +296,9 @@ impl LayoutMath for Content {
return Ok(());
}
- if let Some(tag) = self.to_packed::<TagElem>() {
+ if let Some(elem) = self.to_packed::<TagElem>() {
let mut frame = Frame::soft(Size::zero());
- frame.push(Point::zero(), FrameItem::Tag(tag.elem.clone()));
+ frame.push(Point::zero(), FrameItem::Tag(elem.tag.clone()));
ctx.push(FrameFragment::new(ctx, styles, frame));
return Ok(());
}
diff --git a/crates/typst/src/model/cite.rs b/crates/typst/src/model/cite.rs
index 2d5de152..e156e0ec 100644
--- a/crates/typst/src/model/cite.rs
+++ b/crates/typst/src/model/cite.rs
@@ -1,4 +1,4 @@
-use crate::diag::{bail, At, SourceResult};
+use crate::diag::{error, At, HintedString, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, Cast, Content, Label, Packed, Show, Smart, StyleChain, Synthesize,
@@ -159,6 +159,17 @@ impl Show for Packed<CiteGroup> {
.citations
.get(&location)
.cloned()
- .unwrap_or_else(|| bail!(span, "failed to format citation (this is a bug)"))
+ .ok_or_else(failed_to_format_citation)
+ .at(span)?
}
}
+
+/// The error message when a citation wasn't found in the pre-formatted list.
+#[cold]
+fn failed_to_format_citation() -> HintedString {
+ error!(
+ "cannot format citation in isolation";
+ hint: "check whether this citation is measured \
+ without being inserted into the document"
+ )
+}
diff --git a/crates/typst/src/model/document.rs b/crates/typst/src/model/document.rs
index 7bc517ee..341c077c 100644
--- a/crates/typst/src/model/document.rs
+++ b/crates/typst/src/model/document.rs
@@ -6,7 +6,7 @@ use crate::foundations::{
cast, elem, Args, Array, Construct, Content, Datetime, Packed, Smart, StyleChain,
Value,
};
-use crate::introspection::{Introspector, ManualPageCounter};
+use crate::introspection::{Introspector, Locator, ManualPageCounter};
use crate::layout::{Page, PageElem};
use crate::realize::StyleVec;
@@ -76,6 +76,7 @@ impl Packed<DocumentElem> {
pub fn layout(
&self,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
) -> SourceResult<Document> {
let mut pages = Vec::with_capacity(self.children().len());
@@ -83,13 +84,20 @@ impl Packed<DocumentElem> {
let children = self.children();
let mut iter = children.chain(&styles).peekable();
+ let mut locator = locator.split();
while let Some((child, styles)) = iter.next() {
if let Some(page) = child.to_packed::<PageElem>() {
let extend_to = iter
.peek()
.and_then(|(next, _)| *next.to_packed::<PageElem>()?.clear_to()?);
- let run = page.layout(engine, styles, &mut page_counter, extend_to)?;
+ let run = page.layout(
+ engine,
+ locator.next(&page.span()),
+ styles,
+ &mut page_counter,
+ extend_to,
+ )?;
pages.extend(run);
} else {
bail!(child.span(), "unexpected document child");
diff --git a/crates/typst/src/model/enum.rs b/crates/typst/src/model/enum.rs
index d03c774c..9d4cdf42 100644
--- a/crates/typst/src/model/enum.rs
+++ b/crates/typst/src/model/enum.rs
@@ -9,6 +9,7 @@ use crate::foundations::{
cast, elem, scope, Array, Content, Context, NativeElement, Packed, Show, Smart,
StyleChain, Styles,
};
+use crate::introspection::Locator;
use crate::layout::{
Alignment, Axes, BlockElem, Cell, CellGrid, Em, Fragment, GridLayouter, HAlignment,
Length, Regions, Sizing, Spacing, VAlignment, VElem,
@@ -215,7 +216,9 @@ impl EnumElem {
impl Show for Packed<EnumElem> {
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
- let mut realized = BlockElem::multi_layouter(self.clone(), layout_enum).pack();
+ let mut realized = BlockElem::multi_layouter(self.clone(), layout_enum)
+ .pack()
+ .spanned(self.span());
if self.tight(styles) {
let leading = ParElem::leading_in(styles);
@@ -232,6 +235,7 @@ impl Show for Packed<EnumElem> {
fn layout_enum(
elem: &Packed<EnumElem>,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
@@ -246,6 +250,7 @@ fn layout_enum(
};
let mut cells = vec![];
+ let mut locator = locator.split();
let mut number = elem.start(styles);
let mut parents = EnumElem::parents_in(styles);
@@ -280,11 +285,12 @@ fn layout_enum(
let resolved =
resolved.aligned(number_align).styled(TextElem::set_overhang(false));
- cells.push(Cell::from(Content::empty()));
- cells.push(Cell::from(resolved));
- cells.push(Cell::from(Content::empty()));
- cells.push(Cell::from(
- item.body().clone().styled(EnumElem::set_parents(smallvec![number])),
+ cells.push(Cell::new(Content::empty(), locator.next(&())));
+ cells.push(Cell::new(resolved, locator.next(&())));
+ cells.push(Cell::new(Content::empty(), locator.next(&())));
+ cells.push(Cell::new(
+ item.body.clone().styled(EnumElem::set_parents(smallvec![number])),
+ locator.next(&item.body.span()),
));
number = number.saturating_add(1);
}
diff --git a/crates/typst/src/model/heading.rs b/crates/typst/src/model/heading.rs
index 478b4315..e160eeea 100644
--- a/crates/typst/src/model/heading.rs
+++ b/crates/typst/src/model/heading.rs
@@ -6,7 +6,9 @@ use crate::foundations::{
elem, Content, NativeElement, Packed, Resolve, Show, ShowSet, Smart, StyleChain,
Styles, Synthesize,
};
-use crate::introspection::{Count, Counter, CounterUpdate, Locatable};
+use crate::introspection::{
+ Count, Counter, CounterUpdate, Locatable, Locator, LocatorLink,
+};
use crate::layout::{
Abs, Axes, BlockChild, BlockElem, Em, HElem, Length, Regions, VElem,
};
@@ -221,20 +223,27 @@ impl Show for Packed<HeadingElem> {
let mut realized = self.body().clone();
let hanging_indent = self.hanging_indent(styles);
-
let mut indent = match hanging_indent {
Smart::Custom(length) => length.resolve(styles),
Smart::Auto => Abs::zero(),
};
if let Some(numbering) = (**self).numbering(styles).as_ref() {
+ let location = self.location().unwrap();
let numbering = Counter::of(HeadingElem::elem())
- .display_at_loc(engine, self.location().unwrap(), styles, numbering)?
+ .display_at_loc(engine, location, styles, numbering)?
.spanned(span);
if hanging_indent.is_auto() {
let pod = Regions::one(Axes::splat(Abs::inf()), Axes::splat(false));
- let size = numbering.measure(engine, styles, pod)?.into_frame().size();
+
+ // We don't have a locator for the numbering here, so we just
+ // use the measurement infrastructure for now.
+ let link = LocatorLink::measure(location);
+ let size = numbering
+ .layout(engine, Locator::link(&link), styles, pod)?
+ .into_frame()
+ .size();
indent = size.x + SPACING_TO_NUMBERING.resolve(styles);
}
diff --git a/crates/typst/src/model/list.rs b/crates/typst/src/model/list.rs
index ffecc400..d8e58052 100644
--- a/crates/typst/src/model/list.rs
+++ b/crates/typst/src/model/list.rs
@@ -6,6 +6,7 @@ use crate::foundations::{
cast, elem, scope, Array, Content, Context, Depth, Func, NativeElement, Packed, Show,
Smart, StyleChain, Styles, Value,
};
+use crate::introspection::Locator;
use crate::layout::{
Axes, BlockElem, Cell, CellGrid, Em, Fragment, GridLayouter, HAlignment, Length,
Regions, Sizing, Spacing, VAlignment, VElem,
@@ -139,7 +140,9 @@ impl ListElem {
impl Show for Packed<ListElem> {
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
- let mut realized = BlockElem::multi_layouter(self.clone(), layout_list).pack();
+ let mut realized = BlockElem::multi_layouter(self.clone(), layout_list)
+ .pack()
+ .spanned(self.span());
if self.tight(styles) {
let leading = ParElem::leading_in(styles);
@@ -156,6 +159,7 @@ impl Show for Packed<ListElem> {
fn layout_list(
elem: &Packed<ListElem>,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
@@ -176,11 +180,16 @@ fn layout_list(
.aligned(HAlignment::Start + VAlignment::Top);
let mut cells = vec![];
+ let mut locator = locator.split();
+
for item in elem.children() {
- cells.push(Cell::from(Content::empty()));
- cells.push(Cell::from(marker.clone()));
- cells.push(Cell::from(Content::empty()));
- cells.push(Cell::from(item.body().clone().styled(ListElem::set_depth(Depth(1)))));
+ cells.push(Cell::new(Content::empty(), locator.next(&())));
+ cells.push(Cell::new(marker.clone(), locator.next(&marker.span())));
+ cells.push(Cell::new(Content::empty(), locator.next(&())));
+ cells.push(Cell::new(
+ item.body.clone().styled(ListElem::set_depth(Depth(1))),
+ locator.next(&item.body.span()),
+ ));
}
let grid = CellGrid::new(
diff --git a/crates/typst/src/model/par.rs b/crates/typst/src/model/par.rs
index 7615ddba..8d5178b1 100644
--- a/crates/typst/src/model/par.rs
+++ b/crates/typst/src/model/par.rs
@@ -6,6 +6,7 @@ use crate::foundations::{
elem, Args, Cast, Construct, Content, NativeElement, Packed, Set, Smart, StyleChain,
Unlabellable,
};
+use crate::introspection::Locator;
use crate::layout::{Em, Fragment, Length, Size};
use crate::realize::StyleVec;
@@ -138,6 +139,7 @@ impl Packed<ParElem> {
pub fn layout(
&self,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
consecutive: bool,
region: Size,
@@ -146,6 +148,7 @@ impl Packed<ParElem> {
crate::layout::layout_inline(
&self.children,
engine,
+ locator,
styles,
consecutive,
region,
diff --git a/crates/typst/src/model/table.rs b/crates/typst/src/model/table.rs
index 0c56b7e4..da7b6082 100644
--- a/crates/typst/src/model/table.rs
+++ b/crates/typst/src/model/table.rs
@@ -8,6 +8,7 @@ use crate::engine::Engine;
use crate::foundations::{
cast, elem, scope, Content, Fold, NativeElement, Packed, Show, Smart, StyleChain,
};
+use crate::introspection::Locator;
use crate::layout::{
show_grid_cell, Abs, Alignment, Axes, BlockElem, Cell, CellGrid, Celled, Dir,
Fragment, GridCell, GridFooter, GridHLine, GridHeader, GridLayouter, GridVLine,
@@ -262,7 +263,9 @@ impl TableElem {
impl Show for Packed<TableElem> {
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::multi_layouter(self.clone(), layout_table).pack())
+ Ok(BlockElem::multi_layouter(self.clone(), layout_table)
+ .pack()
+ .spanned(self.span()))
}
}
@@ -271,6 +274,7 @@ impl Show for Packed<TableElem> {
fn layout_table(
elem: &Packed<TableElem>,
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
@@ -304,6 +308,7 @@ fn layout_table(
let grid = CellGrid::resolve(
tracks,
gutter,
+ locator,
children,
fill,
align,
@@ -800,7 +805,7 @@ impl Default for Packed<TableCell> {
}
impl ResolvableCell for Packed<TableCell> {
- fn resolve_cell(
+ fn resolve_cell<'a>(
mut self,
x: usize,
y: usize,
@@ -809,8 +814,9 @@ impl ResolvableCell for Packed<TableCell> {
inset: Sides<Option<Rel<Length>>>,
stroke: Sides<Option<Option<Arc<Stroke<Abs>>>>>,
breakable: bool,
+ locator: Locator<'a>,
styles: StyleChain,
- ) -> Cell {
+ ) -> Cell<'a> {
let cell = &mut *self;
let colspan = cell.colspan(styles);
let rowspan = cell.rowspan(styles);
@@ -862,6 +868,7 @@ impl ResolvableCell for Packed<TableCell> {
cell.push_breakable(Smart::Custom(breakable));
Cell {
body: self.pack(),
+ locator,
fill,
colspan,
rowspan,
diff --git a/crates/typst/src/realize/mod.rs b/crates/typst/src/realize/mod.rs
index a21f8faf..40e9a9b0 100644
--- a/crates/typst/src/realize/mod.rs
+++ b/crates/typst/src/realize/mod.rs
@@ -21,7 +21,7 @@ use crate::engine::{Engine, Route};
use crate::foundations::{
Content, NativeElement, Packed, SequenceElem, StyleChain, StyledElem, Styles,
};
-use crate::introspection::TagElem;
+use crate::introspection::{Locator, SplitLocator, TagElem};
use crate::layout::{
AlignElem, BlockElem, BoxElem, ColbreakElem, FlowElem, FlushElem, HElem, InlineElem,
PageElem, PagebreakElem, Parity, PlaceElem, VElem,
@@ -39,11 +39,12 @@ use crate::text::{LinebreakElem, SmartQuoteElem, SpaceElem, TextElem};
#[typst_macros::time(name = "realize doc")]
pub fn realize_doc<'a>(
engine: &mut Engine,
+ locator: Locator,
arenas: &'a Arenas<'a>,
content: &'a Content,
styles: StyleChain<'a>,
) -> SourceResult<(Packed<DocumentElem>, StyleChain<'a>)> {
- let mut builder = Builder::new(engine, arenas, true);
+ let mut builder = Builder::new(engine, locator, arenas, true);
builder.accept(content, styles)?;
builder.interrupt_page(Some(styles), true)?;
Ok(builder.doc.unwrap().finish())
@@ -53,11 +54,12 @@ pub fn realize_doc<'a>(
#[typst_macros::time(name = "realize flow")]
pub fn realize_flow<'a>(
engine: &mut Engine,
+ locator: Locator,
arenas: &'a Arenas<'a>,
content: &'a Content,
styles: StyleChain<'a>,
) -> SourceResult<(Packed<FlowElem>, StyleChain<'a>)> {
- let mut builder = Builder::new(engine, arenas, false);
+ let mut builder = Builder::new(engine, locator, arenas, false);
builder.accept(content, styles)?;
builder.interrupt_par()?;
Ok(builder.flow.finish())
@@ -67,6 +69,8 @@ pub fn realize_flow<'a>(
struct Builder<'a, 'v, 't> {
/// The engine.
engine: &'v mut Engine<'t>,
+ /// Assigns unique locations to elements.
+ locator: SplitLocator<'v>,
/// Scratch arenas for building.
arenas: &'a Arenas<'a>,
/// The current document building state.
@@ -82,9 +86,15 @@ struct Builder<'a, 'v, 't> {
}
impl<'a, 'v, 't> Builder<'a, 'v, 't> {
- fn new(engine: &'v mut Engine<'t>, arenas: &'a Arenas<'a>, top: bool) -> Self {
+ fn new(
+ engine: &'v mut Engine<'t>,
+ locator: Locator<'v>,
+ arenas: &'a Arenas<'a>,
+ top: bool,
+ ) -> Self {
Self {
engine,
+ locator: locator.split(),
arenas,
doc: top.then(DocBuilder::default),
flow: FlowBuilder::default(),
@@ -107,7 +117,8 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
.store(EquationElem::new(content.clone()).pack().spanned(content.span()));
}
- if let Some(realized) = process(self.engine, content, styles)? {
+ if let Some(realized) = process(self.engine, &mut self.locator, content, styles)?
+ {
self.engine.route.increase();
if !self.engine.route.within(Route::MAX_SHOW_RULE_DEPTH) {
bail!(
diff --git a/crates/typst/src/realize/process.rs b/crates/typst/src/realize/process.rs
index 45d8dc8c..a0ba8fdb 100644
--- a/crates/typst/src/realize/process.rs
+++ b/crates/typst/src/realize/process.rs
@@ -8,9 +8,9 @@ use crate::foundations::{
Content, Context, Packed, Recipe, RecipeIndex, Regex, Selector, Show, ShowSet, Style,
StyleChain, Styles, Synthesize, Transformation,
};
-use crate::introspection::{Locatable, TagElem};
+use crate::introspection::{Locatable, SplitLocator, Tag, TagElem};
use crate::text::TextElem;
-use crate::utils::{hash128, SmallBitSet};
+use crate::utils::SmallBitSet;
/// What to do with an element when encountering it during realization.
struct Verdict<'a> {
@@ -34,6 +34,7 @@ enum ShowStep<'a> {
/// Processes the given `target` element when encountering it during realization.
pub fn process(
engine: &mut Engine,
+ locator: &mut SplitLocator,
target: &Content,
styles: StyleChain,
) -> SourceResult<Option<Content>> {
@@ -49,7 +50,7 @@ pub fn process(
// prepare it.
let mut tag = None;
if !prepared {
- tag = prepare(engine, &mut target, &mut map, styles)?;
+ tag = prepare(engine, locator, &mut target, &mut map, styles)?;
}
// Apply a step, if there is one.
@@ -181,6 +182,7 @@ fn verdict<'a>(
/// This is only executed the first time an element is visited.
fn prepare(
engine: &mut Engine,
+ locator: &mut SplitLocator,
target: &mut Content,
map: &mut Styles,
styles: StyleChain,
@@ -191,11 +193,14 @@ fn prepare(
//
// The element could already have a location even if it is not prepared
// when it stems from a query.
- let mut located = target.location().is_some();
- if !located && (target.can::<dyn Locatable>() || target.label().is_some()) {
- let location = engine.locator.locate(hash128(&target));
+ let mut key = None;
+ if target.location().is_some() {
+ key = Some(crate::utils::hash128(&target));
+ } else if target.can::<dyn Locatable>() || target.label().is_some() {
+ let hash = crate::utils::hash128(&target);
+ let location = locator.next_location(engine.introspector, hash);
target.set_location(location);
- located = true;
+ key = Some(hash);
}
// Apply built-in show-set rules. User-defined show-set rules are already
@@ -220,7 +225,7 @@ fn prepare(
// materialization, so that it includes the synthesized fields. Do it before
// marking as prepared so that show-set rules will apply to this element
// when queried.
- let tag = located.then(|| TagElem::packed(target.clone()));
+ let tag = key.map(|key| TagElem::packed(Tag::new(target.clone(), key)));
// Ensure that this preparation only runs once by marking the element as
// prepared.
diff --git a/crates/typst/src/visualize/image/mod.rs b/crates/typst/src/visualize/image/mod.rs
index 91922d1b..e5916f1d 100644
--- a/crates/typst/src/visualize/image/mod.rs
+++ b/crates/typst/src/visualize/image/mod.rs
@@ -19,6 +19,7 @@ use crate::foundations::{
cast, elem, func, scope, Bytes, Cast, Content, NativeElement, Packed, Show, Smart,
StyleChain,
};
+use crate::introspection::Locator;
use crate::layout::{
Abs, Axes, BlockElem, FixedAlignment, Frame, FrameItem, Length, Point, Region, Rel,
Size,
@@ -159,7 +160,8 @@ impl Show for Packed<ImageElem> {
Ok(BlockElem::single_layouter(self.clone(), layout_image)
.with_width(self.width(styles))
.with_height(self.height(styles))
- .pack())
+ .pack()
+ .spanned(self.span()))
}
}
@@ -174,6 +176,7 @@ impl Figurable for Packed<ImageElem> {}
fn layout_image(
elem: &Packed<ImageElem>,
engine: &mut Engine,
+ _: Locator,
styles: StyleChain,
region: Region,
) -> SourceResult<Frame> {
diff --git a/crates/typst/src/visualize/line.rs b/crates/typst/src/visualize/line.rs
index 0d5cb4b7..f25fc58d 100644
--- a/crates/typst/src/visualize/line.rs
+++ b/crates/typst/src/visualize/line.rs
@@ -1,6 +1,7 @@
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{elem, Content, NativeElement, Packed, Show, StyleChain};
+use crate::introspection::Locator;
use crate::layout::{
Abs, Angle, Axes, BlockElem, Frame, FrameItem, Length, Region, Rel, Size,
};
@@ -60,7 +61,9 @@ pub struct LineElem {
impl Show for Packed<LineElem> {
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(self.clone(), layout_line).pack())
+ Ok(BlockElem::single_layouter(self.clone(), layout_line)
+ .pack()
+ .spanned(self.span()))
}
}
@@ -69,6 +72,7 @@ impl Show for Packed<LineElem> {
fn layout_line(
elem: &Packed<LineElem>,
_: &mut Engine,
+ _: Locator,
styles: StyleChain,
region: Region,
) -> SourceResult<Frame> {
diff --git a/crates/typst/src/visualize/path.rs b/crates/typst/src/visualize/path.rs
index 0005618e..df911426 100644
--- a/crates/typst/src/visualize/path.rs
+++ b/crates/typst/src/visualize/path.rs
@@ -6,6 +6,7 @@ use crate::foundations::{
array, cast, elem, Array, Content, NativeElement, Packed, Reflect, Resolve, Show,
Smart, StyleChain,
};
+use crate::introspection::Locator;
use crate::layout::{
Abs, Axes, BlockElem, Frame, FrameItem, Length, Point, Region, Rel, Size,
};
@@ -72,7 +73,9 @@ pub struct PathElem {
impl Show for Packed<PathElem> {
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(self.clone(), layout_path).pack())
+ Ok(BlockElem::single_layouter(self.clone(), layout_path)
+ .pack()
+ .spanned(self.span()))
}
}
@@ -81,6 +84,7 @@ impl Show for Packed<PathElem> {
fn layout_path(
elem: &Packed<PathElem>,
_: &mut Engine,
+ _: Locator,
styles: StyleChain,
region: Region,
) -> SourceResult<Frame> {
diff --git a/crates/typst/src/visualize/pattern.rs b/crates/typst/src/visualize/pattern.rs
index e467d789..804c87df 100644
--- a/crates/typst/src/visualize/pattern.rs
+++ b/crates/typst/src/visualize/pattern.rs
@@ -6,6 +6,7 @@ use ecow::{eco_format, EcoString};
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{func, repr, scope, ty, Content, Smart, StyleChain};
+use crate::introspection::Locator;
use crate::layout::{Abs, Axes, Frame, Length, Regions, Size};
use crate::syntax::{Span, Spanned};
use crate::utils::{LazyHash, Numeric};
@@ -189,9 +190,10 @@ impl Pattern {
// Layout the pattern.
let world = engine.world;
let library = world.library();
+ let locator = Locator::root();
let styles = StyleChain::new(&library.styles);
let pod = Regions::one(region, Axes::splat(false));
- let mut frame = body.layout(engine, styles, pod)?.into_frame();
+ let mut frame = body.layout(engine, locator, styles, pod)?.into_frame();
// Set the size of the frame if the size is enforced.
if let Smart::Custom(size) = size {
diff --git a/crates/typst/src/visualize/polygon.rs b/crates/typst/src/visualize/polygon.rs
index 305f3cb1..120f41fc 100644
--- a/crates/typst/src/visualize/polygon.rs
+++ b/crates/typst/src/visualize/polygon.rs
@@ -5,6 +5,7 @@ use crate::engine::Engine;
use crate::foundations::{
elem, func, scope, Content, NativeElement, Packed, Resolve, Show, Smart, StyleChain,
};
+use crate::introspection::Locator;
use crate::layout::{Axes, BlockElem, Em, Frame, FrameItem, Length, Point, Region, Rel};
use crate::syntax::Span;
use crate::utils::Numeric;
@@ -125,7 +126,9 @@ impl PolygonElem {
impl Show for Packed<PolygonElem> {
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(self.clone(), layout_polygon).pack())
+ Ok(BlockElem::single_layouter(self.clone(), layout_polygon)
+ .pack()
+ .spanned(self.span()))
}
}
@@ -134,6 +137,7 @@ impl Show for Packed<PolygonElem> {
fn layout_polygon(
elem: &Packed<PolygonElem>,
_: &mut Engine,
+ _: Locator,
styles: StyleChain,
region: Region,
) -> SourceResult<Frame> {
diff --git a/crates/typst/src/visualize/shape.rs b/crates/typst/src/visualize/shape.rs
index 7404763e..8564e1dd 100644
--- a/crates/typst/src/visualize/shape.rs
+++ b/crates/typst/src/visualize/shape.rs
@@ -3,6 +3,7 @@ use std::f64::consts::SQRT_2;
use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{elem, Content, NativeElement, Packed, Show, Smart, StyleChain};
+use crate::introspection::Locator;
use crate::layout::{
Abs, Axes, BlockElem, Corner, Corners, Frame, FrameItem, Length, Point, Ratio,
Region, Regions, Rel, Sides, Size,
@@ -134,24 +135,29 @@ pub struct RectElem {
impl Show for Packed<RectElem> {
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(self.clone(), |elem, engine, styles, region| {
- layout_shape(
- engine,
- styles,
- region,
- ShapeKind::Rect,
- elem.body(styles),
- elem.fill(styles),
- elem.stroke(styles),
- elem.inset(styles),
- elem.outset(styles),
- elem.radius(styles),
- elem.span(),
- )
- })
+ Ok(BlockElem::single_layouter(
+ self.clone(),
+ |elem, engine, locator, styles, region| {
+ layout_shape(
+ engine,
+ locator,
+ styles,
+ region,
+ ShapeKind::Rect,
+ elem.body(styles),
+ elem.fill(styles),
+ elem.stroke(styles),
+ elem.inset(styles),
+ elem.outset(styles),
+ elem.radius(styles),
+ elem.span(),
+ )
+ },
+ )
.with_width(self.width(styles))
.with_height(self.height(styles))
- .pack())
+ .pack()
+ .spanned(self.span()))
}
}
@@ -239,24 +245,29 @@ pub struct SquareElem {
impl Show for Packed<SquareElem> {
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(self.clone(), |elem, engine, styles, regions| {
- layout_shape(
- engine,
- styles,
- regions,
- ShapeKind::Square,
- elem.body(styles),
- elem.fill(styles),
- elem.stroke(styles),
- elem.inset(styles),
- elem.outset(styles),
- elem.radius(styles),
- elem.span(),
- )
- })
+ Ok(BlockElem::single_layouter(
+ self.clone(),
+ |elem, engine, locator, styles, regions| {
+ layout_shape(
+ engine,
+ locator,
+ styles,
+ regions,
+ ShapeKind::Square,
+ elem.body(styles),
+ elem.fill(styles),
+ elem.stroke(styles),
+ elem.inset(styles),
+ elem.outset(styles),
+ elem.radius(styles),
+ elem.span(),
+ )
+ },
+ )
.with_width(self.width(styles))
.with_height(self.height(styles))
- .pack())
+ .pack()
+ .spanned(self.span()))
}
}
@@ -316,24 +327,29 @@ pub struct EllipseElem {
impl Show for Packed<EllipseElem> {
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(self.clone(), |elem, engine, styles, regions| {
- layout_shape(
- engine,
- styles,
- regions,
- ShapeKind::Ellipse,
- elem.body(styles),
- elem.fill(styles),
- elem.stroke(styles).map(|s| Sides::splat(Some(s))),
- elem.inset(styles),
- elem.outset(styles),
- Corners::splat(None),
- elem.span(),
- )
- })
+ Ok(BlockElem::single_layouter(
+ self.clone(),
+ |elem, engine, locator, styles, regions| {
+ layout_shape(
+ engine,
+ locator,
+ styles,
+ regions,
+ ShapeKind::Ellipse,
+ elem.body(styles),
+ elem.fill(styles),
+ elem.stroke(styles).map(|s| Sides::splat(Some(s))),
+ elem.inset(styles),
+ elem.outset(styles),
+ Corners::splat(None),
+ elem.span(),
+ )
+ },
+ )
.with_width(self.width(styles))
.with_height(self.height(styles))
- .pack())
+ .pack()
+ .spanned(self.span()))
}
}
@@ -418,24 +434,29 @@ pub struct CircleElem {
impl Show for Packed<CircleElem> {
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
- Ok(BlockElem::single_layouter(self.clone(), |elem, engine, styles, regions| {
- layout_shape(
- engine,
- styles,
- regions,
- ShapeKind::Circle,
- elem.body(styles),
- elem.fill(styles),
- elem.stroke(styles).map(|s| Sides::splat(Some(s))),
- elem.inset(styles),
- elem.outset(styles),
- Corners::splat(None),
- elem.span(),
- )
- })
+ Ok(BlockElem::single_layouter(
+ self.clone(),
+ |elem, engine, locator, styles, regions| {
+ layout_shape(
+ engine,
+ locator,
+ styles,
+ regions,
+ ShapeKind::Circle,
+ elem.body(styles),
+ elem.fill(styles),
+ elem.stroke(styles).map(|s| Sides::splat(Some(s))),
+ elem.inset(styles),
+ elem.outset(styles),
+ Corners::splat(None),
+ elem.span(),
+ )
+ },
+ )
.with_width(self.width(styles))
.with_height(self.height(styles))
- .pack())
+ .pack()
+ .spanned(self.span()))
}
}
@@ -444,6 +465,7 @@ impl Show for Packed<CircleElem> {
#[allow(clippy::too_many_arguments)]
fn layout_shape(
engine: &mut Engine,
+ locator: Locator,
styles: StyleChain,
region: Region,
kind: ShapeKind,
@@ -471,14 +493,16 @@ fn layout_shape(
}
// Layout the child.
- frame = child.layout(engine, styles, pod.into_regions())?.into_frame();
+ frame = child
+ .layout(engine, locator.relayout(), styles, pod.into_regions())?
+ .into_frame();
// If the child is a square or circle, relayout with full expansion into
// square region to make sure the result is really quadratic.
if kind.is_quadratic() {
let length = frame.size().max_by_side().min(pod.size.min_by_side());
let quad_pod = Regions::one(Size::splat(length), Axes::splat(true));
- frame = child.layout(engine, styles, quad_pod)?.into_frame();
+ frame = child.layout(engine, locator, styles, quad_pod)?.into_frame();
}
// Apply the inset.
diff --git a/tests/ref/issue-2480-counter-reset-2.png b/tests/ref/issue-2480-counter-reset-2.png
new file mode 100644
index 00000000..26b8502c
--- /dev/null
+++ b/tests/ref/issue-2480-counter-reset-2.png
Binary files differ
diff --git a/tests/ref/issue-2480-counter-reset.png b/tests/ref/issue-2480-counter-reset.png
new file mode 100644
index 00000000..5dd52b52
--- /dev/null
+++ b/tests/ref/issue-2480-counter-reset.png
Binary files differ
diff --git a/tests/ref/measure-citation-deeply-nested.png b/tests/ref/measure-citation-deeply-nested.png
new file mode 100644
index 00000000..4027fd76
--- /dev/null
+++ b/tests/ref/measure-citation-deeply-nested.png
Binary files differ
diff --git a/tests/ref/measure-citation-in-flow.png b/tests/ref/measure-citation-in-flow.png
new file mode 100644
index 00000000..14834e76
--- /dev/null
+++ b/tests/ref/measure-citation-in-flow.png
Binary files differ
diff --git a/tests/ref/measure-counter-multiple-times.png b/tests/ref/measure-counter-multiple-times.png
new file mode 100644
index 00000000..f1d1bc06
--- /dev/null
+++ b/tests/ref/measure-counter-multiple-times.png
Binary files differ
diff --git a/tests/ref/measure-counter-width.png b/tests/ref/measure-counter-width.png
new file mode 100644
index 00000000..3a92f816
--- /dev/null
+++ b/tests/ref/measure-counter-width.png
Binary files differ
diff --git a/tests/ref/table-contextual-measurement.png b/tests/ref/table-contextual-measurement.png
new file mode 100644
index 00000000..1a97cbbd
--- /dev/null
+++ b/tests/ref/table-contextual-measurement.png
Binary files differ
diff --git a/tests/ref/table-header-citation.png b/tests/ref/table-header-citation.png
new file mode 100644
index 00000000..0495d5af
--- /dev/null
+++ b/tests/ref/table-header-citation.png
Binary files differ
diff --git a/tests/ref/table-header-counter.png b/tests/ref/table-header-counter.png
new file mode 100644
index 00000000..04a8e92d
--- /dev/null
+++ b/tests/ref/table-header-counter.png
Binary files differ
diff --git a/tests/ref/table-header-footer-madness.png b/tests/ref/table-header-footer-madness.png
new file mode 100644
index 00000000..4e4f771e
--- /dev/null
+++ b/tests/ref/table-header-footer-madness.png
Binary files differ
diff --git a/tests/suite/introspection/counter.typ b/tests/suite/introspection/counter.typ
index 8a5315f9..c0b17921 100644
--- a/tests/suite/introspection/counter.typ
+++ b/tests/suite/introspection/counter.typ
@@ -76,3 +76,31 @@ At Beta, it was #context {
// Hint: 2-28 try wrapping this in a `context` expression
// Hint: 2-28 the `context` expression should wrap everything that depends on this function
#counter("key").at(<label>)
+
+--- issue-2480-counter-reset ---
+#let q = counter("question")
+#let step-show = q.step() + q.display("1")
+#let g = grid(step-show, step-show, gutter: 2pt)
+
+#g
+#pagebreak()
+#step-show
+#q.update(10)
+#g
+
+--- issue-2480-counter-reset-2 ---
+#set block(spacing: 3pt)
+#let c = counter("c")
+#let foo() = context {
+ c.step()
+ c.display("1")
+ str(c.get().first())
+}
+
+#foo()
+#block(foo())
+#foo()
+#foo()
+#block(foo())
+#block(foo())
+#foo()
diff --git a/tests/suite/layout/measure.typ b/tests/suite/layout/measure.typ
index 10c3c818..71a6b32a 100644
--- a/tests/suite/layout/measure.typ
+++ b/tests/suite/layout/measure.typ
@@ -20,3 +20,75 @@
assert(d2.width < 400pt)
assert(d2.height > 50pt)
}
+
+--- measure-counter-width ---
+// Measure a counter. Tests that the introspector-assisted location assignment
+// is able to take `here()` from the context into account to find the closest
+// matching element instaed of any single one. Crucially, we need to reuse
+// the same `context c.display()` to get the same span, hence `it`.
+#let f(it) = context [
+ Is #measure(it).width wide: #it \
+]
+
+#let c = counter("c")
+#let it = context c.display()
+
+#c.update(10000)
+#f(it)
+#c.update(100)
+#f(it)
+#c.update(1)
+#f(it)
+
+--- measure-citation-in-flow ---
+// Try measuring a citation that appears inline with other stuff. The
+// introspection-assisted location assignment will ensure that the citation
+// in the measurement is matched up with the real one.
+#context {
+ let it = [@netwok]
+ let size = measure(it)
+ place(line(length: size.width))
+ v(1mm)
+ it + [ is cited]
+}
+
+#show bibliography: none
+#bibliography("/assets/bib/works.bib")
+
+--- measure-citation-in-flow-different-span ---
+// When the citation has a different span, it stops working.
+#context {
+ // Error: 22-29 cannot format citation in isolation
+ // Hint: 22-29 check whether this citation is measured without being inserted into the document
+ let size = measure[@netwok]
+ place(line(length: size.width))
+ v(1mm)
+ [@netwok is cited]
+}
+
+#show bibliography: none
+#bibliography("/assets/bib/works.bib")
+
+--- measure-citation-deeply-nested ---
+// Nested the citation deeply to test that introspector-assisted measurement
+// is able to deal with memoization boundaries.
+#context {
+ let it = box(pad(x: 5pt, grid(stack[@netwok])))
+ [#measure(it).width]
+ it
+}
+
+#show bibliography: none
+#bibliography("/assets/bib/works.bib")
+
+--- measure-counter-multiple-times ---
+// When the thing we measure appears multiple times, we measure as if it was
+// the first one.
+#context {
+ let c = counter("c")
+ let u(n) = c.update(n)
+ let it = context c.get().first() * h(1pt)
+ let size = measure(it)
+ table(columns: 5, u(17), it, u(1), it, u(5))
+ [#size.width] // 17pt
+}
diff --git a/tests/suite/layout/table.typ b/tests/suite/layout/table.typ
index 4090ef95..fe138f60 100644
--- a/tests/suite/layout/table.typ
+++ b/tests/suite/layout/table.typ
@@ -139,6 +139,55 @@
[G], [H]
)
+--- table-contextual-measurement ---
+// Test that table cells with varying contextual results are properly
+// measured.
+#let c = counter("c")
+#let k = context square(width: c.get().first() * 5pt)
+#let u(n) = [#n] + c.update(n)
+#table(
+ columns: 3,
+ u(1), k, u(2),
+ k, u(4), k,
+ k, k, k,
+)
+
+--- table-header-citation ---
+#set page(height: 60pt)
+#table(
+ table.header[@netwok],
+ [A],
+ [A],
+)
+
+#show bibliography: none
+#bibliography("/assets/bib/works.bib")
+
+--- table-header-counter ---
+#set page(height: 60pt)
+#let c = counter("c")
+#table(
+ table.header(c.step() + context c.display()),
+ [A],
+ [A],
+)
+
+--- table-header-footer-madness ---
+#set page(height: 100pt)
+#let c = counter("c")
+#let it = context c.get().first() * v(10pt)
+#table(
+ table.header(c.step()),
+ [A],
+ [A],
+ [A],
+ [A],
+ [A],
+ [A],
+ [A],
+ table.footer(it),
+)
+
--- table-cell-override ---
// Cell override
#table(