Pure location assignment (#4352)

This commit is contained in:
Laurenz 2024-06-09 15:23:56 +02:00 committed by GitHub
parent cc3e9c8602
commit f91cad7d78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
67 changed files with 1287 additions and 524 deletions

View File

@ -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(),
};

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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]
})
}

View File

@ -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)),
}
}

View File

@ -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,
};

View File

@ -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(),
};

View File

@ -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.

View File

@ -298,7 +298,6 @@ impl Func {
engine.world,
engine.introspector,
engine.route.track(),
engine.locator.track(),
TrackedMut::reborrow_mut(&mut engine.tracer),
context,
args,

View File

@ -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 {

View File

@ -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))
}
}

View File

@ -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()

View File

@ -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]
/// A [`Location`] is a unique ID for an element generated during realization.
///
/// 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).
/// # 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:
///
/// [^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)]
/// - 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 chained locator.
pub fn chained(outer: Tracked<'a, Self>) -> Self {
Self { outer: Some(outer), ..Default::default() }
}
/// Start tracking this locator.
/// Create a new root-level locator.
///
/// In comparison to [`Track::track`], this method skips this chain link
/// if it does not contribute anything.
pub fn track(&self) -> Tracked<'_, Self> {
match self.outer {
Some(outer) if self.hashes.borrow().is_empty() => outer,
_ => Track::track(self),
/// 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 }
}
/// 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 }
}
/// 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(),
}
}
/// 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);
// Bump the next disambiguator up by one.
self.hashes.get_mut().insert(hash, disambiguator + 1);
// Create the location in its default variant.
Location { hash, disambiguator }
}
/// 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);
}
_ => {}
}
}
}
/// 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);
}
}
/// 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 copy of this locator for measurement or relayout of the same
/// content. See the type-level docs for more details.
///
/// 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> {
/// The current disambiguator for the hash.
fn disambiguator(&self, hash: u128) -> usize {
self.disambiguator_impl(hash)
/// Resolves the locator based on its local and the outer information.
fn resolve(&self) -> Resolved {
match self.outer {
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),
},
}
}
}
impl Debug for Locator<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Locator({:?})", self.resolve())
}
}
/// 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),
}
/// 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))
}
/// 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),
}
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(),
}
}
/// 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(),
}
}
/// 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),
})
}
}

View File

@ -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)
}
}

View File

@ -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();

View File

@ -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);

View File

@ -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>

View File

@ -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());

View File

@ -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:?}"),
}
}
}

View File

@ -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)
}

View File

@ -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,8 +922,15 @@ impl<'a> GridLayouter<'a> {
)? {
Some(resolved) => resolved,
None => {
self.finish_region(engine)?;
self.measure_auto_row(engine, y, false, self.unbreakable_rows_left, None)?
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 !last {
let disambiguator = self.finished.len();
if let Some(Repeatable::Repeated(footer)) = &self.grid.footer {
self.prepare_footer(footer, engine)?;
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)?;
self.layout_header(header, engine, disambiguator)?;
}
// Ensure rows don't try to overrun the footer.
self.regions.size.y -= self.footer_height;
}
Ok(())
}

View File

@ -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![

View File

@ -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,

View File

@ -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,
)
}
}

View File

@ -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)
}

View File

@ -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(_) => {}

View File

@ -76,7 +76,9 @@ 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| {
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();
@ -84,10 +86,16 @@ impl Show for Packed<LayoutElem> {
let context = Context::new(Some(loc), Some(styles));
let result = elem
.func()
.call(engine, context.track(), [dict! { "width" => x, "height" => y }])?
.call(
engine,
context.track(),
[dict! { "width" => x, "height" => y }],
)?
.display();
result.layout(engine, styles, regions)
})
.pack())
result.layout(engine, locator, styles, regions)
},
)
.pack()
.spanned(self.span()))
}
}

View File

@ -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(
// 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 })
}

View File

@ -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,
)

View File

@ -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);

View File

@ -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) {

View File

@ -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))
}
}

View File

@ -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;

View File

@ -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.

View File

@ -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.

View File

@ -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),
};

View File

@ -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)

View File

@ -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);

View File

@ -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(());
}

View File

@ -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"
)
}

View File

@ -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");

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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(

View File

@ -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,

View File

@ -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,

View File

@ -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!(

View File

@ -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.

View File

@ -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> {

View File

@ -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> {

View File

@ -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> {

View File

@ -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 {

View File

@ -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> {

View File

@ -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,9 +135,12 @@ 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| {
Ok(BlockElem::single_layouter(
self.clone(),
|elem, engine, locator, styles, region| {
layout_shape(
engine,
locator,
styles,
region,
ShapeKind::Rect,
@ -148,10 +152,12 @@ impl Show for Packed<RectElem> {
elem.radius(styles),
elem.span(),
)
})
},
)
.with_width(self.width(styles))
.with_height(self.height(styles))
.pack())
.pack()
.spanned(self.span()))
}
}
@ -239,9 +245,12 @@ 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| {
Ok(BlockElem::single_layouter(
self.clone(),
|elem, engine, locator, styles, regions| {
layout_shape(
engine,
locator,
styles,
regions,
ShapeKind::Square,
@ -253,10 +262,12 @@ impl Show for Packed<SquareElem> {
elem.radius(styles),
elem.span(),
)
})
},
)
.with_width(self.width(styles))
.with_height(self.height(styles))
.pack())
.pack()
.spanned(self.span()))
}
}
@ -316,9 +327,12 @@ 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| {
Ok(BlockElem::single_layouter(
self.clone(),
|elem, engine, locator, styles, regions| {
layout_shape(
engine,
locator,
styles,
regions,
ShapeKind::Ellipse,
@ -330,10 +344,12 @@ impl Show for Packed<EllipseElem> {
Corners::splat(None),
elem.span(),
)
})
},
)
.with_width(self.width(styles))
.with_height(self.height(styles))
.pack())
.pack()
.spanned(self.span()))
}
}
@ -418,9 +434,12 @@ 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| {
Ok(BlockElem::single_layouter(
self.clone(),
|elem, engine, locator, styles, regions| {
layout_shape(
engine,
locator,
styles,
regions,
ShapeKind::Circle,
@ -432,10 +451,12 @@ impl Show for Packed<CircleElem> {
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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 743 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 B

View File

@ -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()

View File

@ -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
}

View File

@ -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(