Fix logical ordering of floats and footnotes (#5185)
@ -10,7 +10,7 @@ use crate::foundations::{
|
|||||||
cast, func, repr, scope, ty, CastInfo, Content, Context, Dict, Element, FromValue,
|
cast, func, repr, scope, ty, CastInfo, Content, Context, Dict, Element, FromValue,
|
||||||
Func, Label, Reflect, Regex, Repr, Str, StyleChain, Type, Value,
|
Func, Label, Reflect, Regex, Repr, Str, StyleChain, Type, Value,
|
||||||
};
|
};
|
||||||
use crate::introspection::{Introspector, Locatable, Location};
|
use crate::introspection::{Introspector, Locatable, Location, Unqueriable};
|
||||||
use crate::symbols::Symbol;
|
use crate::symbols::Symbol;
|
||||||
|
|
||||||
/// A helper macro to create a field selector used in [`Selector::Elem`]
|
/// A helper macro to create a field selector used in [`Selector::Elem`]
|
||||||
@ -339,7 +339,7 @@ impl FromValue for LocatableSelector {
|
|||||||
fn validate(selector: &Selector) -> StrResult<()> {
|
fn validate(selector: &Selector) -> StrResult<()> {
|
||||||
match selector {
|
match selector {
|
||||||
Selector::Elem(elem, _) => {
|
Selector::Elem(elem, _) => {
|
||||||
if !elem.can::<dyn Locatable>() {
|
if !elem.can::<dyn Locatable>() || elem.can::<dyn Unqueriable>() {
|
||||||
Err(eco_format!("{} is not locatable", elem.name()))?
|
Err(eco_format!("{} is not locatable", elem.name()))?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ use crate::foundations::{
|
|||||||
Element, Func, IntoValue, Label, LocatableSelector, NativeElement, Packed, Repr,
|
Element, Func, IntoValue, Label, LocatableSelector, NativeElement, Packed, Repr,
|
||||||
Selector, Show, Smart, Str, StyleChain, Value,
|
Selector, Show, Smart, Str, StyleChain, Value,
|
||||||
};
|
};
|
||||||
use crate::introspection::{Introspector, Locatable, Location};
|
use crate::introspection::{Introspector, Locatable, Location, Tag};
|
||||||
use crate::layout::{Frame, FrameItem, PageElem};
|
use crate::layout::{Frame, FrameItem, PageElem};
|
||||||
use crate::math::EquationElem;
|
use crate::math::EquationElem;
|
||||||
use crate::model::{FigureElem, FootnoteElem, HeadingElem, Numbering, NumberingPattern};
|
use crate::model::{FigureElem, FootnoteElem, HeadingElem, Numbering, NumberingPattern};
|
||||||
@ -821,8 +821,8 @@ impl ManualPageCounter {
|
|||||||
for (_, item) in page.items() {
|
for (_, item) in page.items() {
|
||||||
match item {
|
match item {
|
||||||
FrameItem::Group(group) => self.visit(engine, &group.frame)?,
|
FrameItem::Group(group) => self.visit(engine, &group.frame)?,
|
||||||
FrameItem::Tag(tag) => {
|
FrameItem::Tag(Tag::Start(elem)) => {
|
||||||
let Some(elem) = tag.elem().to_packed::<CounterUpdateElem>() else {
|
let Some(elem) = elem.to_packed::<CounterUpdateElem>() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if *elem.key() == CounterKey::Page {
|
if *elem.key() == CounterKey::Page {
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
use std::collections::{BTreeSet, HashMap};
|
use std::collections::{BTreeSet, HashMap, HashSet};
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
|
||||||
use ecow::{eco_format, EcoVec};
|
use ecow::EcoVec;
|
||||||
use indexmap::IndexMap;
|
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::diag::{bail, StrResult};
|
use crate::diag::{bail, StrResult};
|
||||||
use crate::foundations::{Content, Label, Repr, Selector};
|
use crate::foundations::{Content, Label, Repr, Selector};
|
||||||
use crate::introspection::{Location, TagKind};
|
use crate::introspection::{Location, Tag};
|
||||||
use crate::layout::{Frame, FrameItem, Page, Point, Position, Transform};
|
use crate::layout::{Frame, FrameItem, Page, Point, Position, Transform};
|
||||||
use crate::model::Numbering;
|
use crate::model::Numbering;
|
||||||
use crate::utils::NonZeroExt;
|
use crate::utils::NonZeroExt;
|
||||||
@ -20,16 +19,20 @@ use crate::utils::NonZeroExt;
|
|||||||
pub struct Introspector {
|
pub struct Introspector {
|
||||||
/// The number of pages in the document.
|
/// The number of pages in the document.
|
||||||
pages: usize,
|
pages: usize,
|
||||||
/// All introspectable elements.
|
|
||||||
elems: IndexMap<Location, (Content, Position)>,
|
|
||||||
/// 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.
|
/// The page numberings, indexed by page number minus 1.
|
||||||
page_numberings: Vec<Option<Numbering>>,
|
page_numberings: Vec<Option<Numbering>>,
|
||||||
|
|
||||||
|
/// All introspectable elements.
|
||||||
|
elems: Vec<Pair>,
|
||||||
|
/// Lists all elements with a specific hash key. This is used for
|
||||||
|
/// introspector-assisted location assignment during measurement.
|
||||||
|
keys: MultiMap<u128, Location>,
|
||||||
|
|
||||||
|
/// Accelerates lookup of elements by location.
|
||||||
|
locations: HashMap<Location, usize>,
|
||||||
|
/// Accelerates lookup of elements by label.
|
||||||
|
labels: MultiMap<Label, usize>,
|
||||||
|
|
||||||
/// Caches queries done on the introspector. This is important because
|
/// Caches queries done on the introspector. This is important because
|
||||||
/// even if all top-level queries are distinct, they often have shared
|
/// even if all top-level queries are distinct, they often have shared
|
||||||
/// subqueries. Example: Individual counter queries with `before` that
|
/// subqueries. Example: Individual counter queries with `before` that
|
||||||
@ -37,81 +40,56 @@ pub struct Introspector {
|
|||||||
queries: QueryCache,
|
queries: QueryCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A pair of content and its position.
|
||||||
|
type Pair = (Content, Position);
|
||||||
|
|
||||||
impl Introspector {
|
impl Introspector {
|
||||||
/// Applies new frames in-place, reusing the existing allocations.
|
/// Creates an introspector for a page list.
|
||||||
#[typst_macros::time(name = "introspect")]
|
#[typst_macros::time(name = "introspect")]
|
||||||
pub fn rebuild(&mut self, pages: &[Page]) {
|
pub fn new(pages: &[Page]) -> Self {
|
||||||
self.pages = pages.len();
|
IntrospectorBuilder::new().build(pages)
|
||||||
self.elems.clear();
|
|
||||||
self.labels.clear();
|
|
||||||
self.keys.clear();
|
|
||||||
self.page_numberings.clear();
|
|
||||||
self.queries.clear();
|
|
||||||
|
|
||||||
for (i, page) in pages.iter().enumerate() {
|
|
||||||
let page_nr = NonZeroUsize::new(1 + i).unwrap();
|
|
||||||
self.extract(&page.frame, page_nr, Transform::identity());
|
|
||||||
self.page_numberings.push(page.numbering.clone());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract metadata from a frame.
|
/// Iterates over all locatable elements.
|
||||||
fn extract(&mut self, frame: &Frame, page: NonZeroUsize, ts: Transform) {
|
|
||||||
for (pos, item) in frame.items() {
|
|
||||||
match item {
|
|
||||||
FrameItem::Group(group) => {
|
|
||||||
let ts = ts
|
|
||||||
.pre_concat(Transform::translate(pos.x, pos.y))
|
|
||||||
.pre_concat(group.transform);
|
|
||||||
self.extract(&group.frame, page, ts);
|
|
||||||
}
|
|
||||||
FrameItem::Tag(tag)
|
|
||||||
if tag.kind() == TagKind::Start
|
|
||||||
&& !self.elems.contains_key(&tag.location()) =>
|
|
||||||
{
|
|
||||||
let pos = pos.transform(ts);
|
|
||||||
let loc = tag.location();
|
|
||||||
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) = tag.elem().label() {
|
|
||||||
self.labels.entry(label).or_default().push(self.elems.len() - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate over all locatable elements.
|
|
||||||
pub fn all(&self) -> impl Iterator<Item = &Content> + '_ {
|
pub fn all(&self) -> impl Iterator<Item = &Content> + '_ {
|
||||||
self.elems.values().map(|(c, _)| c)
|
self.elems.iter().map(|(c, _)| c)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform a binary search for `elem` among the `list`.
|
/// Retrieves the element with the given index.
|
||||||
|
#[track_caller]
|
||||||
|
fn get_by_idx(&self, idx: usize) -> &Content {
|
||||||
|
&self.elems[idx].0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the position of the element with the given index.
|
||||||
|
#[track_caller]
|
||||||
|
fn get_pos_by_idx(&self, idx: usize) -> Position {
|
||||||
|
self.elems[idx].1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves an element by its location.
|
||||||
|
fn get_by_loc(&self, location: &Location) -> Option<&Content> {
|
||||||
|
self.locations.get(location).map(|&idx| self.get_by_idx(idx))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the position of the element with the given index.
|
||||||
|
fn get_pos_by_loc(&self, location: &Location) -> Option<Position> {
|
||||||
|
self.locations.get(location).map(|&idx| self.get_pos_by_idx(idx))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs a binary search for `elem` among the `list`.
|
||||||
fn binary_search(&self, list: &[Content], elem: &Content) -> Result<usize, usize> {
|
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))
|
list.binary_search_by_key(&self.elem_index(elem), |elem| self.elem_index(elem))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an element by its location.
|
/// Gets the index of this element.
|
||||||
fn get(&self, location: &Location) -> Option<&Content> {
|
|
||||||
self.elems.get(location).map(|(elem, _)| elem)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the index of this element among all.
|
|
||||||
fn elem_index(&self, elem: &Content) -> usize {
|
fn elem_index(&self, elem: &Content) -> usize {
|
||||||
self.loc_index(&elem.location().unwrap())
|
self.loc_index(&elem.location().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the index of the element with this location among all.
|
/// Gets the index of the element with this location among all.
|
||||||
fn loc_index(&self, location: &Location) -> usize {
|
fn loc_index(&self, location: &Location) -> usize {
|
||||||
self.elems.get_index_of(location).unwrap_or(usize::MAX)
|
self.locations.get(location).copied().unwrap_or(usize::MAX)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,20 +103,50 @@ impl Introspector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let output = match selector {
|
let output = match selector {
|
||||||
Selector::Label(label) => self
|
Selector::Elem(..) => self
|
||||||
.labels
|
|
||||||
.get(label)
|
|
||||||
.map(|indices| {
|
|
||||||
indices.iter().map(|&index| self.elems[index].0.clone()).collect()
|
|
||||||
})
|
|
||||||
.unwrap_or_default(),
|
|
||||||
Selector::Elem(..) | Selector::Can(_) => self
|
|
||||||
.all()
|
.all()
|
||||||
.filter(|elem| selector.matches(elem, None))
|
.filter(|elem| selector.matches(elem, None))
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect(),
|
.collect(),
|
||||||
Selector::Location(location) => {
|
Selector::Location(location) => {
|
||||||
self.get(location).cloned().into_iter().collect()
|
self.get_by_loc(location).cloned().into_iter().collect()
|
||||||
|
}
|
||||||
|
Selector::Label(label) => self
|
||||||
|
.labels
|
||||||
|
.get(label)
|
||||||
|
.iter()
|
||||||
|
.map(|&idx| self.get_by_idx(idx).clone())
|
||||||
|
.collect(),
|
||||||
|
Selector::Or(selectors) => selectors
|
||||||
|
.iter()
|
||||||
|
.flat_map(|sel| self.query(sel))
|
||||||
|
.map(|elem| self.elem_index(&elem))
|
||||||
|
.collect::<BTreeSet<usize>>()
|
||||||
|
.into_iter()
|
||||||
|
.map(|idx| self.get_by_idx(idx).clone())
|
||||||
|
.collect(),
|
||||||
|
Selector::And(selectors) => {
|
||||||
|
let mut results: Vec<_> =
|
||||||
|
selectors.iter().map(|sel| self.query(sel)).collect();
|
||||||
|
|
||||||
|
// Extract the smallest result list and then keep only those
|
||||||
|
// elements in the smallest list that are also in all other
|
||||||
|
// lists.
|
||||||
|
results
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.min_by_key(|(_, vec)| vec.len())
|
||||||
|
.map(|(i, _)| i)
|
||||||
|
.map(|i| results.swap_remove(i))
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.filter(|candidate| {
|
||||||
|
results
|
||||||
|
.iter()
|
||||||
|
.all(|other| self.binary_search(other, candidate).is_ok())
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
Selector::Before { selector, end, inclusive } => {
|
Selector::Before { selector, end, inclusive } => {
|
||||||
let mut list = self.query(selector);
|
let mut list = self.query(selector);
|
||||||
@ -168,39 +176,8 @@ impl Introspector {
|
|||||||
}
|
}
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
Selector::And(selectors) => {
|
|
||||||
let mut results: Vec<_> =
|
|
||||||
selectors.iter().map(|sel| self.query(sel)).collect();
|
|
||||||
|
|
||||||
// Extract the smallest result list and then keep only those
|
|
||||||
// elements in the smallest list that are also in all other
|
|
||||||
// lists.
|
|
||||||
results
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.min_by_key(|(_, vec)| vec.len())
|
|
||||||
.map(|(i, _)| i)
|
|
||||||
.map(|i| results.swap_remove(i))
|
|
||||||
.iter()
|
|
||||||
.flatten()
|
|
||||||
.filter(|candidate| {
|
|
||||||
results
|
|
||||||
.iter()
|
|
||||||
.all(|other| self.binary_search(other, candidate).is_ok())
|
|
||||||
})
|
|
||||||
.cloned()
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
Selector::Or(selectors) => selectors
|
|
||||||
.iter()
|
|
||||||
.flat_map(|sel| self.query(sel))
|
|
||||||
.map(|elem| self.elem_index(&elem))
|
|
||||||
.collect::<BTreeSet<usize>>()
|
|
||||||
.into_iter()
|
|
||||||
.map(|index| self.elems[index].0.clone())
|
|
||||||
.collect(),
|
|
||||||
// Not supported here.
|
// Not supported here.
|
||||||
Selector::Regex(_) => EcoVec::new(),
|
Selector::Can(_) | Selector::Regex(_) => EcoVec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.queries.insert(hash, output.clone());
|
self.queries.insert(hash, output.clone());
|
||||||
@ -210,12 +187,12 @@ impl Introspector {
|
|||||||
/// Query for the first element that matches the selector.
|
/// Query for the first element that matches the selector.
|
||||||
pub fn query_first(&self, selector: &Selector) -> Option<Content> {
|
pub fn query_first(&self, selector: &Selector) -> Option<Content> {
|
||||||
match selector {
|
match selector {
|
||||||
Selector::Location(location) => self.get(location).cloned(),
|
Selector::Location(location) => self.get_by_loc(location).cloned(),
|
||||||
Selector::Label(label) => self
|
Selector::Label(label) => self
|
||||||
.labels
|
.labels
|
||||||
.get(label)
|
.get(label)
|
||||||
.and_then(|indices| indices.first())
|
.first()
|
||||||
.map(|&index| self.elems[index].0.clone()),
|
.map(|&idx| self.get_by_idx(idx).clone()),
|
||||||
_ => self.query(selector).first().cloned(),
|
_ => self.query(selector).first().cloned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -224,7 +201,7 @@ impl Introspector {
|
|||||||
pub fn query_unique(&self, selector: &Selector) -> StrResult<Content> {
|
pub fn query_unique(&self, selector: &Selector) -> StrResult<Content> {
|
||||||
match selector {
|
match selector {
|
||||||
Selector::Location(location) => self
|
Selector::Location(location) => self
|
||||||
.get(location)
|
.get_by_loc(location)
|
||||||
.cloned()
|
.cloned()
|
||||||
.ok_or_else(|| "element does not exist in the document".into()),
|
.ok_or_else(|| "element does not exist in the document".into()),
|
||||||
Selector::Label(label) => self.query_label(*label).cloned(),
|
Selector::Label(label) => self.query_label(*label).cloned(),
|
||||||
@ -243,15 +220,11 @@ impl Introspector {
|
|||||||
|
|
||||||
/// Query for a unique element with the label.
|
/// Query for a unique element with the label.
|
||||||
pub fn query_label(&self, label: Label) -> StrResult<&Content> {
|
pub fn query_label(&self, label: Label) -> StrResult<&Content> {
|
||||||
let indices = self.labels.get(&label).ok_or_else(|| {
|
match *self.labels.get(&label) {
|
||||||
eco_format!("label `{}` does not exist in the document", label.repr())
|
[idx] => Ok(self.get_by_idx(idx)),
|
||||||
})?;
|
[] => bail!("label `{}` does not exist in the document", label.repr()),
|
||||||
|
_ => bail!("label `{}` occurs multiple times in the document", label.repr()),
|
||||||
if indices.len() > 1 {
|
|
||||||
bail!("label `{}` occurs multiple times in the document", label.repr());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(&self.elems[indices[0]].0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is an optimized version of
|
/// This is an optimized version of
|
||||||
@ -259,7 +232,7 @@ impl Introspector {
|
|||||||
pub fn query_count_before(&self, selector: &Selector, end: Location) -> usize {
|
pub fn query_count_before(&self, selector: &Selector, end: Location) -> usize {
|
||||||
// See `query()` for details.
|
// See `query()` for details.
|
||||||
let list = self.query(selector);
|
let list = self.query(selector);
|
||||||
if let Some(end) = self.get(&end) {
|
if let Some(end) = self.get_by_loc(&end) {
|
||||||
match self.binary_search(&list, end) {
|
match self.binary_search(&list, end) {
|
||||||
Ok(i) => i + 1,
|
Ok(i) => i + 1,
|
||||||
Err(i) => i,
|
Err(i) => i,
|
||||||
@ -274,14 +247,6 @@ impl Introspector {
|
|||||||
NonZeroUsize::new(self.pages).unwrap_or(NonZeroUsize::ONE)
|
NonZeroUsize::new(self.pages).unwrap_or(NonZeroUsize::ONE)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the page numbering for the given location, if any.
|
|
||||||
pub fn page_numbering(&self, location: Location) -> Option<&Numbering> {
|
|
||||||
let page = self.page(location);
|
|
||||||
self.page_numberings
|
|
||||||
.get(page.get() - 1)
|
|
||||||
.and_then(|slot| slot.as_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find the page number for the given location.
|
/// Find the page number for the given location.
|
||||||
pub fn page(&self, location: Location) -> NonZeroUsize {
|
pub fn page(&self, location: Location) -> NonZeroUsize {
|
||||||
self.position(location).page
|
self.position(location).page
|
||||||
@ -289,12 +254,18 @@ impl Introspector {
|
|||||||
|
|
||||||
/// Find the position for the given location.
|
/// Find the position for the given location.
|
||||||
pub fn position(&self, location: Location) -> Position {
|
pub fn position(&self, location: Location) -> Position {
|
||||||
self.elems
|
self.get_pos_by_loc(&location)
|
||||||
.get(&location)
|
|
||||||
.map(|&(_, pos)| pos)
|
|
||||||
.unwrap_or(Position { page: NonZeroUsize::ONE, point: Point::zero() })
|
.unwrap_or(Position { page: NonZeroUsize::ONE, point: Point::zero() })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the page numbering for the given location, if any.
|
||||||
|
pub fn page_numbering(&self, location: Location) -> Option<&Numbering> {
|
||||||
|
let page = self.page(location);
|
||||||
|
self.page_numberings
|
||||||
|
.get(page.get() - 1)
|
||||||
|
.and_then(|slot| slot.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
/// Try to find a location for an element with the given `key` hash
|
/// Try to find a location for an element with the given `key` hash
|
||||||
/// that is closest after the `anchor`.
|
/// that is closest after the `anchor`.
|
||||||
///
|
///
|
||||||
@ -304,7 +275,7 @@ impl Introspector {
|
|||||||
pub fn locator(&self, key: u128, anchor: Location) -> Option<Location> {
|
pub fn locator(&self, key: u128, anchor: Location) -> Option<Location> {
|
||||||
let anchor = self.loc_index(&anchor);
|
let anchor = self.loc_index(&anchor);
|
||||||
self.keys
|
self.keys
|
||||||
.get(&key)?
|
.get(&key)
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.min_by_key(|loc| self.loc_index(loc).wrapping_sub(anchor))
|
.min_by_key(|loc| self.loc_index(loc).wrapping_sub(anchor))
|
||||||
@ -317,6 +288,33 @@ impl Debug for Introspector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A map from one keys to multiple elements.
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct MultiMap<K, V>(HashMap<K, SmallVec<[V; 1]>>);
|
||||||
|
|
||||||
|
impl<K, V> MultiMap<K, V>
|
||||||
|
where
|
||||||
|
K: Hash + Eq,
|
||||||
|
{
|
||||||
|
fn get(&self, key: &K) -> &[V] {
|
||||||
|
self.0.get(key).map_or(&[], |vec| vec.as_slice())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(&mut self, key: K, value: V) {
|
||||||
|
self.0.entry(key).or_default().push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn take(&mut self, key: &K) -> Option<impl Iterator<Item = V>> {
|
||||||
|
self.0.remove(key).map(|vec| vec.into_iter())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K, V> Default for MultiMap<K, V> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(HashMap::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Caches queries.
|
/// Caches queries.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct QueryCache(RwLock<HashMap<u128, EcoVec<Content>>>);
|
struct QueryCache(RwLock<HashMap<u128, EcoVec<Content>>>);
|
||||||
@ -329,10 +327,6 @@ impl QueryCache {
|
|||||||
fn insert(&self, hash: u128, output: EcoVec<Content>) {
|
fn insert(&self, hash: u128, output: EcoVec<Content>) {
|
||||||
self.0.write().unwrap().insert(hash, output);
|
self.0.write().unwrap().insert(hash, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear(&mut self) {
|
|
||||||
self.0.get_mut().unwrap().clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for QueryCache {
|
impl Clone for QueryCache {
|
||||||
@ -340,3 +334,120 @@ impl Clone for QueryCache {
|
|||||||
Self(RwLock::new(self.0.read().unwrap().clone()))
|
Self(RwLock::new(self.0.read().unwrap().clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builds the introspector.
|
||||||
|
#[derive(Default)]
|
||||||
|
struct IntrospectorBuilder {
|
||||||
|
page_numberings: Vec<Option<Numbering>>,
|
||||||
|
seen: HashSet<Location>,
|
||||||
|
insertions: MultiMap<Location, Vec<Pair>>,
|
||||||
|
keys: MultiMap<u128, Location>,
|
||||||
|
locations: HashMap<Location, usize>,
|
||||||
|
labels: MultiMap<Label, usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntrospectorBuilder {
|
||||||
|
/// Create an empty builder.
|
||||||
|
fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the introspector.
|
||||||
|
fn build(mut self, pages: &[Page]) -> Introspector {
|
||||||
|
self.page_numberings.reserve(pages.len());
|
||||||
|
|
||||||
|
// Discover all elements.
|
||||||
|
let mut root = Vec::new();
|
||||||
|
for (i, page) in pages.iter().enumerate() {
|
||||||
|
self.page_numberings.push(page.numbering.clone());
|
||||||
|
self.discover(
|
||||||
|
&mut root,
|
||||||
|
&page.frame,
|
||||||
|
NonZeroUsize::new(1 + i).unwrap(),
|
||||||
|
Transform::identity(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.locations.reserve(self.seen.len());
|
||||||
|
|
||||||
|
// Save all pairs and their descendants in the correct order.
|
||||||
|
let mut elems = Vec::with_capacity(self.seen.len());
|
||||||
|
for pair in root {
|
||||||
|
self.visit(&mut elems, pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
Introspector {
|
||||||
|
pages: pages.len(),
|
||||||
|
page_numberings: self.page_numberings,
|
||||||
|
elems,
|
||||||
|
keys: self.keys,
|
||||||
|
locations: self.locations,
|
||||||
|
labels: self.labels,
|
||||||
|
queries: QueryCache::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Processes the tags in the frame.
|
||||||
|
fn discover(
|
||||||
|
&mut self,
|
||||||
|
sink: &mut Vec<Pair>,
|
||||||
|
frame: &Frame,
|
||||||
|
page: NonZeroUsize,
|
||||||
|
ts: Transform,
|
||||||
|
) {
|
||||||
|
for (pos, item) in frame.items() {
|
||||||
|
match item {
|
||||||
|
FrameItem::Group(group) => {
|
||||||
|
let ts = ts
|
||||||
|
.pre_concat(Transform::translate(pos.x, pos.y))
|
||||||
|
.pre_concat(group.transform);
|
||||||
|
|
||||||
|
if let Some(parent) = group.parent {
|
||||||
|
let mut nested = vec![];
|
||||||
|
self.discover(&mut nested, &group.frame, page, ts);
|
||||||
|
self.insertions.insert(parent, nested);
|
||||||
|
} else {
|
||||||
|
self.discover(sink, &group.frame, page, ts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FrameItem::Tag(Tag::Start(elem)) => {
|
||||||
|
let loc = elem.location().unwrap();
|
||||||
|
if self.seen.insert(loc) {
|
||||||
|
let point = pos.transform(ts);
|
||||||
|
sink.push((elem.clone(), Position { page, point }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FrameItem::Tag(Tag::End(loc, key)) => {
|
||||||
|
self.keys.insert(*key, *loc);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Saves a pair and all its descendants into `elems` and populates the
|
||||||
|
/// acceleration structures.
|
||||||
|
fn visit(&mut self, elems: &mut Vec<Pair>, pair: Pair) {
|
||||||
|
let elem = &pair.0;
|
||||||
|
let loc = elem.location().unwrap();
|
||||||
|
let idx = elems.len();
|
||||||
|
|
||||||
|
// Populate the location acceleration map.
|
||||||
|
self.locations.insert(loc, idx);
|
||||||
|
|
||||||
|
// Populate the label acceleration map.
|
||||||
|
if let Some(label) = elem.label() {
|
||||||
|
self.labels.insert(label, idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the element.
|
||||||
|
elems.push(pair);
|
||||||
|
|
||||||
|
// Process potential descendants.
|
||||||
|
if let Some(insertions) = self.insertions.take(&loc) {
|
||||||
|
for pair in insertions.flatten() {
|
||||||
|
self.visit(elems, pair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -105,5 +105,9 @@ impl Repr for Location {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Makes this element locatable through `engine.locate`.
|
/// Makes this element as locatable through the introspector.
|
||||||
pub trait Locatable {}
|
pub trait Locatable {}
|
||||||
|
|
||||||
|
/// Marks this element as not being queryable even though it is locatable for
|
||||||
|
/// internal reasons.
|
||||||
|
pub trait Unqueriable {}
|
||||||
|
@ -7,79 +7,48 @@ use crate::foundations::{
|
|||||||
};
|
};
|
||||||
use crate::introspection::Location;
|
use crate::introspection::Location;
|
||||||
|
|
||||||
/// Holds a locatable element that was realized.
|
/// Marks the start or end of a locatable element.
|
||||||
#[derive(Clone, PartialEq, Hash)]
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
pub struct Tag {
|
pub enum Tag {
|
||||||
/// Whether this is a start or end tag.
|
/// The stored element starts here.
|
||||||
kind: TagKind,
|
///
|
||||||
/// The introspectible element.
|
/// Content placed in a tag **must** have a [`Location`] or there will be
|
||||||
elem: Content,
|
/// panics.
|
||||||
/// The element's key hash.
|
Start(Content),
|
||||||
key: u128,
|
/// The element with the given location and key hash ends here.
|
||||||
|
///
|
||||||
|
/// Note: The key hash is stored here instead of in `Start` simply to make
|
||||||
|
/// the two enum variants more balanced in size, keeping a `Tag`'s memory
|
||||||
|
/// size down. There are no semantic reasons for this.
|
||||||
|
End(Location, u128),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tag {
|
impl Tag {
|
||||||
/// Create a start tag from an element and its key hash.
|
/// Access the location of the tag.
|
||||||
///
|
|
||||||
/// Panics if the element does not have a [`Location`].
|
|
||||||
#[track_caller]
|
|
||||||
pub fn new(elem: Content, key: u128) -> Self {
|
|
||||||
assert!(elem.location().is_some());
|
|
||||||
Self { elem, key, kind: TagKind::Start }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the same tag with the given kind.
|
|
||||||
pub fn with_kind(self, kind: TagKind) -> Self {
|
|
||||||
Self { kind, ..self }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether this is a start or end tag.
|
|
||||||
pub fn kind(&self) -> TagKind {
|
|
||||||
self.kind
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The locatable element that the tag holds.
|
|
||||||
pub fn elem(&self) -> &Content {
|
|
||||||
&self.elem
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Access the location of the element.
|
|
||||||
pub fn location(&self) -> Location {
|
pub fn location(&self) -> Location {
|
||||||
self.elem.location().unwrap()
|
match self {
|
||||||
}
|
Tag::Start(elem) => elem.location().unwrap(),
|
||||||
|
Tag::End(loc, _) => *loc,
|
||||||
/// 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 fn key(&self) -> u128 {
|
|
||||||
self.key
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Tag {
|
impl Debug for Tag {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f, "Tag({:?}, {:?})", self.kind, self.elem.elem().name())
|
match self {
|
||||||
|
Tag::Start(elem) => write!(f, "Start({:?})", elem.elem().name()),
|
||||||
|
Tag::End(..) => f.pad("End"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines whether a tag marks the start or end of an element.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
|
||||||
pub enum TagKind {
|
|
||||||
/// The tag indicates that the element starts here.
|
|
||||||
Start,
|
|
||||||
/// The tag indicates that the element end here.
|
|
||||||
End,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Holds a tag for 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
|
/// The `TagElem` is handled by all layouters. The held element becomes
|
||||||
/// available for introspection in the next compiler iteration.
|
/// available for introspection in the next compiler iteration.
|
||||||
#[elem(Construct, Unlabellable)]
|
#[elem(Construct, Unlabellable)]
|
||||||
pub struct TagElem {
|
pub struct TagElem {
|
||||||
/// The introspectible element.
|
/// The introspectable element.
|
||||||
#[required]
|
#[required]
|
||||||
#[internal]
|
#[internal]
|
||||||
pub tag: Tag,
|
pub tag: Tag,
|
||||||
|
@ -190,7 +190,7 @@ impl Packed<BoxElem> {
|
|||||||
|
|
||||||
// Assign label to the frame.
|
// Assign label to the frame.
|
||||||
if let Some(label) = self.label() {
|
if let Some(label) = self.label() {
|
||||||
frame.group(|group| group.label = Some(label))
|
frame.label(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply baseline shift. Do this after setting the size and applying the
|
// Apply baseline shift. Do this after setting the size and applying the
|
||||||
@ -562,7 +562,7 @@ impl Packed<BlockElem> {
|
|||||||
|
|
||||||
// Assign label to each frame in the fragment.
|
// Assign label to each frame in the fragment.
|
||||||
if let Some(label) = self.label() {
|
if let Some(label) = self.label() {
|
||||||
frame.group(|group| group.label = Some(label));
|
frame.label(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(frame)
|
Ok(frame)
|
||||||
@ -723,7 +723,7 @@ impl Packed<BlockElem> {
|
|||||||
// Assign label to each frame in the fragment.
|
// Assign label to each frame in the fragment.
|
||||||
if let Some(label) = self.label() {
|
if let Some(label) = self.label() {
|
||||||
for frame in fragment.iter_mut() {
|
for frame in fragment.iter_mut() {
|
||||||
frame.group(|group| group.label = Some(label))
|
frame.label(label);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ use crate::diag::{bail, SourceResult};
|
|||||||
use crate::engine::{Engine, Route, Sink, Traced};
|
use crate::engine::{Engine, Route, Sink, Traced};
|
||||||
use crate::foundations::{Packed, Resolve, Smart, StyleChain};
|
use crate::foundations::{Packed, Resolve, Smart, StyleChain};
|
||||||
use crate::introspection::{
|
use crate::introspection::{
|
||||||
Introspector, Locator, LocatorLink, SplitLocator, Tag, TagElem,
|
Introspector, Location, Locator, LocatorLink, SplitLocator, Tag, TagElem,
|
||||||
};
|
};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
layout_frame, Abs, AlignElem, Alignment, Axes, BlockElem, ColbreakElem,
|
layout_frame, Abs, AlignElem, Alignment, Axes, BlockElem, ColbreakElem,
|
||||||
@ -62,7 +62,7 @@ struct Collector<'a, 'x, 'y> {
|
|||||||
impl<'a> Collector<'a, '_, '_> {
|
impl<'a> Collector<'a, '_, '_> {
|
||||||
/// Perform the collection.
|
/// Perform the collection.
|
||||||
fn run(mut self) -> SourceResult<Vec<Child<'a>>> {
|
fn run(mut self) -> SourceResult<Vec<Child<'a>>> {
|
||||||
for (idx, &(child, styles)) in self.children.iter().enumerate() {
|
for &(child, styles) in self.children {
|
||||||
if let Some(elem) = child.to_packed::<TagElem>() {
|
if let Some(elem) = child.to_packed::<TagElem>() {
|
||||||
self.output.push(Child::Tag(&elem.tag));
|
self.output.push(Child::Tag(&elem.tag));
|
||||||
} else if let Some(elem) = child.to_packed::<VElem>() {
|
} else if let Some(elem) = child.to_packed::<VElem>() {
|
||||||
@ -72,7 +72,7 @@ impl<'a> Collector<'a, '_, '_> {
|
|||||||
} else if let Some(elem) = child.to_packed::<BlockElem>() {
|
} else if let Some(elem) = child.to_packed::<BlockElem>() {
|
||||||
self.block(elem, styles);
|
self.block(elem, styles);
|
||||||
} else if let Some(elem) = child.to_packed::<PlaceElem>() {
|
} else if let Some(elem) = child.to_packed::<PlaceElem>() {
|
||||||
self.place(idx, elem, styles)?;
|
self.place(elem, styles)?;
|
||||||
} else if child.is::<FlushElem>() {
|
} else if child.is::<FlushElem>() {
|
||||||
self.output.push(Child::Flush);
|
self.output.push(Child::Flush);
|
||||||
} else if let Some(elem) = child.to_packed::<ColbreakElem>() {
|
} else if let Some(elem) = child.to_packed::<ColbreakElem>() {
|
||||||
@ -220,7 +220,6 @@ impl<'a> Collector<'a, '_, '_> {
|
|||||||
/// Collects a placed element into a [`PlacedChild`].
|
/// Collects a placed element into a [`PlacedChild`].
|
||||||
fn place(
|
fn place(
|
||||||
&mut self,
|
&mut self,
|
||||||
idx: usize,
|
|
||||||
elem: &'a Packed<PlaceElem>,
|
elem: &'a Packed<PlaceElem>,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
@ -257,7 +256,6 @@ impl<'a> Collector<'a, '_, '_> {
|
|||||||
let clearance = elem.clearance(styles);
|
let clearance = elem.clearance(styles);
|
||||||
let delta = Axes::new(elem.dx(styles), elem.dy(styles)).resolve(styles);
|
let delta = Axes::new(elem.dx(styles), elem.dy(styles)).resolve(styles);
|
||||||
self.output.push(Child::Placed(self.boxed(PlacedChild {
|
self.output.push(Child::Placed(self.boxed(PlacedChild {
|
||||||
idx,
|
|
||||||
align_x,
|
align_x,
|
||||||
align_y,
|
align_y,
|
||||||
scope,
|
scope,
|
||||||
@ -553,7 +551,6 @@ impl MultiSpill<'_, '_> {
|
|||||||
/// A child that encapsulates a prepared placed element.
|
/// A child that encapsulates a prepared placed element.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PlacedChild<'a> {
|
pub struct PlacedChild<'a> {
|
||||||
pub idx: usize,
|
|
||||||
pub align_x: FixedAlignment,
|
pub align_x: FixedAlignment,
|
||||||
pub align_y: Smart<Option<FixedAlignment>>,
|
pub align_y: Smart<Option<FixedAlignment>>,
|
||||||
pub scope: PlacementScope,
|
pub scope: PlacementScope,
|
||||||
@ -573,16 +570,27 @@ impl PlacedChild<'_> {
|
|||||||
self.cell.get_or_init(base, |base| {
|
self.cell.get_or_init(base, |base| {
|
||||||
let align = self.alignment.unwrap_or_else(|| Alignment::CENTER);
|
let align = self.alignment.unwrap_or_else(|| Alignment::CENTER);
|
||||||
let aligned = AlignElem::set_alignment(align).wrap();
|
let aligned = AlignElem::set_alignment(align).wrap();
|
||||||
layout_frame(
|
|
||||||
|
let mut frame = layout_frame(
|
||||||
engine,
|
engine,
|
||||||
&self.elem.body,
|
&self.elem.body,
|
||||||
self.locator.relayout(),
|
self.locator.relayout(),
|
||||||
self.styles.chain(&aligned),
|
self.styles.chain(&aligned),
|
||||||
Region::new(base, Axes::splat(false)),
|
Region::new(base, Axes::splat(false)),
|
||||||
)
|
)?;
|
||||||
.map(|frame| frame.post_processed(self.styles))
|
|
||||||
|
if self.float {
|
||||||
|
frame.set_parent(self.elem.location().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(frame.post_processed(self.styles))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The element's location.
|
||||||
|
pub fn location(&self) -> Location {
|
||||||
|
self.elem.location().unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wraps a parameterized computation and caches its latest output.
|
/// Wraps a parameterized computation and caches its latest output.
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
use super::{
|
use super::{distribute, Config, FlowResult, LineNumberConfig, PlacedChild, Stop, Work};
|
||||||
distribute, Config, FlowResult, LineNumberConfig, PlacedChild, Skip, Stop, Work,
|
|
||||||
};
|
|
||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{Content, NativeElement, Packed, Resolve, Smart};
|
use crate::foundations::{Content, NativeElement, Packed, Resolve, Smart};
|
||||||
use crate::introspection::{
|
use crate::introspection::{
|
||||||
Counter, CounterDisplayElem, CounterState, CounterUpdate, Locator, SplitLocator,
|
Counter, CounterDisplayElem, CounterState, CounterUpdate, Location, Locator,
|
||||||
TagKind,
|
SplitLocator, Tag,
|
||||||
};
|
};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
layout_fragment, layout_frame, Abs, Axes, Dir, FixedAlignment, Frame, FrameItem,
|
layout_fragment, layout_frame, Abs, Axes, Dir, FixedAlignment, Fragment, Frame,
|
||||||
OuterHAlignment, PlacementScope, Point, Region, Regions, Rel, Size,
|
FrameItem, OuterHAlignment, PlacementScope, Point, Region, Regions, Rel, Size,
|
||||||
};
|
};
|
||||||
use crate::model::{
|
use crate::model::{
|
||||||
FootnoteElem, FootnoteEntry, LineNumberingScope, Numbering, ParLineMarker,
|
FootnoteElem, FootnoteEntry, LineNumberingScope, Numbering, ParLineMarker,
|
||||||
@ -246,7 +244,8 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> {
|
|||||||
clearance: bool,
|
clearance: bool,
|
||||||
) -> FlowResult<()> {
|
) -> FlowResult<()> {
|
||||||
// If the float is already processed, skip it.
|
// If the float is already processed, skip it.
|
||||||
if self.skipped(Skip::Placed(placed.idx)) {
|
let loc = placed.location();
|
||||||
|
if self.skipped(loc) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,7 +316,7 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> {
|
|||||||
|
|
||||||
// Put the float there.
|
// Put the float there.
|
||||||
area.push_float(placed, frame, align_y);
|
area.push_float(placed, frame, align_y);
|
||||||
area.skips.push(Skip::Placed(placed.idx));
|
area.skips.push(loc);
|
||||||
|
|
||||||
// Trigger relayout.
|
// Trigger relayout.
|
||||||
Err(Stop::Relayout(placed.scope))
|
Err(Stop::Relayout(placed.scope))
|
||||||
@ -391,7 +390,7 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> {
|
|||||||
) -> FlowResult<()> {
|
) -> FlowResult<()> {
|
||||||
// Ignore reference footnotes and already processed ones.
|
// Ignore reference footnotes and already processed ones.
|
||||||
let loc = elem.location().unwrap();
|
let loc = elem.location().unwrap();
|
||||||
if elem.is_ref() || self.skipped(Skip::Footnote(loc)) {
|
if elem.is_ref() || self.skipped(loc) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -420,14 +419,7 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> {
|
|||||||
pod.size.y -= flow_need + separator_need + self.config.footnote.gap;
|
pod.size.y -= flow_need + separator_need + self.config.footnote.gap;
|
||||||
|
|
||||||
// Layout the footnote entry.
|
// Layout the footnote entry.
|
||||||
let frames = layout_fragment(
|
let frames = layout_footnote(self.engine, self.config, &elem, pod)?.into_frames();
|
||||||
self.engine,
|
|
||||||
&FootnoteEntry::new(elem.clone()).pack(),
|
|
||||||
Locator::synthesize(elem.location().unwrap()),
|
|
||||||
self.config.shared,
|
|
||||||
pod,
|
|
||||||
)?
|
|
||||||
.into_frames();
|
|
||||||
|
|
||||||
// Find nested footnotes in the entry.
|
// Find nested footnotes in the entry.
|
||||||
let nested = find_in_frames::<FootnoteElem>(&frames);
|
let nested = find_in_frames::<FootnoteElem>(&frames);
|
||||||
@ -458,7 +450,7 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> {
|
|||||||
|
|
||||||
// Save the footnote's frame.
|
// Save the footnote's frame.
|
||||||
area.push_footnote(self.config, first);
|
area.push_footnote(self.config, first);
|
||||||
area.skips.push(Skip::Footnote(loc));
|
area.skips.push(loc);
|
||||||
regions.size.y -= note_need;
|
regions.size.y -= note_need;
|
||||||
|
|
||||||
// Save the spill.
|
// Save the spill.
|
||||||
@ -501,10 +493,10 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> {
|
|||||||
|
|
||||||
/// Checks whether an insertion was already processed and doesn't need to be
|
/// Checks whether an insertion was already processed and doesn't need to be
|
||||||
/// handled again.
|
/// handled again.
|
||||||
fn skipped(&self, skip: Skip) -> bool {
|
fn skipped(&self, loc: Location) -> bool {
|
||||||
self.work.skips.contains(&skip)
|
self.work.skips.contains(&loc)
|
||||||
|| self.page_insertions.skips.contains(&skip)
|
|| self.page_insertions.skips.contains(&loc)
|
||||||
|| self.column_insertions.skips.contains(&skip)
|
|| self.column_insertions.skips.contains(&loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The amount of width needed by insertions.
|
/// The amount of width needed by insertions.
|
||||||
@ -528,6 +520,29 @@ fn layout_footnote_separator(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lay out a footnote.
|
||||||
|
fn layout_footnote(
|
||||||
|
engine: &mut Engine,
|
||||||
|
config: &Config,
|
||||||
|
elem: &Packed<FootnoteElem>,
|
||||||
|
pod: Regions,
|
||||||
|
) -> SourceResult<Fragment> {
|
||||||
|
let loc = elem.location().unwrap();
|
||||||
|
layout_fragment(
|
||||||
|
engine,
|
||||||
|
&FootnoteEntry::new(elem.clone()).pack(),
|
||||||
|
Locator::synthesize(loc),
|
||||||
|
config.shared,
|
||||||
|
pod,
|
||||||
|
)
|
||||||
|
.map(|mut fragment| {
|
||||||
|
for frame in &mut fragment {
|
||||||
|
frame.set_parent(loc);
|
||||||
|
}
|
||||||
|
fragment
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// An additive list of insertions.
|
/// An additive list of insertions.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Insertions<'a, 'b> {
|
struct Insertions<'a, 'b> {
|
||||||
@ -538,7 +553,7 @@ struct Insertions<'a, 'b> {
|
|||||||
top_size: Abs,
|
top_size: Abs,
|
||||||
bottom_size: Abs,
|
bottom_size: Abs,
|
||||||
width: Abs,
|
width: Abs,
|
||||||
skips: Vec<Skip>,
|
skips: Vec<Location>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Insertions<'a, 'b> {
|
impl<'a, 'b> Insertions<'a, 'b> {
|
||||||
@ -836,8 +851,8 @@ fn find_in_frame_impl<T: NativeElement>(
|
|||||||
let y = y_offset + pos.y;
|
let y = y_offset + pos.y;
|
||||||
match item {
|
match item {
|
||||||
FrameItem::Group(group) => find_in_frame_impl(output, &group.frame, y),
|
FrameItem::Group(group) => find_in_frame_impl(output, &group.frame, y),
|
||||||
FrameItem::Tag(tag) if tag.kind() == TagKind::Start => {
|
FrameItem::Tag(Tag::Start(elem)) => {
|
||||||
if let Some(elem) = tag.elem().to_packed::<T>() {
|
if let Some(elem) = elem.to_packed::<T>() {
|
||||||
output.push((y, elem.clone()));
|
output.push((y, elem.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ pub fn distribute(composer: &mut Composer, regions: Regions) -> FlowResult<Frame
|
|||||||
};
|
};
|
||||||
let init = distributor.snapshot();
|
let init = distributor.snapshot();
|
||||||
let forced = match distributor.run() {
|
let forced = match distributor.run() {
|
||||||
Ok(()) => true,
|
Ok(()) => distributor.composer.work.done(),
|
||||||
Err(Stop::Finish(forced)) => forced,
|
Err(Stop::Finish(forced)) => forced,
|
||||||
Err(err) => return Err(err),
|
Err(err) => return Err(err),
|
||||||
};
|
};
|
||||||
|
@ -255,18 +255,7 @@ struct Work<'a, 'b> {
|
|||||||
/// Identifies floats and footnotes that can be skipped if visited because
|
/// Identifies floats and footnotes that can be skipped if visited because
|
||||||
/// they were already handled and incorporated as column or page level
|
/// they were already handled and incorporated as column or page level
|
||||||
/// insertions.
|
/// insertions.
|
||||||
skips: Rc<HashSet<Skip>>,
|
skips: Rc<HashSet<Location>>,
|
||||||
}
|
|
||||||
|
|
||||||
/// Identifies an element that that can be skipped if visited because it was
|
|
||||||
/// already processed.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
|
||||||
enum Skip {
|
|
||||||
/// Uniquely identifies a placed elements. We can't use a [`Location`]
|
|
||||||
/// because `PlaceElem` is not currently locatable.
|
|
||||||
Placed(usize),
|
|
||||||
/// Uniquely identifies a footnote.
|
|
||||||
Footnote(Location),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Work<'a, 'b> {
|
impl<'a, 'b> Work<'a, 'b> {
|
||||||
@ -304,7 +293,7 @@ impl<'a, 'b> Work<'a, 'b> {
|
|||||||
|
|
||||||
/// Add skipped floats and footnotes from the insertion areas to the skip
|
/// Add skipped floats and footnotes from the insertion areas to the skip
|
||||||
/// set.
|
/// set.
|
||||||
fn extend_skips(&mut self, skips: &[Skip]) {
|
fn extend_skips(&mut self, skips: &[Location]) {
|
||||||
if !skips.is_empty() {
|
if !skips.is_empty() {
|
||||||
Rc::make_mut(&mut self.skips).extend(skips.iter().copied());
|
Rc::make_mut(&mut self.skips).extend(skips.iter().copied());
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ use std::sync::Arc;
|
|||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::foundations::{cast, dict, Dict, Label, StyleChain, Value};
|
use crate::foundations::{cast, dict, Dict, Label, StyleChain, Value};
|
||||||
use crate::introspection::Tag;
|
use crate::introspection::{Location, Tag};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, Axes, Corners, FixedAlignment, HideElem, Length, Point, Rel, Sides, Size,
|
Abs, Axes, Corners, FixedAlignment, HideElem, Length, Point, Rel, Sides, Size,
|
||||||
Transform,
|
Transform,
|
||||||
@ -407,8 +407,21 @@ impl Frame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a label to the frame.
|
||||||
|
pub fn label(&mut self, label: Label) {
|
||||||
|
self.group(|g| g.label = Some(label));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a parent for the frame. As a result, all elements in the frame
|
||||||
|
/// become logically ordered immediately after the given location.
|
||||||
|
pub fn set_parent(&mut self, parent: Location) {
|
||||||
|
if !self.is_empty() {
|
||||||
|
self.group(|g| g.parent = Some(parent));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Wrap the frame's contents in a group and modify that group with `f`.
|
/// Wrap the frame's contents in a group and modify that group with `f`.
|
||||||
pub fn group<F>(&mut self, f: F)
|
fn group<F>(&mut self, f: F)
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut GroupItem),
|
F: FnOnce(&mut GroupItem),
|
||||||
{
|
{
|
||||||
@ -557,6 +570,9 @@ pub struct GroupItem {
|
|||||||
pub clip_path: Option<Path>,
|
pub clip_path: Option<Path>,
|
||||||
/// The group's label.
|
/// The group's label.
|
||||||
pub label: Option<Label>,
|
pub label: Option<Label>,
|
||||||
|
/// The group's logical parent. All elements in this group are logically
|
||||||
|
/// ordered immediately after the parent's start location.
|
||||||
|
pub parent: Option<Location>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GroupItem {
|
impl GroupItem {
|
||||||
@ -567,6 +583,7 @@ impl GroupItem {
|
|||||||
transform: Transform::identity(),
|
transform: Transform::identity(),
|
||||||
clip_path: None,
|
clip_path: None,
|
||||||
label: None,
|
label: None,
|
||||||
|
parent: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -575,38 +575,34 @@ fn add_par_line_marker(
|
|||||||
locator: &mut SplitLocator,
|
locator: &mut SplitLocator,
|
||||||
top: Abs,
|
top: Abs,
|
||||||
) {
|
) {
|
||||||
if let Some(numbering) = ParLine::numbering_in(styles) {
|
let Some(numbering) = ParLine::numbering_in(styles) else { return };
|
||||||
let number_margin = ParLine::number_margin_in(styles);
|
let margin = ParLine::number_margin_in(styles);
|
||||||
let number_align = ParLine::number_align_in(styles);
|
let align = ParLine::number_align_in(styles);
|
||||||
|
|
||||||
// Delay resolving the number clearance until line numbers are laid out
|
// Delay resolving the number clearance until line numbers are laid out to
|
||||||
// to avoid inconsistent spacing depending on varying font size.
|
// avoid inconsistent spacing depending on varying font size.
|
||||||
let number_clearance = ParLine::number_clearance_in(styles);
|
let clearance = ParLine::number_clearance_in(styles);
|
||||||
|
|
||||||
let mut par_line =
|
// Elements in tags must have a location for introspection to work. We do
|
||||||
ParLineMarker::new(numbering, number_align, number_margin, number_clearance)
|
// the work here instead of going through all of the realization process
|
||||||
.pack();
|
// just for this, given we don't need to actually place the marker as we
|
||||||
|
// manually search for it in the frame later (when building a root flow,
|
||||||
|
// where line numbers can be displayed), so we just need it to be in a tag
|
||||||
|
// and to be valid (to have a location).
|
||||||
|
let mut marker = ParLineMarker::new(numbering, align, margin, clearance).pack();
|
||||||
|
let key = crate::utils::hash128(&marker);
|
||||||
|
let loc = locator.next_location(engine.introspector, key);
|
||||||
|
marker.set_location(loc);
|
||||||
|
|
||||||
// Elements in tags must have a location for introspection to work.
|
// Create start and end tags through which we can search for this line's
|
||||||
// We do the work here instead of going through all of the realization
|
// marker later. The 'x' coordinate is not important, just the 'y'
|
||||||
// process just for this, given we don't need to actually place the
|
// coordinate, as that's what is used for line numbers. We will place the
|
||||||
// marker as we manually search for it in the frame later (when
|
// tags among other subframes in the line such that it is aligned with the
|
||||||
// building a root flow, where line numbers can be displayed), so we
|
// line's general baseline. However, the line number will still need to
|
||||||
// just need it to be in a tag and to be valid (to have a location).
|
// manually adjust its own 'y' position based on its own baseline.
|
||||||
let hash = crate::utils::hash128(&par_line);
|
let pos = Point::with_y(top);
|
||||||
let location = locator.next_location(engine.introspector, hash);
|
output.push(pos, FrameItem::Tag(Tag::Start(marker)));
|
||||||
par_line.set_location(location);
|
output.push(pos, FrameItem::Tag(Tag::End(loc, key)));
|
||||||
|
|
||||||
// Create a tag through which we can search for this line's marker
|
|
||||||
// later. Its 'x' coordinate is not important, just the 'y'
|
|
||||||
// coordinate, as that's what is used for line numbers. We will place
|
|
||||||
// the tag among other subframes in the line such that it is aligned
|
|
||||||
// with the line's general baseline. However, the line number will
|
|
||||||
// still need to manually adjust its own 'y' position based on its own
|
|
||||||
// baseline.
|
|
||||||
let tag = Tag::new(par_line, hash);
|
|
||||||
output.push(Point::with_y(top), FrameItem::Tag(tag));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How much a character should hang into the end margin.
|
/// How much a character should hang into the end margin.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use crate::foundations::StyleChain;
|
use crate::foundations::StyleChain;
|
||||||
use crate::introspection::{Locator, SplitLocator, TagElem, TagKind};
|
use crate::introspection::{Locator, SplitLocator, Tag, TagElem};
|
||||||
use crate::layout::{PagebreakElem, Parity};
|
use crate::layout::{PagebreakElem, Parity};
|
||||||
use crate::realize::Pair;
|
use crate::realize::Pair;
|
||||||
|
|
||||||
@ -121,7 +121,7 @@ pub fn collect<'a>(
|
|||||||
/// a pagebreak to after it. Returns the position right after the last
|
/// a pagebreak to after it. Returns the position right after the last
|
||||||
/// non-migrated tag.
|
/// non-migrated tag.
|
||||||
///
|
///
|
||||||
/// This is important because we want the positions of introspectible elements
|
/// This is important because we want the positions of introspectable elements
|
||||||
/// that technically started before a pagebreak, but have no visible content
|
/// that technically started before a pagebreak, but have no visible content
|
||||||
/// yet, to be after the pagebreak. A typical case where this happens is `show
|
/// yet, to be after the pagebreak. A typical case where this happens is `show
|
||||||
/// heading: it => pagebreak() + it`.
|
/// heading: it => pagebreak() + it`.
|
||||||
@ -136,9 +136,10 @@ fn migrate_unterminated_tags(children: &mut [Pair], mid: usize) -> usize {
|
|||||||
// are terminated).
|
// are terminated).
|
||||||
let excluded: HashSet<_> = children[start..mid]
|
let excluded: HashSet<_> = children[start..mid]
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(c, _)| c.to_packed::<TagElem>())
|
.filter_map(|(c, _)| match c.to_packed::<TagElem>()?.tag {
|
||||||
.filter(|elem| elem.tag.kind() == TagKind::End)
|
Tag::Start(_) => None,
|
||||||
.map(|elem| elem.tag.location())
|
Tag::End(loc, _) => Some(loc),
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// A key function that partitions the area of interest into three groups:
|
// A key function that partitions the area of interest into three groups:
|
||||||
|
@ -40,7 +40,7 @@ pub fn finalize(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add the "before" marginals. The order in which we push things here is
|
// Add the "before" marginals. The order in which we push things here is
|
||||||
// important as it affects the relative ordering of introspectible elements
|
// important as it affects the relative ordering of introspectable elements
|
||||||
// and thus how counters resolve.
|
// and thus how counters resolve.
|
||||||
if let Some(background) = background {
|
if let Some(background) = background {
|
||||||
frame.push_frame(Point::zero(), background);
|
frame.push_frame(Point::zero(), background);
|
||||||
|
@ -80,8 +80,9 @@ fn layout_document_impl(
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
let pages = layout_pages(&mut engine, &mut children, locator, styles)?;
|
let pages = layout_pages(&mut engine, &mut children, locator, styles)?;
|
||||||
|
let introspector = Introspector::new(&pages);
|
||||||
|
|
||||||
Ok(Document { pages, info, introspector: Introspector::default() })
|
Ok(Document { pages, info, introspector })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layouts the document's pages.
|
/// Layouts the document's pages.
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use crate::foundations::{elem, scope, Cast, Content, Smart};
|
use crate::foundations::{elem, scope, Cast, Content, Packed, Smart};
|
||||||
|
use crate::introspection::{Locatable, Unqueriable};
|
||||||
use crate::layout::{Alignment, Em, Length, Rel};
|
use crate::layout::{Alignment, Em, Length, Rel};
|
||||||
|
|
||||||
/// Places content relatively to its parent container.
|
/// Places content relatively to its parent container.
|
||||||
@ -65,7 +66,7 @@ use crate::layout::{Alignment, Em, Length, Rel};
|
|||||||
///
|
///
|
||||||
/// The zero-width weak spacing serves to discard spaces between the function
|
/// The zero-width weak spacing serves to discard spaces between the function
|
||||||
/// call and the next word.
|
/// call and the next word.
|
||||||
#[elem(scope)]
|
#[elem(scope, Locatable, Unqueriable)]
|
||||||
pub struct PlaceElem {
|
pub struct PlaceElem {
|
||||||
/// Relative to which position in the parent container to place the content.
|
/// Relative to which position in the parent container to place the content.
|
||||||
///
|
///
|
||||||
@ -163,6 +164,10 @@ pub struct PlaceElem {
|
|||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `PlaceElem` must be locatable to support logical ordering of floats, but I
|
||||||
|
/// do not want to expose `query(place)` for now.
|
||||||
|
impl Unqueriable for Packed<PlaceElem> {}
|
||||||
|
|
||||||
#[scope]
|
#[scope]
|
||||||
impl PlaceElem {
|
impl PlaceElem {
|
||||||
#[elem]
|
#[elem]
|
||||||
|
@ -152,7 +152,6 @@ fn compile_impl(
|
|||||||
|
|
||||||
// Layout!
|
// Layout!
|
||||||
document = crate::layout::layout_document(&mut engine, &content, styles)?;
|
document = crate::layout::layout_document(&mut engine, &content, styles)?;
|
||||||
document.introspector.rebuild(&document.pages);
|
|
||||||
iter += 1;
|
iter += 1;
|
||||||
|
|
||||||
if timed!("check stabilized", document.introspector.validate(&constraint)) {
|
if timed!("check stabilized", document.introspector.validate(&constraint)) {
|
||||||
|
@ -18,7 +18,7 @@ use crate::foundations::{
|
|||||||
SequenceElem, Show, ShowSet, Style, StyleChain, StyleVec, StyledElem, Styles,
|
SequenceElem, Show, ShowSet, Style, StyleChain, StyleVec, StyledElem, Styles,
|
||||||
Synthesize, Transformation,
|
Synthesize, Transformation,
|
||||||
};
|
};
|
||||||
use crate::introspection::{Locatable, SplitLocator, Tag, TagElem, TagKind};
|
use crate::introspection::{Locatable, SplitLocator, Tag, TagElem};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
AlignElem, BoxElem, HElem, InlineElem, PageElem, PagebreakElem, VElem,
|
AlignElem, BoxElem, HElem, InlineElem, PageElem, PagebreakElem, VElem,
|
||||||
};
|
};
|
||||||
@ -352,9 +352,9 @@ fn visit_show_rules<'a>(
|
|||||||
|
|
||||||
// If the element isn't yet prepared (we're seeing it for the first time),
|
// If the element isn't yet prepared (we're seeing it for the first time),
|
||||||
// prepare it.
|
// prepare it.
|
||||||
let mut tag = None;
|
let mut tags = None;
|
||||||
if !prepared {
|
if !prepared {
|
||||||
tag = prepare(s.engine, s.locator, output.to_mut(), &mut map, styles)?;
|
tags = prepare(s.engine, s.locator, output.to_mut(), &mut map, styles)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply a show rule step, if there is one.
|
// Apply a show rule step, if there is one.
|
||||||
@ -393,9 +393,9 @@ fn visit_show_rules<'a>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Push start tag.
|
// Push start tag.
|
||||||
if let Some(tag) = &tag {
|
let (start, end) = tags.unzip();
|
||||||
let start_tag = TagElem::packed(tag.clone());
|
if let Some(tag) = start {
|
||||||
visit(s, s.store(start_tag), styles)?;
|
visit(s, s.store(TagElem::packed(tag)), styles)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let prev_outside = s.outside;
|
let prev_outside = s.outside;
|
||||||
@ -409,9 +409,8 @@ fn visit_show_rules<'a>(
|
|||||||
s.engine.route.decrease();
|
s.engine.route.decrease();
|
||||||
|
|
||||||
// Push end tag.
|
// Push end tag.
|
||||||
if let Some(tag) = tag {
|
if let Some(tag) = end {
|
||||||
let end_tag = TagElem::packed(tag.with_kind(TagKind::End));
|
visit(s, s.store(TagElem::packed(tag)), styles)?;
|
||||||
visit(s, s.store(end_tag), styles)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
@ -517,21 +516,19 @@ fn prepare(
|
|||||||
target: &mut Content,
|
target: &mut Content,
|
||||||
map: &mut Styles,
|
map: &mut Styles,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Option<Tag>> {
|
) -> SourceResult<Option<(Tag, Tag)>> {
|
||||||
// Generate a location for the element, which uniquely identifies it in
|
// Generate a location for the element, which uniquely identifies it in
|
||||||
// the document. This has some overhead, so we only do it for elements
|
// the document. This has some overhead, so we only do it for elements
|
||||||
// that are explicitly marked as locatable and labelled elements.
|
// that are explicitly marked as locatable and labelled elements.
|
||||||
//
|
//
|
||||||
// The element could already have a location even if it is not prepared
|
// The element could already have a location even if it is not prepared
|
||||||
// when it stems from a query.
|
// when it stems from a query.
|
||||||
let mut key = None;
|
let key = crate::utils::hash128(&target);
|
||||||
if target.location().is_some() {
|
if target.location().is_none()
|
||||||
key = Some(crate::utils::hash128(&target));
|
&& (target.can::<dyn Locatable>() || target.label().is_some())
|
||||||
} else if target.can::<dyn Locatable>() || target.label().is_some() {
|
{
|
||||||
let hash = crate::utils::hash128(&target);
|
let loc = locator.next_location(engine.introspector, key);
|
||||||
let location = locator.next_location(engine.introspector, hash);
|
target.set_location(loc);
|
||||||
target.set_location(location);
|
|
||||||
key = Some(hash);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply built-in show-set rules. User-defined show-set rules are already
|
// Apply built-in show-set rules. User-defined show-set rules are already
|
||||||
@ -551,18 +548,20 @@ fn prepare(
|
|||||||
// available in rules.
|
// available in rules.
|
||||||
target.materialize(styles.chain(map));
|
target.materialize(styles.chain(map));
|
||||||
|
|
||||||
// If the element is locatable, create a tag element to be able to find the
|
// If the element is locatable, create start and end tags to be able to find
|
||||||
// element in the frames after layout. Do this after synthesis and
|
// the element in the frames after layout. Do this after synthesis and
|
||||||
// materialization, so that it includes the synthesized fields. Do it before
|
// materialization, so that it includes the synthesized fields. Do it before
|
||||||
// marking as prepared so that show-set rules will apply to this element
|
// marking as prepared so that show-set rules will apply to this element
|
||||||
// when queried.
|
// when queried.
|
||||||
let tag = key.map(|key| Tag::new(target.clone(), key));
|
let tags = target
|
||||||
|
.location()
|
||||||
|
.map(|loc| (Tag::Start(target.clone()), Tag::End(loc, key)));
|
||||||
|
|
||||||
// Ensure that this preparation only runs once by marking the element as
|
// Ensure that this preparation only runs once by marking the element as
|
||||||
// prepared.
|
// prepared.
|
||||||
target.mark_prepared();
|
target.mark_prepared();
|
||||||
|
|
||||||
Ok(tag)
|
Ok(tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles a styled element.
|
/// Handles a styled element.
|
||||||
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
BIN
tests/ref/footnote-nested-break-across-pages.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
BIN
tests/ref/issue-4966-figure-float-counter.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 683 B After Width: | Height: | Size: 670 B |
@ -9,13 +9,16 @@ A#footnote[A] \
|
|||||||
A #footnote[A]
|
A #footnote[A]
|
||||||
|
|
||||||
--- footnote-nested ---
|
--- footnote-nested ---
|
||||||
// Currently, numbers a bit out of order if a nested footnote ends up in the
|
|
||||||
// same frame as another one. :(
|
|
||||||
First \
|
First \
|
||||||
Second #footnote[A, #footnote[B, #footnote[C]]]
|
Second #footnote[A, #footnote[B, #footnote[C]]]
|
||||||
Third #footnote[D, #footnote[E]] \
|
Third #footnote[D, #footnote[E]] \
|
||||||
Fourth #footnote[F]
|
Fourth #footnote[F]
|
||||||
|
|
||||||
|
--- footnote-nested-break-across-pages ---
|
||||||
|
#set page(height: 80pt)
|
||||||
|
A #footnote([I: ] + lines(6) + footnote[II])
|
||||||
|
B #footnote[III]
|
||||||
|
|
||||||
--- footnote-entry ---
|
--- footnote-entry ---
|
||||||
// Test customization.
|
// Test customization.
|
||||||
#show footnote: set text(red)
|
#show footnote: set text(red)
|
||||||
|
@ -165,7 +165,6 @@ C
|
|||||||
place(auto, float: true, block(width: 100%, height: 100%, fill: aqua))
|
place(auto, float: true, block(width: 100%, height: 100%, fill: aqua))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
--- place-float-column-align-auto ---
|
--- place-float-column-align-auto ---
|
||||||
#set page(height: 150pt, columns: 2)
|
#set page(height: 150pt, columns: 2)
|
||||||
#set place(auto, float: true, clearance: 10pt)
|
#set place(auto, float: true, clearance: 10pt)
|
||||||
|
@ -267,3 +267,25 @@ HI#footnote.entry(clearance: 2.5em)[There]
|
|||||||
// Test that figure caption separator is synthesized correctly.
|
// Test that figure caption separator is synthesized correctly.
|
||||||
#show figure.caption: c => test(c.separator, [#": "])
|
#show figure.caption: c => test(c.separator, [#": "])
|
||||||
#figure(table[], caption: [This is a test caption])
|
#figure(table[], caption: [This is a test caption])
|
||||||
|
|
||||||
|
--- issue-4966-figure-float-counter ---
|
||||||
|
#let c = context counter(figure.where(kind: image)).display()
|
||||||
|
#set align(center)
|
||||||
|
|
||||||
|
#c
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
square(c),
|
||||||
|
placement: bottom,
|
||||||
|
caption: [A]
|
||||||
|
)
|
||||||
|
|
||||||
|
#c
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
circle(c),
|
||||||
|
placement: top,
|
||||||
|
caption: [B]
|
||||||
|
)
|
||||||
|
|
||||||
|
#c
|
||||||
|