typst_layout/
introspect.rs1use std::fmt::{self, Debug, Formatter};
2use std::num::NonZeroUsize;
3
4use ecow::{EcoString, EcoVec};
5
6use rustc_hash::FxHashSet;
7use typst_library::diag::StrResult;
8use typst_library::foundations::{Content, Label, Selector};
9use typst_library::introspection::{
10 DocumentPosition, ElementIntrospector, ElementIntrospectorBuilder, Introspector,
11 Location, PagedPosition,
12};
13use typst_library::layout::{Frame, FrameItem, Point, Transform};
14use typst_library::model::{Destination, Numbering};
15use typst_syntax::VirtualPath;
16use typst_utils::NonZeroExt;
17
18use crate::Page;
19
20#[derive(Clone)]
22pub struct PagedIntrospector {
23 elements: ElementIntrospector<PagedPosition>,
25 frame_link_targets: FxHashSet<Location>,
27 pages: NonZeroUsize,
29 page_numberings: Vec<Option<Numbering>>,
31 page_supplements: Vec<Content>,
33}
34
35impl PagedIntrospector {
36 #[typst_macros::time(name = "introspect pages")]
38 pub fn new(pages: &[Page]) -> PagedIntrospector {
39 let mut builder = PagedIntrospectorBuilder::default();
40 let mut page_numberings = Vec::with_capacity(pages.len());
41 let mut page_supplements = Vec::with_capacity(pages.len());
42
43 for (i, page) in pages.iter().enumerate() {
45 let nr = NonZeroUsize::new(1 + i).unwrap();
46 page_numberings.push(page.numbering.clone());
47 page_supplements.push(page.supplement.clone());
48 builder.discover_frame(&page.frame, Transform::identity(), &mut |point| {
49 PagedPosition { page: nr, point }
50 });
51 }
52
53 builder.finish(
54 NonZeroUsize::new(pages.len()).unwrap_or(NonZeroUsize::ONE),
55 page_numberings,
56 page_supplements,
57 )
58 }
59
60 pub fn position(&self, location: Location) -> Option<PagedPosition> {
62 self.elements.position(location).copied()
63 }
64
65 pub fn elements(&self) -> &ElementIntrospector<PagedPosition> {
67 &self.elements
68 }
69
70 pub fn frame_link_targets(&self) -> &FxHashSet<Location> {
73 &self.frame_link_targets
74 }
75}
76
77impl Introspector for PagedIntrospector {
78 fn query(&self, selector: &Selector) -> EcoVec<Content> {
79 self.elements.query(selector)
80 }
81
82 fn query_first(&self, selector: &Selector) -> Option<Content> {
83 self.elements.query_first(selector)
84 }
85
86 fn query_unique(&self, selector: &Selector) -> StrResult<Content> {
87 self.elements.query_unique(selector)
88 }
89
90 fn query_label(&self, label: Label) -> StrResult<&Content> {
91 self.elements.query_label(label)
92 }
93
94 fn query_labelled(&self) -> EcoVec<Content> {
95 self.elements.query_labelled()
96 }
97
98 fn query_count_before(&self, selector: &Selector, end: Location) -> usize {
99 self.elements.query_count_before(selector, end)
100 }
101
102 fn label_count(&self, label: Label) -> usize {
103 self.elements.label_count(label)
104 }
105
106 fn locator(&self, key: u128, base: Location) -> Option<Location> {
107 self.elements.locator(key, base)
108 }
109
110 fn pages(&self, _: Location) -> Option<NonZeroUsize> {
111 Some(self.pages)
112 }
113
114 fn page(&self, location: Location) -> Option<NonZeroUsize> {
115 self.elements.position(location).map(|pos| pos.page)
116 }
117
118 fn position(&self, location: Location) -> Option<DocumentPosition> {
119 self.elements.position(location).copied().map(DocumentPosition::Paged)
120 }
121
122 fn page_numbering(&self, location: Location) -> Option<&Numbering> {
123 let page = self.page(location)?;
124 self.page_numberings
125 .get(page.get() - 1)
126 .and_then(|slot| slot.as_ref())
127 }
128
129 fn page_supplement(&self, location: Location) -> Option<&Content> {
130 let page = self.page(location)?;
131 self.page_supplements.get(page.get() - 1)
132 }
133
134 fn anchor(&self, _: Location) -> Option<&EcoString> {
135 None
136 }
137
138 fn document(&self, _: Location) -> Option<Location> {
139 None
140 }
141
142 fn path(&self, _: Location) -> Option<&VirtualPath> {
143 None
144 }
145}
146
147impl Debug for PagedIntrospector {
148 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
149 f.pad("PagedIntrospector(..)")
150 }
151}
152
153#[derive(Default)]
155struct PagedIntrospectorBuilder {
156 elements: ElementIntrospectorBuilder<PagedPosition>,
157 frame_link_targets: FxHashSet<Location>,
158}
159
160impl PagedIntrospectorBuilder {
161 fn finish(
164 self,
165 pages: NonZeroUsize,
166 page_numberings: Vec<Option<Numbering>>,
167 page_supplements: Vec<Content>,
168 ) -> PagedIntrospector {
169 PagedIntrospector {
170 elements: self.elements.finalize(),
171 frame_link_targets: self.frame_link_targets,
172 pages,
173 page_numberings,
174 page_supplements,
175 }
176 }
177
178 fn discover_frame<F>(&mut self, frame: &Frame, ts: Transform, to_pos: &mut F)
180 where
181 F: FnMut(Point) -> PagedPosition,
182 {
183 for (pos, item) in frame.items() {
184 match item {
185 FrameItem::Tag(tag) => {
186 self.elements.discover_tag(tag, to_pos(pos.transform(ts)));
187 }
188 FrameItem::Group(group) => {
189 let ts = ts
190 .pre_concat(Transform::translate(pos.x, pos.y))
191 .pre_concat(group.transform);
192
193 if let Some(parent) = group.parent {
194 self.elements.start_insertion();
195 self.discover_frame(&group.frame, ts, to_pos);
196 self.elements.end_insertion(parent.location);
197 } else {
198 self.discover_frame(&group.frame, ts, to_pos);
199 }
200 }
201 FrameItem::Link(dest, _) => {
202 if let Destination::Location(loc) = dest {
203 self.frame_link_targets.insert(*loc);
204 }
205 }
206 FrameItem::Text(..) | FrameItem::Shape(..) | FrameItem::Image(..) => {}
207 }
208 }
209 }
210}