1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
//! Schema-field geometry, queried from a compiled
//! [`LiveSession`](crate::LiveSession) via
//! [`regions`](crate::LiveSession::regions) and
//! [`field_at`](crate::LiveSession::field_at).
//!
//! A region ties a rectangle on the rendered page to the **quill schema field**
//! that produced it — the address the document author already uses to refer to
//! that field (the same address the Typst plate reads as `data.*` and the
//! pdfform binder resolves against `compile_data`). The two directions a
//! consumer navigates get two queries: `regions` answers *field → rectangle*
//! (scroll to / highlight the focused field), `field_at` answers *point →
//! field* (click a rendered field → focus it in the editor).
//!
//! Three producers feed regions, all keyed on the schema path:
//!
//! - **Content fields** (a richtext body, a `richtext[]` element, a card's
//! content field) are tracked by the **spans** their glyphs carry: the
//! backend evaluates each one's value at its own generated call site and
//! records the site's byte window, so every glyph of that content resolves
//! back to its field — through *any* placement context, including a package
//! that rebuilds the content (a `show`-rule pass that captures paragraphs
//! into a state buffer and re-emits them), because the origin rides the
//! glyph, not a sibling marker a rebuild could drop. A field that is blank
//! or draws nothing (an empty or whitespace-only body) has no inked extent
//! to bound and surfaces no region — present-but-empty is not the same as
//! placed.
//! - **Direct scalar references** — every `data.<field>` / `data.at("field")`
//! expression in the plate is its own tracked site: the interpolated
//! value's glyphs carry a span at or around that reference expression. A
//! scalar shown in both a header and a footer surfaces both sites, because
//! two source expressions are two origins; a reference wrapped in an
//! expression (`#upper(data.subject)`) attributes the whole expression's
//! ink to the field as long as it is the expression's only reference. Not
//! tracked: an expression mixing several fields (`data.from + ", " + rank`
//! has no single owner), a value laundered through an intermediate binding
//! (`#let s = data.x` … `#s`), and card scalars read from the per-card
//! loop variable (`card.from` is *one* expression site shared by every card
//! instance — span data holds no per-instance identity; bind a widget for
//! those).
//! - **Form-field widgets** carry a schema path explicitly: pdfform binds it
//! from the form mapping; a Typst `form-field` binds it from its `field:`
//! argument. A widget that binds none produces **no** region — its backend
//! identifier (the `/T` widget name) is not a schema address, so there is
//! nothing for a consumer to route to. Only schema-addressable fields surface
//! a region.
//!
//! **First placement only.** A content value placed at two sites surfaces one
//! region set — its first placement's — because span data cannot distinguish
//! "package chrome interrupting one placement" from "a second placement of
//! the same value", and a spanning union would claim the ink between them.
//! The first placement is one region per page it touches, in page order, so
//! highlighting covers continuation pages — page marginals (headers, footers,
//! page numbers) between one page's body and the next's do not end it, only a
//! same-page interruption does: foreign ink within a page (a rebuild's
//! numbering chrome) shrinks the region to the placement's true start rather
//! than lying about extent. `field` is still not unique in the
//! result: page fragments, several scalar reference sites, or tracked content
//! plus a bound widget each surface independently.
//! [`LiveSession::regions`](crate::LiveSession::regions) passes the backend's
//! entries through; consumers group by `field`. Later placements stay
//! reachable point-wise: [`field_at`](crate::LiveSession::field_at) resolves
//! a click on *any* placement, since one concrete point identifies one drawn
//! item whose origin is unambiguous.
//!
//! Regions are primarily a session-level query: the geometry is a property of
//! the current compile, re-read from the session per edit without producing
//! any byte artifact — the interactive-preview path (overlays over a
//! `paint`-ed canvas) reads it that way. A one-shot byte render carries the
//! same sidecar only on request ([`RenderOptions::regions`](crate::RenderOptions))
//! for consumers without a live session (static SVG overlays, PDF
//! post-processing, CI coverage probes). Either way regions are an overlay
//! sidecar, never a compositing input: every canvas backend hands back a
//! complete page raster, so nothing about the picture depends on reading a
//! region. Empty for backends that place no schema fields.
/// One schema field placement's extent on a rendered page.
///
/// `rect` is `[x0, y0, x1, y1]` in PDF points with a **bottom-left** origin —
/// the same final geometry the stamp spine writes to the widget `/Rect`, so the
/// region and the rendered field describe the identical box.
///
/// `field` is **not** unique within the `Vec` that
/// [`LiveSession::regions`](crate::LiveSession::regions) returns: a content
/// field breaks into one entry **per segment** (paragraph, heading, whole code
/// fence) and per page each segment touches, a scalar referenced at several
/// plate sites yields one per site, and tracked content plus a bound widget
/// yields both. Consumers group by `field`; every entry routes to that field.
/// The whole-field box is **derived** — the union of a page's `span`-bearing
/// segment rects, so inter-paragraph whitespace stays uncovered (#829); the
/// [`field_boxes`] helper (and
/// [`LiveSession::field_boxes`](crate::LiveSession::field_boxes)) owns that
/// union so consumers need not reimplement it.
/// The whole-field highlight boxes for `field`, derived from a region set: one
/// union rect per page, over that field's **`span`-bearing** (content) regions.
///
/// This owns the subtle part [`regions`](crate::LiveSession::regions) leaves to
/// consumers — filter by field, keep only the segment rects that carry a `span`,
/// union per page, inherit first-placement-only from the input — so a
/// "highlight the focused field" consumer never reimplements it and cannot
/// reintroduce the field-level union the #829 disjointness invariant exists to
/// prevent (the input is already striped; this unions the *bounding* box per
/// page, so inter-paragraph whitespace still is not a separate box but the
/// derived rect does bound it). Pass the output of
/// [`LiveSession::regions`](crate::LiveSession::regions) (or a one-shot
/// [`RenderOptions::regions`](crate::RenderOptions) sidecar); the convenience
/// [`LiveSession::field_boxes`](crate::LiveSession::field_boxes) reads the
/// session's own.
///
/// **Content only.** A scalar-reference site or a widget carries no `span`
/// ([`RenderedRegion::span`] is `None`), so a field placed *only* as a scalar
/// reference or a bound widget yields an empty result here — its highlight box
/// is a single region's `rect`, read straight from the region set with no
/// derivation. Each returned region carries the union `span`
/// (`[min start, max end)` over the page's contributing segments);
/// `page`-ascending.
/// How precisely a [`ContentHit::pos`] resolved — the marker a caret UI reads to
/// decide whether to trust the offset. The value is never sub-cluster; the two
/// variants distinguish the finest this API offers from the segment floor it
/// degrades to.
/// A resolved point → content position: the schema field a click landed in and
/// the USV offset into that field's `Content`. The forward
/// [`position_at`](crate::LiveSession::position_at) direction, paired with
/// [`locate`](crate::LiveSession::locate) (content position → caret rect).
///
/// `pos` is **cluster-exact, not sub-character**: a hit inside a char that
/// escaped to several generated bytes (`*`→`\*`, `你`→3, the `//`→`\/\/`
/// coupling) floors to that cluster's first content char. A click on
/// origin-less ink (list markers, numbering, a multi-line code fence's interior
/// — spans that resolve to no single run) degrades to the containing segment's
/// content start rather than a wrong finer position, and a click off all content
/// ink resolves to nothing. [`granularity`](Self::granularity) reports which of
/// those two happened, so a caret UI need not guess.