@embedpdf/core-annotation 3.0.0-next.13 → 3.0.0-next.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,71 +1,391 @@
1
- import { AnnotationDTO, AnnotationFlags, AnnotationFlags as AnnotationFlags$1, AnnotationRef, BlendMode, BlendMode as BlendMode$1, CaretIntent, InkIntent, LineEnding, LineEndings, NO_ANNOTATION_FLAGS, PdfLinkTarget, PdfPoint, PdfRect, RichTextDocumentInput, RichTextParagraph, RichTextRunStyle, StrikeoutIntent } from "@embedpdf/engine-core/runtime";
2
- import { PageRotation, Point, Rect as Rect$1, Size, TextQuad, TextQuad as TextQuad$1 } from "@embedpdf/core-geometry";
3
- import { PageObjectNumber } from "@embedpdf/core";
4
- //#region src/flags.d.ts
5
- /** The `/F` every freshly DRAWN annotation starts with: `print` set (Acrobat
6
- * parity — without it the annotation silently disappears when printed). */
7
- declare const DRAWN_FLAGS: AnnotationFlags;
8
- /** All ten flags, for iteration/equality — kept in the primitives' order. */
9
- declare const FLAG_KEYS: ReadonlyArray<keyof AnnotationFlags>;
10
- declare const flagsEqual: (a: AnnotationFlags, b: AnnotationFlags) => boolean;
11
- /** Merge a partial write over the current flags. */
12
- declare const mergeFlags: (base: AnnotationFlags, patch: Partial<AnnotationFlags>) => AnnotationFlags;
1
+ import { Annotation, AnnotationBorderStyle, AnnotationDraft, AnnotationFlags, AnnotationFlags as AnnotationFlags$1, AnnotationPatch, AnnotationRef, AnnotationSubtype, BlendMode, BlendMode as BlendMode$1, Color, FaceRequest, FileAttachmentIcon, FontHandle, InkIntent, LineEnding, LineEnding as LineEnding$1, LineEndings, LineEndings as LineEndings$1, NO_ANNOTATION_FLAGS, NoteIcon, PageRef, PdfLinkTarget, PdfLinkTargetWritable, RichTextBody, RichTextDocument, RichTextDocumentInput, RichTextParagraph, RichTextRunStyle, STANDARD_FACES, WidgetAppearance } from "@embedpdf/engine-core/runtime";
2
+ import { PageRotation, Point, Point as Point$1, Quad, Quad as Quad$1, QuadRing, Rect as Rect$1, Size } from "@embedpdf/core-geometry";
3
+ //#region src/editable.d.ts
4
+ /** The intents a measurement goes by in {@link EditableKinds}. */
5
+ type MeasurementIntent = 'line-dimension' | 'polyline-dimension' | 'polygon-dimension';
6
+ /** What {@link EditableKinds} names an annotation by: {@link editableKindOf}. */
7
+ type EditableKind = AnnotationSubtype | MeasurementIntent;
13
8
  /**
14
- * On-screen visibility. `hidden` beats everything; `noView` hides unless
15
- * `toggleNoView` and the annotation is ENGAGED — the spec says hover/selection,
16
- * and v1 uses selection (the model tracks no hover).
9
+ * Which annotations people may change by hand: `true`, every one; `false`,
10
+ * none; a list, the annotations of the kinds it names, each by its subtype,
11
+ * or a measurement by its intent (so measurements and the shapes they're
12
+ * drawn as are told apart).
17
13
  */
18
- declare const viewable: (f: AnnotationFlags, engaged?: boolean) => boolean;
19
- /** Any pointer interaction (click-to-select, hover). ReadOnly kills it. */
20
- declare const interactive: (f: AnnotationFlags) => boolean;
21
- /** The subset of an Annot these predicates read — keeps them testable bare. */
22
- interface FlagBearer {
23
- subtype: string;
24
- flags: AnnotationFlags;
25
- /** Session authority projected at ingest (permissions.md). Absent =
26
- * unstamped (drafts, wildcard local engines, tests) = allowed. */
27
- authority?: {
28
- update: boolean;
29
- delete: boolean;
14
+ type EditableKinds = boolean | readonly EditableKind[];
15
+ /** The name {@link EditableKinds} knows an annotation by: a measurement's intent, else its subtype. */
16
+ declare function editableKindOf(annotation: Annotation): EditableKind;
17
+ /** Whether the session's rule lets people change this annotation by hand. */
18
+ declare function isEditableByHand(model: Pick<Session, 'editable'>, annotation: Annotation): boolean;
19
+ /** Whether people may move, resize, turn, reshape or restyle the record by hand now. */
20
+ declare const isTransformableByHand: (model: Pick<Session, "editable">, record: ModelAnnotation) => boolean;
21
+ /** Whether people may delete the record by hand now. */
22
+ declare const isDeletableByHand: (model: Pick<Session, "editable">, record: ModelAnnotation) => boolean;
23
+ /** Whether people may edit the record's text in place now. */
24
+ declare const isContentsEditableByHand: (model: Pick<Session, "editable">, record: ModelAnnotation) => boolean;
25
+ //#endregion
26
+ //#region src/painted.d.ts
27
+ /** One piece of what an annotation paints, in page space. */
28
+ type PaintedPiece =
29
+ /** Ink along a path, `halfWidth` either side of it; `closed` runs it back to the first point. */
30
+ {
31
+ readonly kind: 'stroke';
32
+ readonly points: readonly Point[];
33
+ readonly closed: boolean;
34
+ readonly halfWidth: number;
35
+ } |
36
+ /** A filled polygon (even-odd). A margin doesn't grow it: ink around it does. */
37
+ {
38
+ readonly kind: 'area';
39
+ readonly ring: readonly Point[];
40
+ } |
41
+ /**
42
+ * An ellipse turned `rotation` degrees clockwise about `center`: its ink,
43
+ * `halfWidth` either side of it, and its inside when `filled`. The ink's
44
+ * width is measured in radii along the smaller radius, so a long ellipse's
45
+ * ink is a little wider at its ends.
46
+ */
47
+ {
48
+ readonly kind: 'oval';
49
+ readonly center: Point;
50
+ readonly rx: number;
51
+ readonly ry: number;
52
+ readonly rotation: number;
53
+ readonly halfWidth: number;
54
+ readonly filled: boolean;
55
+ };
56
+ /**
57
+ * A solid body: anything inside the ring, and anything within a margin of
58
+ * its edge (a rim of ink with no width). A text box, a caret and a
59
+ * measurement's caption are hit this way.
60
+ */
61
+ declare const bodyPieces: (ring: readonly Point[]) => PaintedPiece[];
62
+ /** Is `point` within `margin` of what the pieces paint? */
63
+ declare const paintedNear: (pieces: readonly PaintedPiece[], point: Point, margin: number) => boolean;
64
+ /** Does the rect touch what the pieces paint: cross its ink, or lie on its fill? */
65
+ declare const paintedTouches: (pieces: readonly PaintedPiece[], rect: Rect) => boolean;
66
+ //#endregion
67
+ //#region src/measurement-shape.d.ts
68
+ type ShapeAnnotation = Extract<Annotation, {
69
+ subtype: 'polygon' | 'polyline';
70
+ }>;
71
+ /**
72
+ * A perimeter's or area's measurement: the engine's own fields for its scale,
73
+ * whether its caption shows, and its label (`contents`, which the engine
74
+ * works out). Where the caption sits is the shape's `captionCenter`.
75
+ */
76
+ type ShapeMeasurementAppearance = {
77
+ intent: 'polyline-dimension' | 'polygon-dimension';
78
+ } & Pick<ShapeAnnotation, 'measure' | 'captionEnabled' | 'contents'>;
79
+ interface ShapeMeasurementLayout {
80
+ caption: DistanceCaptionLayout | null;
81
+ visualBounds: Rect;
82
+ selectionPoints: Point[];
83
+ }
84
+ declare function shapeMeasurementReadout(geometry: Shape, appearance: ShapeMeasurementAppearance): import("@embedpdf/engine-core/runtime").MeasurementReadout | import("@embedpdf/engine-core/runtime").MeasurementUnavailable;
85
+ declare function shapeMeasurementLabel(geometry: Shape, appearance: ShapeMeasurementAppearance): string;
86
+ /** Match the native shape-caption anchor, including an interior fallback for concave areas. */
87
+ declare function automaticShapeCaptionCenter(points: readonly Point[], closed: boolean): Point;
88
+ declare function shapeMeasurementLayout(geometry: Shape, appearance: ShapeMeasurementAppearance, style: Style): ShapeMeasurementLayout | null;
89
+ declare function measurementLayout(geometry: Shape, appearance: MeasurementAppearance, style: Style): ShapeMeasurementLayout | null;
90
+ declare function measurementSelectionQuad(geometry: Shape, appearance: MeasurementAppearance, style: Style): QuadRing;
91
+ /**
92
+ * The caption dragged by `delta`: a distance's caption offset changes (its
93
+ * measurement), a perimeter's or area's caption center (its shape, kept
94
+ * upright with the vertices).
95
+ */
96
+ declare function moveMeasurementCaption(geometry: Shape, appearance: MeasurementAppearance, delta: Point, style: Style): {
97
+ geometry: Shape;
98
+ measure: MeasurementAppearance;
99
+ };
100
+ //#endregion
101
+ //#region src/measurement.d.ts
102
+ type MeasurementAppearance = DistanceAppearance | ShapeMeasurementAppearance;
103
+ type LineAnnotation = Extract<Annotation, {
104
+ subtype: 'line';
105
+ }>;
106
+ /**
107
+ * A distance's measurement: the engine's own fields for its scale, its
108
+ * caption (on or off, its position, and its offset in the line's own axes),
109
+ * its leader and its label (`contents`, which the engine works out).
110
+ */
111
+ type DistanceAppearance = {
112
+ intent: 'line-dimension';
113
+ } & Pick<LineAnnotation, 'measure' | 'captionEnabled' | 'captionPosition' | 'captionOffset' | 'leader' | 'contents'>;
114
+ /**
115
+ * The annotation's measurement, when it is one: a line measuring a
116
+ * distance, a polyline a perimeter, a polygon an area, as its intent says.
117
+ * Its measurement fields, read off it for drawing and hit-testing.
118
+ */
119
+ declare function measurementOf(annotation: Annotation): MeasurementAppearance | undefined;
120
+ /**
121
+ * What a new measurement states beside its points: its intent, its scale
122
+ * (one the engine can write), its caption and leader as it has them, and its
123
+ * subject. Its label is the engine's: it works it out from the points and
124
+ * the scale.
125
+ */
126
+ declare function measurementDraftFields(appearance: MeasurementAppearance): FieldValues;
127
+ interface DistanceSegment {
128
+ from: Point;
129
+ to: Point;
130
+ }
131
+ interface DistanceCaptionLayout {
132
+ text: string;
133
+ center: Point;
134
+ along: Point;
135
+ normal: Point;
136
+ width: number;
137
+ height: number;
138
+ bounds: QuadRing;
139
+ }
140
+ /** One layout drives the preview, selection, handles, and hit testing. */
141
+ interface DistanceLayout {
142
+ along: Point;
143
+ normal: Point;
144
+ length: number;
145
+ measuredStart: Point;
146
+ measuredEnd: Point;
147
+ dimensionStart: Point;
148
+ dimensionEnd: Point;
149
+ dimensionSegments: DistanceSegment[];
150
+ leaderSegments: DistanceSegment[];
151
+ captionConnector: DistanceSegment[];
152
+ caption: DistanceCaptionLayout | null;
153
+ arrowPlacement: 'inside' | 'outside';
154
+ endings: RenderNode[];
155
+ selectionPoints: Point[];
156
+ visualBounds: Rect;
157
+ }
158
+ declare function distanceLabel(geometry: Shape, appearance: DistanceAppearance): string;
159
+ declare function distanceLayout(geometry: Shape, appearance: DistanceAppearance, strokeWidth: number): DistanceLayout | null;
160
+ declare function expandDistanceBounds(bounds: Rect, padding: number): Rect;
161
+ declare function distanceSelectionQuad(geometry: Shape, appearance: DistanceAppearance, strokeWidth: number): QuadRing;
162
+ declare function distanceHandles(layout: DistanceLayout): Handle[];
163
+ declare function distanceCaptionHit(layout: {
164
+ caption: DistanceCaptionLayout | null;
165
+ }, point: Point, margin?: number): boolean;
166
+ /** What a measurement's caption paints: its box, grabbed anywhere in it and near its edge. */
167
+ declare const captionPainted: (caption: DistanceCaptionLayout | null) => PaintedPiece[];
168
+ /**
169
+ * What a distance measurement paints: its caption, the ink of its dimension
170
+ * line, leaders and caption connector, and its endings.
171
+ */
172
+ declare function distancePainted(layout: DistanceLayout, strokeWidth: number): PaintedPiece[];
173
+ declare function distanceCaptionAt(geometry: Shape, appearance: DistanceAppearance, width: number): Point | null;
174
+ declare function distanceLeaderLength(geometry: Shape, point: Point): number;
175
+ declare function moveDistanceCaption(geometry: Shape, appearance: DistanceAppearance, delta: Point): DistanceAppearance;
176
+ /**
177
+ * The upright box around all a distance paints: its dimension line, leader
178
+ * and connector lines and its endings stroked at `strokeWidth` (what
179
+ * `distanceScene` draws), and its caption's box.
180
+ */
181
+ declare function distanceDrawnBounds(layout: DistanceLayout, strokeWidth: number): Rect;
182
+ declare function distanceScene(geometry: Shape, appearance: DistanceAppearance, style: Style): SceneNode[];
183
+ declare function measurementCaptionScene(caption: DistanceCaptionLayout | null, style: Style): SceneNode[];
184
+ //#endregion
185
+ //#region src/shapes/family.d.ts
186
+ /** A box's four corners as the page shows them: nw, ne, se, sw. */
187
+ type Corners = [Point, Point, Point, Point];
188
+ interface ShapeFamily<S extends Shape = Shape> {
189
+ /** The shape, read off an annotation of a kind in this family. */
190
+ read(annotation: Annotation): S;
191
+ /** The engine fields that state `shape` on an annotation of `subtype`. */
192
+ write(shape: S, subtype: string): FieldValues;
193
+ /**
194
+ * The shape a create gesture makes at `placement` (a clicked or dragged box
195
+ * or segment, `placement.ts`) for `annotation`, what the tool creates before
196
+ * it has a shape; `null` when this family's kinds aren't made that way.
197
+ */
198
+ placed(placement: Placement, annotation: Annotation): S | null;
199
+ /** The box around its box or points, the stroke left out. */
200
+ bounds(shape: S): Rect;
201
+ /**
202
+ * The box the live drawing is painted into (the stroke, endings, a cloud's
203
+ * bumps): a box's or caret's own, before its turn (the renderer turns it),
204
+ * the page's for the rest.
205
+ */
206
+ drawnBounds(shape: S, stroke: Stroke): Rect;
207
+ /** The upright page box around all it paints: the engine's `rect`, as it measures its appearance. */
208
+ rect(shape: S, stroke: Stroke): Rect;
209
+ /** The box a selection wraps: where its handles sit, and where a selected shape is grabbed. */
210
+ selectionBounds(shape: S, stroke: Stroke): Rect;
211
+ /** Does the shape have a turned box of its own, which a selection outlines turned? */
212
+ oriented(shape: S): boolean;
213
+ /** That turned box's corners, or `null` when the shape has none. */
214
+ turnedCorners(shape: S, stroke: Stroke): Corners | null;
215
+ /** Where a turn of the shape pivots, as the engine turns it. */
216
+ pivot(shape: S): Point;
217
+ /** The shape moved by `delta`. */
218
+ translate(shape: S, delta: Point): S;
219
+ /** The shape turned `degrees` clockwise about `pivot`; unchanged when it doesn't turn. */
220
+ rotateAbout(shape: S, pivot: Point, degrees: number): S;
221
+ /**
222
+ * The shape scaled about `anchor` by `(sx, sy)`, exactly. A resize passes
223
+ * its limit, `minSize`: a box then ends up no smaller than that.
224
+ */
225
+ scaleAbout(shape: S, anchor: Point, sx: number, sy: number, minSize?: number): S;
226
+ /** The shape with its turn cleared, turned back about `pivot` where that matters. */
227
+ upright(shape: S, pivot?: Point): S;
228
+ /** Its handles: resize corners and sides, or vertices. */
229
+ handles(shape: S): Handle[];
230
+ /** The shape with `handle` dragged to `to`. */
231
+ drag(shape: S, handle: string, to: Point): S;
232
+ /**
233
+ * What it paints (`painted.ts`): its ink, and its inside when `filled`. A
234
+ * click and the marquee both read these pieces.
235
+ */
236
+ painted(shape: S, stroke: Stroke, filled: boolean): PaintedPiece[];
237
+ /** What it draws, for the live (vector) view. */
238
+ scene(shape: S, stroke: Stroke): RenderNode[];
239
+ }
240
+ //#endregion
241
+ //#region src/shapes/box.d.ts
242
+ /** A box and its turn, as the engine keeps them. */
243
+ interface TurnedBox {
244
+ /** The box before any turn: the engine's `box`, or its `rect` for a kind that never turns. */
245
+ box: Rect;
246
+ /** Degrees clockwise about the middle of `box`; 0 when upright. */
247
+ rotation: number;
248
+ }
249
+ /** A box family record's shape: the engine's box and its turn. */
250
+ interface BoxShape extends TurnedBox {
251
+ kind: 'box';
252
+ /** Drawn as the ellipse in `box`: a circle. */
253
+ ellipse: boolean;
254
+ }
255
+ /** The box family: a box and its turn, drawn as a rectangle, an ellipse or a cloud. */
256
+ declare const boxFamily: ShapeFamily<BoxShape>;
257
+ //#endregion
258
+ //#region src/shapes/caret.d.ts
259
+ /** A caret record's shape: the engine's box and its turn. */
260
+ interface CaretShape extends TurnedBox {
261
+ kind: 'caret';
262
+ }
263
+ /**
264
+ * The caret box for a text-edit anchor, upright: half the glyph's ink height,
265
+ * centred on the trailing baseline corner in reading direction (`advance`
266
+ * decides which end, so a right-to-left caret lands on the visual left),
267
+ * sitting on the baseline. {@link caretFromAnchor} gives the turned caret.
268
+ */
269
+ declare function caretRectFromAnchor(anchor: TextEndAnchor): Rect;
270
+ /**
271
+ * The caret for a text-edit anchor: a box whose middle sits half a caret size
272
+ * toward the ascent from the trailing baseline corner, turned by the text's
273
+ * baseline tilt (degrees clockwise), so the mark hugs the turned baseline and
274
+ * points at its text. For upright text this is exactly
275
+ * {@link caretRectFromAnchor} with no turn.
276
+ */
277
+ declare function caretFromAnchor(anchor: TextEndAnchor): CaretShape;
278
+ /**
279
+ * The caret family: a mark in a turned box that follows its text. It moves
280
+ * and scales with a selection, but no gesture turns, resizes or drags it.
281
+ */
282
+ declare const caretFamily: ShapeFamily<CaretShape>;
283
+ //#endregion
284
+ //#region src/shapes/points.d.ts
285
+ /** A line: its two ends upright, and their endings. */
286
+ interface LineShape {
287
+ kind: 'line';
288
+ linePoints: {
289
+ start: Point;
290
+ end: Point;
30
291
  };
292
+ lineEndings?: LineEndings$1;
293
+ /** Degrees clockwise about the middle of the box around the upright points; 0 when upright. */
294
+ rotation: number;
295
+ }
296
+ /** A polygon (`closed`) or polyline: its vertices upright, and a polyline's endings. */
297
+ interface PolyShape {
298
+ kind: 'poly';
299
+ vertices: Point[];
300
+ closed: boolean;
301
+ lineEndings?: LineEndings$1;
302
+ /** A measurement's caption center, upright with the vertices; `null` places it automatically. */
303
+ captionCenter?: Point | null;
304
+ /** Degrees clockwise about the middle of the box around the upright points; 0 when upright. */
305
+ rotation: number;
306
+ }
307
+ /** Ink: its strokes upright. */
308
+ interface InkShape {
309
+ kind: 'ink';
310
+ inkList: Point[][];
311
+ /** Degrees clockwise about the middle of the box around the upright points; 0 when upright. */
312
+ rotation: number;
313
+ }
314
+ /** A points family record's shape. */
315
+ type PointsShape = LineShape | PolyShape | InkShape;
316
+ /** A line's or polyline's endings; none for an annotation that has none. */
317
+ declare const lineEndingsOf: (annotation: Annotation) => LineEndings$1;
318
+ /** Where the points are drawn: the upright ones turned by `rotation`. The same lists for the same shape. */
319
+ declare function drawnStrokesOf(shape: PointsShape): Point[][];
320
+ /** Miter limit shared by the bounds math and the SVG renderer (`stroke-miterlimit`),
321
+ * so the computed box and the drawn stroke always agree on where a sharp join
322
+ * bevels instead of spiking. 10 = the PDF default (also what the baked /AP uses). */
323
+ declare const MITER_LIMIT = 10;
324
+ /** The points family: points kept upright and the turn that draws them. */
325
+ declare const pointsFamily: ShapeFamily<PointsShape>;
326
+ //#endregion
327
+ //#region src/shapes/quads.d.ts
328
+ /** A quads family record's shape: its text's quads. */
329
+ interface QuadsShape {
330
+ kind: 'quads';
331
+ quadPoints: Quad$1[];
31
332
  }
32
- /** Interaction gate for a concrete annotation: widget kinds ignore `readOnly`
33
- * (ISO 32000 — a ReadOnly form field must still be movable by a form designer;
34
- * the form-filling layer enforces field ReadOnly itself). */
35
- declare const annotInteractive: (a: FlagBearer) => boolean;
36
333
  /**
37
- * Geometry/style mutations — `locked` freezes the OBJECT, not its contents
38
- * (that's `lockedContents`), AND the session must hold update authority over
39
- * the record. One predicate for hit-test, chrome, and props alike, so a
40
- * record you may not edit renders and behaves exactly like a locked one.
334
+ * The quads family: marks bound to their text. They move with a selection,
335
+ * but no gesture turns, scales or drags them, and they have no turned box.
41
336
  */
42
- declare const annotTransformable: (a: FlagBearer) => boolean;
43
- /** Deletion — the delete half of the authority split (a narrowed grant can
44
- * allow update but not delete, or vice versa). Flags gate like transforms. */
45
- declare const annotDeletable: (a: FlagBearer) => boolean;
46
- /** `/Contents` text edits — the contents counterpart of `locked`. */
47
- declare const annotContentsEditable: (a: FlagBearer) => boolean;
337
+ declare const quadsFamily: ShapeFamily<QuadsShape>;
48
338
  //#endregion
49
- //#region src/types.d.ts
339
+ //#region src/shapes/text-box.d.ts
340
+ type FreeTextAnnotation = Extract<Annotation, {
341
+ subtype: 'free-text';
342
+ }>;
343
+ /** A callout's line: its tip, its knee when it bends, and where it meets the box. */
344
+ type CalloutLine = NonNullable<FreeTextAnnotation['calloutLine']>;
345
+ /** A free text's shape: the engine's box, turn, and a callout's line. */
346
+ interface TextBoxShape extends TurnedBox {
347
+ kind: 'text-box';
348
+ /** A callout's line; `null` for a plain text box. */
349
+ calloutLine: CalloutLine | null;
350
+ /** The ending at a callout's tip. */
351
+ lineEnding: LineEnding$1 | null;
352
+ }
50
353
  /**
51
- * Content-space point/rect (y-down, PDF points, crop-relative) — the viewer space.
52
- * These are the shared `@embedpdf/core-geometry` primitives: a `Vec` IS a `Point`
53
- * and `Rect` IS geometry's `Rect`, so the whole stack speaks one vocabulary and
54
- * the coordinate math lives in ONE package.
354
+ * Where a callout's line meets its box: the middle of the side `toward` (the
355
+ * knee, else the tip) lies beyond, chosen by which way it is farther from the
356
+ * box's middle, across or up and down. A turned box decides in its own frame,
357
+ * and the point lands on the turned side, the one the page shows. The
358
+ * engine's rule, in page space.
55
359
  */
56
- type Vec = Point;
360
+ declare function calloutEnd(box: Rect, toward: Point, rotation?: number): Point;
361
+ /**
362
+ * The text box family: a free text's box and turn, and a callout's line.
363
+ * Only a plain box turns; a callout's box keeps the turn it was made with,
364
+ * and a selection outlines everything it paints, upright.
365
+ */
366
+ declare const textBoxFamily: ShapeFamily<TextBoxShape>;
367
+ /**
368
+ * The text plate inset of a free-text box: twice the border width. The plate
369
+ * — where text lays out, clips and scrolls — is the box deflated by this on
370
+ * every side: the ink band and an equal breathing band, so the text never
371
+ * touches the stroke. Acrobat's rule, measured 1–12 pt on plain boxes and
372
+ * 1–7 pt on callouts; the engine's `FreeTextPlate` is the same formula, so
373
+ * the live editor sits exactly where the baked text lands. Acrobat's thinnest
374
+ * border is 1 pt; a width of 0 is ours alone and gives no inset (the plate is
375
+ * the box).
376
+ */
377
+ declare function textPlateInset(strokeWidth: number): number;
378
+ //#endregion
379
+ //#region src/types.d.ts
57
380
  type Rect = Rect$1;
58
- /** Four POSITIONAL content-space points — selection chrome / OBB corners.
59
- * Text-markup geometry uses the corner-NAMED {@link TextQuad} instead. */
60
- type Quad = [Vec, Vec, Vec, Vec];
61
381
  /**
62
382
  * Where a text-edit annotation (caret / replace-text) anchors: the boundary
63
- * glyph's oriented cell plus the READING direction along its baseline
64
- * (+1 = toward `end`, −1 = toward `start` — sequence-derived, never inferred
65
- * from geometry).
383
+ * glyph's oriented cell plus the reading direction along its baseline
384
+ * (+1 = toward the right end, −1 = toward the left end — sequence-derived,
385
+ * never inferred from geometry).
66
386
  */
67
387
  interface TextEndAnchor {
68
- glyphQuad: TextQuad$1;
388
+ glyphQuad: Quad$1;
69
389
  advance: 1 | -1;
70
390
  }
71
391
  type Id = string;
@@ -73,128 +393,80 @@ type Cursor = string;
73
393
  type PolySubtype = 'polygon' | 'polyline';
74
394
  /**
75
395
  * The per-page view environment the screen-anchor projection consumes:
76
- * `zoom` — the page's zoom RELATIVE to its 100% baseline (dimensionless;
396
+ * `zoom` — the page's zoom relative to its 100% baseline (dimensionless;
77
397
  * 1 = Acrobat's 100% = `viewUnitsPerPoint × userUnit` view px per point) —
78
- * and the TOTAL display rotation (document /Rotate + view rotation).
79
- * Deliberately NOT a px-per-point scale: `noZoom` means "hold the body at its
80
- * 100%-zoom size", a statement about zoom, so feeding it a units conversion
81
- * (the old mistake) shrank bodies at 100%. Per-event / per-call context (the
398
+ * and the total display rotation (document /Rotate + view rotation).
399
+ * Deliberately not a px-per-point scale: `noZoom` means "hold the body at its
400
+ * 100%-zoom size", a statement about zoom; a units conversion here would
401
+ * shrink bodies at 100%. Per-event / per-call context (the
82
402
  * `pageBox` pattern) — never stored on the model; the pointer drafts capture
83
- * it at DOWN so anchored gestures project and commit with the view they
403
+ * it at down so anchored gestures project and commit with the view they
84
404
  * started under. See anchor.ts.
85
405
  */
86
406
  interface ViewEnv {
87
407
  zoom: number;
88
408
  rotation: PageRotation;
89
409
  }
90
- type Subtype = 'highlight' | 'underline' | 'squiggly' | 'strikeout' | 'square' | 'circle' | 'line' | 'polygon' | 'polyline' | 'caret' | 'ink' | 'stamp' | 'text' | 'file-attachment' | 'redact' | 'link' | (string & {});
91
- /**
92
- * Content-space geometry — the ONE thing hit-testing, editing, and rendering work
93
- * on. A small closed union covers every kind: shapes (rect/ellipse), line,
94
- * polygon/polyline (poly), text markup (quads), and caret.
95
- */
96
410
  /**
97
- * A free-text callout's leader: the `/CL` line + `/LE` arrow. `tip` is the
98
- * called-out point (the arrow is drawn here); `knee` is the optional elbow. The
99
- * point where the leader meets the text box (the third `/CL` point) is NEVER
100
- * stored — it is DERIVED from the box + the knee (see `calloutConnection`), so it
101
- * can't drift when the box or knee moves. Content space (y-down).
411
+ * An annotation kind's name (`kinds/`): its PDF subtype, a widget's field
412
+ * family (`widget-text`…), or a free text's callout (`free-text-callout`).
102
413
  */
103
- interface Callout {
104
- tip: Vec;
105
- knee?: Vec;
106
- ending: LineEnding;
107
- }
414
+ type KindName = 'highlight' | 'underline' | 'squiggly' | 'strikeout' | 'square' | 'circle' | 'line' | 'polygon' | 'polyline' | 'caret' | 'ink' | 'stamp' | 'text' | 'file-attachment' | 'redact' | 'link' | (string & {});
108
415
  /**
109
- * Rotation (degrees, clockwise in content space, normalized `[0,360)`) carried by
110
- * the rotatable `Geom` variants. The semantics differ by family, which is exactly
111
- * the box-vs-vertex split:
416
+ * An annotation's shape: what hit-testing, gestures and drawing work on, read
417
+ * off the annotation by its kind's family (`shapeOf`). One arm per family,
418
+ * each the engine's own fields
419
+ * ({@link BoxShape}, {@link TextBoxShape}, {@link PointsShape},
420
+ * {@link QuadsShape}, {@link CaretShape}).
112
421
  *
113
- * - **Box** (`rect`, `text`): `rect` is the UNROTATED local box and `rot` is the
114
- * applied tilt — together they reconstruct the visual. The repository emits
115
- * `rect` as `unrotatedRect`, `rot` as the rendered `/EMBD_Metadata/Rotation`,
116
- * and the rotated visual AABB as `/Rect`, so PDFium bakes a portable `/AP`.
117
- * - **Vertex** (`line`, `poly`, `ink`): the points are ALREADY rotated (they are
118
- * the portable visual), so `rot` is an ADVISORY scalar — the cumulative tilt the
119
- * user applied since authoring. It lets EmbedPDF reconstruct an oriented
120
- * selection box (`obbFromTheta`) and offer reset-to-0; it is inert for
121
- * rendering (PDFium ignores a lone `Rotation` with no `UnrotatedRect`).
122
- */
123
- type Geom = {
124
- t: 'rect';
125
- rect: Rect;
126
- ellipse: boolean;
127
- rot?: number;
128
- } | {
129
- t: 'line';
130
- a: Vec;
131
- b: Vec;
132
- ends?: LineEndings;
133
- rot?: number;
134
- } | {
135
- t: 'poly';
136
- points: Vec[];
137
- closed: boolean;
138
- ends?: LineEndings;
139
- rot?: number;
140
- } | {
141
- t: 'quads';
142
- quads: TextQuad$1[];
143
- } | {
144
- t: 'caret';
145
- rect: Rect;
146
- rot?: number;
147
- } | {
148
- t: 'ink';
149
- strokes: Vec[][];
150
- rot?: number;
151
- } | {
152
- t: 'text';
153
- rect: Rect;
154
- callout?: Callout;
155
- rot?: number;
156
- };
422
+ * Rotation is degrees clockwise in page space, normalized `[0,360)`, and
423
+ * works the same way for every family that turns: the shape is kept upright
424
+ * (a box, or points), and `rotation` turns it about its middle. The engine
425
+ * works out `/Rect` around the turned drawing, so PDFium bakes a portable `/AP`.
426
+ */
427
+ type Shape = BoxShape | PointsShape | QuadsShape | CaretShape | TextBoxShape;
157
428
  /**
158
- * How a shape's outline is stroked. A discriminated union so illegal combinations
159
- * — a dash array on a cloudy border, an intensity on a dashed one — are simply
160
- * unrepresentable. Maps onto the engine's `/BS /S` (`borderStyle`), `/BS /D`
161
- * (`dashArray`), and `/BE /I` (`cloudyIntensity`) wire fields. Cloudy is only
162
- * honoured for shapes (square/circle); other kinds treat it as solid.
429
+ * How an annotation is drawn, by the engine's field names: its colours, its
430
+ * stroke and its border, with its kind's fill-ins (a highlight strokes
431
+ * nothing, a caret draws 1 pt). A view its kind works out from the
432
+ * annotation (`styleOf`, `kinds/styles.ts`) for drawing, bounding and
433
+ * hit-testing: nothing is written back through it.
163
434
  */
164
- type Border = {
165
- kind: 'solid';
166
- } | {
167
- kind: 'dashed';
168
- dash: number[];
169
- } | {
170
- kind: 'cloudy';
171
- intensity: number;
172
- };
173
435
  interface Style {
174
- /** `/C` colour — stroke for geometric kinds, highlight colour for markup. */
175
- color: string;
176
- /** `/IC` interior (fill) colour. `null` when the annotation has no fill. */
177
- interiorColor: string | null;
436
+ /** The stroke for a drawn kind, a markup's colour, a free text's border. */
437
+ color: Color;
438
+ /** The fill; `null` when the annotation has none. */
439
+ interiorColor: Color | null;
178
440
  strokeWidth: number;
179
441
  opacity: number;
180
- /** Effective blend mode of the annotation's normal appearance. */
181
442
  blendMode: BlendMode$1;
182
- /** Outline style — defaults to `{ kind: 'solid' }`. */
183
- border: Border;
443
+ borderStyle: AnnotationBorderStyle;
444
+ /** A dashed border's pattern; `null` draws the engine's default dash. */
445
+ dashArray: number[] | null;
446
+ /** A cloudy border's intensity; `null` without a cloud. A cloud wins over a dash. */
447
+ cloudyIntensity: number | null;
448
+ }
449
+ /**
450
+ * What a shape's bounds, hit test and drawing depend on besides the shape:
451
+ * how wide its stroke is, and a cloud's intensity. A {@link Style} is one.
452
+ */
453
+ interface Stroke {
454
+ strokeWidth: number;
455
+ cloudyIntensity?: number | null;
184
456
  }
185
457
  type TextAlign = 'left' | 'center' | 'right';
186
458
  /**
187
- * Content-space text styling for a text-editable kind (free text) — the text
188
- * counterpart of {@link Style}, projected from the DTO's `/DA` fields the same
189
- * way `style` is projected from `/C`/`/CA`/`/BS`. CSS colour string; the engine
190
- * `Color` seam is crossed only in the plugin repository.
459
+ * How an annotation's text is set, by the engine's field names: its font,
460
+ * size, colour and alignment, and a free text's bold, italic and underline.
461
+ * A view its kind works out from the annotation (`textOf`,
462
+ * `kinds/texts.ts`): nothing is written back through it.
191
463
  */
192
464
  interface TextStyle {
193
465
  /** A PDF standard font name or a registered font key. */
194
466
  fontFamily: string;
195
- /** Content units (PDF points). */
467
+ /** Page units (PDF points); 0 fits a form field's or a redaction label's box. */
196
468
  fontSize: number;
197
- fontColor: string;
469
+ fontColor: Color;
198
470
  textAlign: TextAlign;
199
471
  /**
200
472
  * The rich body's formatting (free text only): bold = body weight ≥ 600,
@@ -207,81 +479,40 @@ interface TextStyle {
207
479
  underline?: boolean;
208
480
  }
209
481
  /**
210
- * The ONE flat vocabulary for editable appearance properties — what property
211
- * sidebars/toolbars read and write, regardless of where each property is stored
212
- * internally (`style`, `geom.ends`, or `text`). Which keys apply to a kind, and
213
- * in what UI order, is declared per kind in the KIND table (`propsFor`).
482
+ * Engine fields by name, as a create or an update states them: a tool's
483
+ * defaults (a draft without its shape), or what a selection edit writes to
484
+ * each member. The kind table's {@link AnnotationProperty} lists say which a style panel edits.
485
+ */
486
+ type FieldValues = Readonly<Record<string, unknown>>;
487
+ /**
488
+ * One annotation as the core works on it: the engine's record, and how it is
489
+ * drawn right now. The record is the only data: its ref, page, kind and
490
+ * relationships are read off it (`refOf`, `kindOf`, `irtOf`, `groupOf`), so
491
+ * is its shape (`shapeOf`), and an edit writes engine fields back
492
+ * (`withShape`, `withValues`; record/).
214
493
  */
215
- interface AnnotationProps extends Style, TextStyle {
216
- /** `/LE` endings (line / polyline). */
217
- lineEndings: LineEndings;
494
+ interface ModelAnnotation {
218
495
  /**
219
- * `/Name` icon of an icon kind (text note, file attachment). Optional in
220
- * the bag — each kind falls back to its own spec default ('comment',
221
- * 'paperclip') at creation, so one flat vocabulary needs no per-kind base
222
- * value. Valid values are the `options` of the kind's `icon` PropSpec.
496
+ * The record's key: `annotationKey(annotation.ref)`, for life. A record
497
+ * this session creates has its final ref from the start: the object number
498
+ * its create names.
223
499
  */
224
- icon?: string;
500
+ id: Id;
225
501
  /**
226
- * Where this annotation LINKS to, or `null` for none. Optional in the bag
227
- * like `icon`. One key, two storages (the props.ts routing rule): on the
228
- * link kind it is the annotation's OWN target (`/A`); on every other
229
- * linkable kind it is the target of the ATTACHED link child(ren) —
230
- * grouped `/Link` annotations the model folds into their parent (the
231
- * serialization the PDF spec forces, since only Link annotations are
232
- * clickable). Writing it creates/retargets/deletes those children through
233
- * the ONE `syncLink` seam.
502
+ * A record this session created that the engine hasn't written yet: it
503
+ * has its ref, but no raster of its appearance.
234
504
  */
235
- link?: PdfLinkTarget | null;
236
- }
237
- type PropKey = keyof AnnotationProps;
238
- /**
239
- * What a committed edit CHANGED — carried on the `patch` effect so the shell
240
- * emits exactly that intent (repository `toScopedPatch`) instead of
241
- * reconstructing a full projection. `geometry` covers every gesture commit
242
- * (move/resize/rotate/vertex edit); `props` forwards the user's patch keys
243
- * verbatim. Text content never rides this effect (the debounced text-edit
244
- * write owns `contents`).
245
- */
246
- type PatchScope = {
247
- kind: 'geometry';
248
- } | {
249
- kind: 'props';
250
- keys: PropKey[];
251
- };
252
- /** A partial property write. `lineEndings` merges per side (set just `end`
253
- * without knowing `start`); every other key overwrites. */
254
- type AnnotationPropsPatch = { [K in PropKey]?: K extends 'lineEndings' ? Partial<LineEndings> : AnnotationProps[K]; };
255
- interface Annot {
256
- id: Id;
257
- ref: AnnotationRef | null;
258
- pon: PageObjectNumber;
259
- subtype: Subtype;
260
- geom: Geom;
261
- style: Style;
262
- /** Text styling — present only for text-editable kinds (free text). Like
263
- * `style`, a content-space projection of `data`, editable via `setProps`. */
264
- text?: TextStyle;
265
- /** Redaction label (`/OverlayText` + `/Repeat`) — redact kind only. A
266
- * projection of `data` like `text`; the hover preview scene draws it. */
267
- label?: {
268
- text: string;
269
- repeat: boolean;
270
- };
271
- /** `/Name` icon — present only for icon kinds (text note, file attachment).
272
- * Like `style`, a projection of `data`, editable via `setProps`. */
273
- icon?: string;
505
+ unconfirmed?: true;
274
506
  /**
275
- * The `/F` annotation flags, verbatim from the DTO (freshly drawn annotations
276
- * start at {@link DRAWN_FLAGS} — `print` set). NEVER read individual keys to
277
- * gate behavior — the predicates in `flags.ts` (`annotInteractive`,
278
- * `annotTransformable`, `annotContentsEditable`, `viewable`) are the one
279
- * interpretation of the spec, and `anchorModeOf` owns `noZoom`/`noRotate`.
507
+ * How the record renders: `baked` blits the engine's appearance raster,
508
+ * `vector` draws it live from its geometry and style. The core sets
509
+ * `vector` when an edit makes the raster stale (a resize, a restyle,
510
+ * typing); a move keeps the raster. The plugin decides it for confirmed
511
+ * records: this session's edits stay live, another session's are baked.
280
512
  */
281
- flags: AnnotationFlags$1;
282
513
  source: 'baked' | 'vector';
283
514
  /**
284
- * Content-space box of the engine appearance raster (the AP `/Rect`), set when
515
+ * Page-space box of the engine appearance raster (the AP `/Rect`), set when
285
516
  * the annotation is derived from a DTO. While `source === 'baked'` the renderer
286
517
  * blits the engine bitmap into this box; a move translates it (a rigid shift
287
518
  * keeps the raster valid), so the bitmap rides along without re-rendering.
@@ -289,72 +520,58 @@ interface Annot {
289
520
  */
290
521
  apBox?: Rect;
291
522
  /**
292
- * Rotation (deg, CW) that was STRIPPED from the baked raster — present only
293
- * when the engine rendered this appearance rotation-free (a box-family kind
294
- * whose DTO carries BOTH `rotation` and `unrotatedRect`; `apBox` is then the
295
- * unrotated box). The blit re-applies it as a view transform about the box
296
- * centre. Vertex kinds pre-rotate their geometry, so this stays unset there
297
- * and their rasters blit untransformed.
523
+ * Rotation (deg, CW) that was stripped from the baked raster — present only
524
+ * when the engine rendered this appearance rotation-free (`appearanceTurnOf`:
525
+ * a box kind drawn turned, inside its turned box; `apBox` is then its `box`).
526
+ * The blit re-applies it as a view transform about the box centre. Vertex
527
+ * kinds pre-rotate their geometry, so this stays unset there and their
528
+ * rasters blit untransformed.
298
529
  */
299
530
  apRot?: number;
300
531
  /**
301
- * Revision of the engine-baked `/AP` CONTENT (absent ≡ 0). The raster a baked
532
+ * The version of the engine's picture of it (absent ≡ 0). The picture a baked
302
533
  * annotation shows depends on exactly this and the render scale — never on
303
534
  * position (`apBox` translates the blit) or rotation (`apRot` transforms it) —
304
- * so the shell re-fetches appearances precisely when it changes. Bumped by the
305
- * `upsert` that confirms an engine re-bake with new content: a geometry patch
306
- * that changed the authoring frame's SIZE resolving (`Effect.apChanged`), or a
307
- * remote edit folding in. A move or rotate leaves it untouched: the old raster
308
- * is still pixel-exact, so those cost zero re-renders.
535
+ * so the shell re-fetches pictures precisely when it changes. Owned by the
536
+ * plugin's confirmed records, which advance it when the engine reports a
537
+ * re-baked appearance; the core only reads it.
309
538
  */
310
- apVersion?: number;
539
+ pictureVersion?: number;
311
540
  /**
312
- * This SESSION's authority over this record, projected from the security
313
- * service's collab mirrors at ingest (see permissions.md) — model-owned
314
- * derived state like `apVersion`, never DTO-derived. Fused into
541
+ * A lent record's live drawing, from its lender (a form widget, drawn by the
542
+ * form's painter): the record drawn upright in `box`, in page space. It
543
+ * gives a kind with no live drawing of its own one (`drawsLive`), so the
544
+ * record draws live while a gesture reshapes it, as a square does.
545
+ */
546
+ painter?: (box: Rect) => readonly SceneNode[];
547
+ /**
548
+ * This session's authority over this record (see permissions.md), set by the
549
+ * plugin from the security service; the core only reads it. Fused into
315
550
  * `annotTransformable`/`annotDeletable`, so a record the session may not
316
551
  * edit renders and behaves exactly like a `locked` one (bare outline, no
317
- * handles, no drag). Absent = unstamped (a local draft, a wildcard local
318
- * engine, tests) and treated as allowed — the client gate is a COURTESY
319
- * that keeps the UI truthful; the engine independently enforces.
552
+ * handles, no drag). Absent = unstamped (a record created in this session,
553
+ * a wildcard local engine, tests) and treated as allowed — the client gate
554
+ * is a courtesy that keeps the UI truthful; the engine independently enforces.
320
555
  */
321
556
  authority?: {
322
557
  update: boolean;
323
558
  delete: boolean;
324
559
  };
325
560
  /**
326
- * The canonical engine DTO this annotation was derived from (PDF-space, sRGB)
327
- * — the single source of truth for its data. `geom` and `style` are
328
- * content-space RENDER PROJECTIONS of it, recomputed (never edited directly)
329
- * whenever `data` changes, so the two can't drift. Absent only for a vector
330
- * draft that hasn't been committed to the engine yet (no DTO exists).
331
- */
332
- data?: AnnotationDTO;
333
- /** Normalized PDF `/IT` for intent-bearing annotations authored before a DTO exists. */
334
- intent?: CaretIntent | StrikeoutIntent | InkIntent;
335
- /**
336
- * The link KIND's own `/A` target (see {@link AnnotationProps.link}) —
337
- * present only on `subtype: 'link'`. Every OTHER kind's link is an
338
- * attached child annotation in the substrate, read through the `linkOf`
339
- * lens and materialized by the shell's `syncLink` reconciler; parents
340
- * store nothing.
561
+ * The annotation's data in the engine's shape: what the engine will read
562
+ * back once this session's writes land. A confirmed record's is the
563
+ * engine's own read; a record this session created reads as its create
564
+ * writes it (`newRecord`); an edit merges the fields it changed, and
565
+ * `update` applies them as the engine will, a drawn kind's `rect`
566
+ * included (`record/written.ts`). Its attribution waits for the engine's
567
+ * answer.
341
568
  */
342
- link?: PdfLinkTarget | null;
343
- /**
344
- * Relationship to another annotation. `irt` ("in reply to") links a child to a
345
- * parent — a reply in a comment thread, or a caret bound to its strikeout in a
346
- * replace-text pair. `group` ties a set into one composite unit (created and,
347
- * typically, deleted together). Both are unused until comments / replace-text
348
- * land, but the field lives here from the start so select/delete/persistence
349
- * never have to be retrofitted around it.
350
- */
351
- irt?: Id;
352
- group?: string;
569
+ annotation: Annotation;
353
570
  }
354
571
  /** A draggable handle: a resize corner/edge (rect) or a vertex (line/poly). */
355
572
  interface Handle {
356
573
  id: string;
357
- at: Vec;
574
+ at: Point$1;
358
575
  cursor: Cursor;
359
576
  }
360
577
  /** An alignment guide produced by move-snapping: a vertical (`axis: 'x'`) or
@@ -365,198 +582,297 @@ interface Guide {
365
582
  lo: number;
366
583
  hi: number;
367
584
  }
368
- /** Snapping behaviour — seeded from the plugin config, live-adjustable via the
369
- * `setSnap` msg (so an app can wire a UI toggle). */
585
+ /**
586
+ * Snapping: to other annotations and the page while moving, and to angles
587
+ * while turning. The plugin keeps it in its settings and hands it in with
588
+ * `setSnap`.
589
+ */
370
590
  interface SnapSettings {
371
- /** Alignment guides while moving (snap to other annotations + the page). */
372
- guides: boolean;
373
- /** Guide snap tolerance, content units (PDF pt) — the `hitMargin` convention. */
374
- guideThreshold: number;
375
- /** Snap the rotate gesture onto `rotationAngles`. */
376
- rotation: boolean;
377
- rotationAngles: number[];
378
- /** Rotation snap tolerance, degrees. */
379
- rotationThreshold: number;
591
+ /** Snap to the edges and centres of other annotations and of the page while moving. */
592
+ readonly alignment: boolean;
593
+ /**
594
+ * How close a move comes before it snaps, in screen pixels: each pointer
595
+ * sample converts it by its `scale`. Without one it is in page units.
596
+ */
597
+ readonly alignmentThreshold: number;
598
+ /** Snap a turn onto `rotationAngles`. */
599
+ readonly rotation: boolean;
600
+ /** The angles a turn snaps to, in degrees clockwise. */
601
+ readonly rotationAngles: readonly number[];
602
+ /** How close a turn comes before it snaps, in degrees. */
603
+ readonly rotationThreshold: number;
380
604
  }
381
605
  /**
382
606
  * The `defaults` key a creation draft resolves its props from — the authoring
383
- * TOOL that started it, which may differ from the PDF `subtype`. Two tools can
607
+ * tool that started it, which may differ from the PDF `subtype`. Two tools can
384
608
  * share a subtype but carry distinct defaults (an "arrow" is a `line` with an
385
609
  * arrowhead default); the preset keeps them apart. Absent → fall back to
386
610
  * `subtype` (a headless / programmatic caller that isn't tool-driven), so the
387
- * built-in tools where preset === subtype behave exactly as before.
611
+ * built-in tools where preset === subtype resolve from their subtype.
388
612
  */
389
613
  type Draft = {
390
- g: 'create-rect';
391
- subtype: Subtype;
614
+ kind: 'create-rect';
615
+ subtype: KindName;
392
616
  preset?: string;
393
- pon: PageObjectNumber;
394
- from: Vec;
395
- to: Vec;
396
- ellipse: boolean;
397
- /** Display rotation + upright policy captured at DOWN (the gesture's home
398
- * page), so the commit counter-rotates against what the author SAW even
617
+ page: PageRef;
618
+ from: Point$1;
619
+ to: Point$1;
620
+ /** Display rotation + upright policy captured at down (the gesture's home
621
+ * page), so the commit counter-rotates against what the author saw even
399
622
  * if the up sample resolves elsewhere. See {@link PointerInput}. */
400
623
  displayRotation?: PageRotation;
401
624
  upright?: boolean;
402
- /** The tool's click-create policy, captured at DOWN like `upright`. */
625
+ /** The tool's click-create policy, captured at down like `upright`. */
403
626
  clickCreate?: ClickCreate | false;
404
- /** The tool's `/F` seed, captured at DOWN like `upright` — merged over
627
+ /** The tool's `/F` seed, captured at down like `upright` — merged over
405
628
  * {@link DRAWN_FLAGS} at commit (a note tool sets noZoom/noRotate). */
406
629
  flags?: Partial<AnnotationFlags$1>;
407
630
  } | {
408
- g: 'create-line';
409
- subtype: Subtype;
631
+ kind: 'create-distance';
632
+ step: 'endpoints' | 'offset';
633
+ subtype: 'line';
634
+ preset: string;
635
+ page: PageRef;
636
+ from: Point$1;
637
+ to: Point$1;
638
+ measure: DistanceAppearance;
639
+ flags?: Partial<AnnotationFlags$1>;
640
+ } | {
641
+ kind: 'create-line';
642
+ measure?: MeasurementAppearance;
643
+ capture?: string;
644
+ subtype: KindName;
410
645
  preset?: string;
411
- pon: PageObjectNumber;
412
- from: Vec;
413
- to: Vec;
414
- /** The tool's click-create policy, captured at DOWN like `upright`. */
646
+ page: PageRef;
647
+ from: Point$1;
648
+ to: Point$1;
649
+ /** Display rotation + upright policy captured at down (see the rect
650
+ * draft): an upright tool's click lays its line as the author saw the page. */
651
+ displayRotation?: PageRotation;
652
+ upright?: boolean;
653
+ /** The tool's click-create policy, captured at down like `upright`. */
415
654
  clickCreate?: ClickCreate | false;
416
- /** The tool's `/F` seed, captured at DOWN (see the rect draft). */
655
+ /** The tool's `/F` seed, captured at down (see the rect draft). */
417
656
  flags?: Partial<AnnotationFlags$1>;
418
657
  } | {
419
- g: 'create-poly';
420
- subtype: Subtype;
658
+ kind: 'create-poly';
659
+ measure?: ShapeMeasurementAppearance;
660
+ subtype: KindName;
421
661
  preset?: string;
422
- pon: PageObjectNumber;
423
- points: Vec[];
424
- cur: Vec;
662
+ page: PageRef;
663
+ points: Point$1[];
664
+ current: Point$1;
425
665
  closed: boolean;
426
- /** The tool's `/F` seed, captured at DOWN (see the rect draft). */
666
+ /** The tool's `/F` seed, captured at down (see the rect draft). */
427
667
  flags?: Partial<AnnotationFlags$1>;
428
668
  } | {
429
- g: 'create-ink';
430
- subtype: Subtype;
669
+ kind: 'create-ink';
670
+ subtype: KindName;
431
671
  preset?: string;
432
- pon: PageObjectNumber;
433
- strokes: Vec[][];
672
+ page: PageRef;
673
+ strokes: Point$1[][];
434
674
  intent?: InkIntent;
435
- /** The tool's `/F` seed, captured at DOWN (see the rect draft). */
675
+ /** The tool's `/F` seed, captured at down (see the rect draft). */
436
676
  flags?: Partial<AnnotationFlags$1>;
437
677
  } | {
438
- g: 'create-callout';
439
- subtype: Subtype;
678
+ kind: 'create-callout';
679
+ subtype: KindName;
440
680
  preset?: string;
441
- pon: PageObjectNumber;
681
+ page: PageRef;
442
682
  step: 'knee' | 'box';
443
- tip: Vec;
444
- knee?: Vec;
445
- cur: Vec;
446
- boxFrom?: Vec;
447
- boxTo?: Vec;
448
- /** Display rotation + upright policy captured at the TIP click (the
449
- * gesture's home page) — the text BOX commits counter-rotated so it
683
+ tip: Point$1;
684
+ knee?: Point$1;
685
+ current: Point$1;
686
+ boxFrom?: Point$1;
687
+ boxTo?: Point$1;
688
+ /** Home-page box captured at the tip click. The default text box slides
689
+ * fully inside it; a drag is already point-clamped and ignores this. */
690
+ pageBox?: Rect;
691
+ /** Display rotation + upright policy captured at the tip click (the
692
+ * gesture's home page) — the text box commits counter-rotated so it
450
693
  * reads upright; the leader (tip/knee) is page-space and never turns.
451
694
  * Same capture rule as the rect draft. */
452
695
  displayRotation?: PageRotation;
453
696
  upright?: boolean;
454
- /** The tool's `/F` seed, captured at DOWN (see the rect draft). */
697
+ /** The tool's `/F` seed, captured at down (see the rect draft). */
455
698
  flags?: Partial<AnnotationFlags$1>;
456
699
  } | {
457
- g: 'move';
700
+ kind: 'move';
458
701
  ids: Id[];
459
- start: Vec;
460
- delta: Vec;
702
+ start: Point$1;
703
+ delta: Point$1;
461
704
  guides: Guide[];
462
705
  } | {
463
- g: 'handle';
706
+ kind: 'handle';
464
707
  id: Id;
465
708
  handle: string;
466
- base: Geom;
467
- cur: Geom;
709
+ base: Shape;
710
+ current: Shape;
468
711
  view?: ViewEnv;
469
712
  } | {
470
- g: 'rotate';
713
+ kind: 'rotate';
471
714
  ids: Id[];
472
- pivot: Vec;
473
- start: Vec;
474
- cur: Vec;
715
+ pivot: Point$1;
716
+ start: Point$1;
717
+ current: Point$1;
475
718
  free?: boolean;
476
719
  view?: ViewEnv;
477
720
  } | {
478
- g: 'group';
721
+ kind: 'group';
479
722
  op: 'resize';
480
723
  ids: Id[];
481
724
  handle: string;
482
- anchor: Vec;
725
+ anchor: Point$1;
483
726
  base: Rect;
484
- cur: Rect;
727
+ current: Rect;
485
728
  view?: ViewEnv;
486
729
  } | {
487
- g: 'marquee';
488
- pon: PageObjectNumber;
489
- from: Vec;
490
- to: Vec;
730
+ kind: 'caption';
731
+ id: Id;
732
+ start: Point$1;
733
+ delta: Point$1;
734
+ } | {
735
+ kind: 'leader';
736
+ id: Id;
737
+ start: Point$1;
738
+ delta: number;
739
+ } | {
740
+ kind: 'marquee';
741
+ page: PageRef;
742
+ from: Point$1;
743
+ to: Point$1;
491
744
  };
492
745
  /** A live text-markup preview (the in-progress selection rendered as the markup it
493
746
  * will become). Per page, since a selection can span pages. */
494
747
  interface MarkupPreview {
495
- subtype: Subtype;
748
+ subtype: KindName;
496
749
  /** Defaults key, distinct from subtype for presets such as replace-text. */
497
750
  preset: string;
498
- byPage: Record<number, TextQuad$1[]>;
751
+ /** Keyed by `page.pageObjectNumber` (an internal lookup, not an address). */
752
+ byPage: Record<number, Quad$1[]>;
499
753
  }
500
754
  /** Anchor + affordance state for UI that controls an in-progress creation draft. */
755
+ /**
756
+ * A rotation in progress, for the badge that follows the pointer: the page
757
+ * it runs on, the pointer there (page space) and the selection's angle as the
758
+ * commit will apply it (degrees clockwise, whole degrees).
759
+ */
760
+ interface RotationAnchor {
761
+ page: PageRef;
762
+ at: Point$1;
763
+ angle: number;
764
+ }
501
765
  interface CreationDraftAnchor {
502
766
  kind: 'poly';
503
767
  subtype: PolySubtype;
504
- pon: PageObjectNumber;
768
+ page: PageRef;
505
769
  bounds: Rect;
506
770
  pointCount: number;
507
771
  minPoints: number;
508
772
  canFinish: boolean;
509
773
  }
510
- interface Model {
511
- byId: Record<Id, Annot>;
512
- order: Id[];
774
+ /**
775
+ * What the core owns: everything about the user's session that is not a
776
+ * record. `update` returns the next session; the plugin stores it.
777
+ */
778
+ interface Session {
513
779
  selected: Id[];
514
780
  /** The annotation under the pointer (topmost hit), or null. View-model
515
781
  * state like `selected` — drives hover affordances (a redaction mark's
516
- * applied-look preview) purely from the scene. Updated on CHANGE only
782
+ * applied-look preview) purely from the scene. Updated on change only
517
783
  * (enter/leave cadence, never per-move). */
518
784
  hovered: Id | null;
785
+ /** The gesture in progress (a move, a resize, a shape being drawn), or null. */
519
786
  draft: Draft | null;
520
787
  /** Transient ghost of an in-progress markup selection (null when idle). */
521
788
  preview: MarkupPreview | null;
522
- seq: number;
523
- /** The base style new annotations inherit (per-tool `defaults` layer on top). */
524
- style: Style;
525
- /** Per-tool (keyed by subtype / tool id) property overrides for newly drawn
526
- * annotations — the SAME flat vocabulary `setProps` uses. `lineEndings` is
527
- * stored fully resolved (merged at `setDefaults` time). */
528
- defaults: Record<string, AnnotationPropsPatch>;
529
- /** Extra clickable margin (content units) around a stroke — bump it for touch. */
789
+ /**
790
+ * The object numbers this session holds for the records it creates next: a
791
+ * message's first new record takes the first, and so on. The plugin hands
792
+ * the session numbers from the document's pool before a message that may
793
+ * create (`newRecordsAtMost`).
794
+ */
795
+ objectNumbers: readonly number[];
796
+ /**
797
+ * Each tool's defaults for the annotations it draws, keyed by the tool's
798
+ * preset: the engine fields its creates state. The engine's own defaults
799
+ * fill in what they leave out.
800
+ */
801
+ defaults: Record<string, FieldValues>;
802
+ /**
803
+ * Extra clickable margin around what an annotation paints, in screen
804
+ * pixels: converted by the page's view scale, so a click reaches as far at
805
+ * every zoom. Bump it for touch.
806
+ */
530
807
  hitMargin: number;
531
- /** The free-text annotation currently in TEXT-EDIT mode (its `contentEditable`
808
+ /** The free-text annotation currently in text-edit mode (its `contentEditable`
532
809
  * is focused), or null. Distinct from `selected`: you select to move/resize,
533
810
  * you edit to type. */
534
811
  editing: Id | null;
535
812
  /** Snapping behaviour (alignment guides + rotation). */
536
813
  snap: SnapSettings;
814
+ /** Which annotations people may change by hand (editable.ts): the plugin sets it from its settings. */
815
+ editable: EditableKinds;
816
+ }
817
+ /**
818
+ * The records a gesture works on, read-only: what the engine confirmed with
819
+ * this session's pending changes on top. The plugin builds it; the core never
820
+ * stores a record.
821
+ */
822
+ interface AnnotationView {
823
+ byId: Record<Id, ModelAnnotation>;
824
+ /** Paint order: the document's order, then records created in this session. */
825
+ order: Id[];
537
826
  }
827
+ /** What every transition and read takes: the session composed with the records it works on. */
828
+ type Model = Session & AnnotationView;
538
829
  /**
539
- * Selection-chrome geometry in CONTENT units — the grab zones and the knob
830
+ * What one message changed in the records. `put` holds new or changed records
831
+ * as the user produced them; `drop` holds the ids the user deleted. The plugin
832
+ * shows them until the engine writes that carry them settle.
833
+ */
834
+ interface ChangeSet {
835
+ readonly put: readonly ModelAnnotation[];
836
+ readonly drop: readonly Id[];
837
+ /**
838
+ * What each changed record's change means to the engine, by id: the fields
839
+ * it changed, as `update` takes them. None for a new record (its create
840
+ * writes it) or for a change the engine keeps nothing of (a record handed
841
+ * to live rendering, a value set to what it was).
842
+ */
843
+ readonly patches: Readonly<Record<Id, AnnotationPatch>>;
844
+ }
845
+ /** The result of one message: the next session, the records it changed, and the engine work to do. */
846
+ interface UpdateResult {
847
+ readonly session: Session;
848
+ readonly change: ChangeSet;
849
+ readonly effects: readonly Effect[];
850
+ }
851
+ /**
852
+ * Selection-chrome geometry in content units — the grab zones and the knob
540
853
  * stalk. The core is unit-agnostic and zoom-free: callers own the px→content
541
854
  * conversion (settings are CSS px; divide by the page's view scale), so grab
542
855
  * zones stay screen-constant across zoom.
543
856
  */
544
- interface ChromeGeom {
857
+ interface ChromeGeometry {
545
858
  /** Half-side of a resize/vertex handle's square grab zone. */
546
859
  handleTol: number;
547
860
  /** Half-side of the rotate knob's square grab zone. */
548
861
  knobTol: number;
549
862
  /** How far the rotate knob hangs off the selection edge. */
550
863
  knobOffset: number;
864
+ /** `false`: the selection has no rotation handle to grab (it isn't drawn either). */
865
+ rotationHandle?: boolean;
551
866
  }
552
867
  interface PointerInput {
553
- pon: PageObjectNumber;
554
- point: Vec;
868
+ /** The page the sample resolved against (its own page-space frame). */
869
+ page: PageRef;
870
+ point: Point$1;
555
871
  shift: boolean;
556
872
  finish?: boolean;
557
873
  /**
558
874
  * The page's content box (`{0, 0, crop.width, crop.height}`) — when present,
559
- * gestures are CLAMPED to it: a move keeps the selection's bounds inside the
875
+ * gestures are clamped to it: a move keeps the selection's bounds inside the
560
876
  * page (sliding along the edge when the pointer overshoots), resize/create
561
877
  * points pin to the edge. Annotations are page-bound; the pointer isn't.
562
878
  */
@@ -566,34 +882,41 @@ interface PointerInput {
566
882
  * exactly like `pageBox` (the caller converts its CSS-px settings by the
567
883
  * page's view scale at dispatch). Absent → `DEFAULT_CHROME_GEOM`.
568
884
  */
569
- chrome?: ChromeGeom;
885
+ chrome?: ChromeGeometry;
570
886
  /**
571
- * The page's TOTAL display rotation (document /Rotate + view rotation) at the
572
- * gesture — per-event environmental context like `pageBox`. A creation DOWN
887
+ * The page's total display rotation (document /Rotate + view rotation) at the
888
+ * gesture — per-event environmental context like `pageBox`. A creation down
573
889
  * captures it on the draft (an `upright` commit counter-rotates against how
574
- * the page was DISPLAYED); edit gestures read it per sample, paired with
890
+ * the page was displayed); edit gestures read it per sample, paired with
575
891
  * `scale`, so screen-anchored (`noZoom`/`noRotate`) annotations hit-test and
576
- * clamp at their EFFECTIVE geometry. Content space itself never rotates.
892
+ * clamp at their effective geometry. Page space itself never rotates.
577
893
  */
578
894
  displayRotation?: PageRotation;
579
895
  /**
580
- * The page's zoom RELATIVE to its 100% baseline at the event — the other
896
+ * Screen pixels per page unit at the event: the page's view scale. A
897
+ * setting in screen pixels (`snap.alignmentThreshold`) is converted by it,
898
+ * so snapping feels the same at every zoom. Absent → 1.
899
+ */
900
+ scale?: number;
901
+ /**
902
+ * The page's zoom relative to its 100% baseline at the event — the other
581
903
  * half of the {@link ViewEnv} pair with `displayRotation` (dimensionless,
582
- * `transform.zoom`; NOT the px-per-point scale the caller uses for its
904
+ * `transform.zoom`; not the px-per-point scale the caller uses for its
583
905
  * CSS-px chrome conversion). Absent → 1 (headless callers; screen-anchored
584
906
  * bodies then hit-test at rect size).
585
907
  */
586
908
  zoom?: number;
587
909
  /**
588
- * Counter-rotate the created annotation so it reads upright at
589
- * `displayRotation` — the authoring TOOL's `upright` policy, resolved by the
590
- * caller (the core knows subtypes, not tools). Box kinds only (free-text /
591
- * stamp are the ones with a natural reading orientation); vertex kinds and
592
- * callouts ignore it.
910
+ * Lay the created annotation out as the author sees the page at
911
+ * `displayRotation` — the authoring tool's `upright` policy, resolved by the
912
+ * caller (the core knows subtypes, not tools). A box counter-rotates so it
913
+ * reads upright (free text, stamps); a clicked line points the way its
914
+ * `ClickCreate` says on screen. A dragged line and a callout's leader
915
+ * follow the pointer and ignore it.
593
916
  */
594
917
  upright?: boolean;
595
918
  /**
596
- * Ids invisible to hit-testing and marquee for THIS event — per-event
919
+ * Ids invisible to hit-testing and marquee for this event — per-event
597
920
  * environmental context like `pageBox`. The caller (plugin shell) resolves
598
921
  * its engaged Behaviors (form widgets under a fill tool render their own
599
922
  * DOM and must not select/move), so the core stays behavior-agnostic.
@@ -601,17 +924,19 @@ interface PointerInput {
601
924
  inert?: ReadonlySet<Id>;
602
925
  }
603
926
  /**
604
- * What a bare CLICK (a press-release under the drag threshold) creates for a
605
- * tool: a default-size box with an EXPLICIT anchor (`center` unless stated —
606
- * free text declares `top-left` so the box hangs where you'll type,
607
- * display-frame-aware under `upright`), or a default-length line from the
608
- * point (`angleDeg` 0 = rightward, CW-positive in y-down space). Resolved
609
- * from the TOOL by the caller and passed on the message — the core knows
610
- * subtypes, not tools (the `upright` pattern). `false` suppresses a kind's
611
- * own click fallback (free text always click-creates by default).
612
- * Anchoring is policy DATA, never inferred from the kind — the same policy
613
- * drives annotation commits, footprint ghosts, and form-field placement
614
- * (see `resolveClickPlacement`).
927
+ * What a bare click (a press-release under the drag threshold) creates for a
928
+ * tool, in PDF points:
929
+ * - a box, `width` × `height`, whose `anchor` sits on the click: its middle
930
+ * (`center`, the default) or its top-left corner (`top-left`: free text
931
+ * hangs where you'll type);
932
+ * - a line, `length` long, pointing `rotation` degrees clockwise from
933
+ * rightward, whose `anchor` sits on the click: its middle (`center`, the
934
+ * default), its start, or its end (an arrow's tip).
935
+ * Under an `upright` tool the anchor, the box's sides and the line's
936
+ * direction are as the person sees the turned page. Resolved from the tool
937
+ * by the caller and passed on the message: the core knows kinds, not tools.
938
+ * The same policy drives the commit, the tool's ghost and a form field's
939
+ * placement (see `resolveClickPlacement`).
615
940
  */
616
941
  type ClickCreate = {
617
942
  width: number;
@@ -619,20 +944,40 @@ type ClickCreate = {
619
944
  anchor?: 'center' | 'top-left';
620
945
  } | {
621
946
  length: number;
622
- angleDeg?: number;
947
+ rotation?: number;
948
+ anchor?: 'center' | 'start' | 'end';
623
949
  };
624
- type Msg = {
625
- t: 'editPointer';
950
+ /**
951
+ * Where a create gesture puts what it makes, on the page and slid inside it:
952
+ * a box and the turn an `upright` tool gives it (degrees clockwise, 0 for
953
+ * none), or a segment from `a` to `b`. A click's comes from the tool's
954
+ * {@link ClickCreate}, a drag's from the dragged points. It says nothing of
955
+ * what is drawn there: each shape family turns it into its shape
956
+ * (`ShapeFamily.placed`), and a form field takes the box as its bounds.
957
+ */
958
+ type Placement = {
959
+ kind: 'box';
960
+ rect: Rect;
961
+ rot: number;
962
+ } | {
963
+ kind: 'segment';
964
+ a: Point$1;
965
+ b: Point$1;
966
+ };
967
+ type Message = {
968
+ type: 'editPointer';
626
969
  phase: 'down' | 'move' | 'up';
627
970
  in: PointerInput;
628
971
  } | {
629
- t: 'marqueePointer';
972
+ type: 'marqueePointer';
630
973
  phase: 'down' | 'move' | 'up';
631
974
  in: PointerInput;
632
975
  } | {
633
- t: 'createPointer';
976
+ type: 'createPointer';
977
+ measure?: MeasurementAppearance;
978
+ capture?: string;
634
979
  phase: 'down' | 'move' | 'up';
635
- subtype: Subtype;
980
+ subtype: KindName;
636
981
  /** The authoring tool's `defaults` key (see {@link Draft}). Defaults to `subtype`. */
637
982
  preset?: string;
638
983
  /** PDF intent carried by an ink authoring preset. */
@@ -648,190 +993,171 @@ type Msg = {
648
993
  straightenInk?: InkStraightenOptions;
649
994
  in: PointerInput;
650
995
  } | {
651
- t: 'finishInkDraft';
996
+ type: 'finishInkDraft';
652
997
  } | {
653
- t: 'finishCreationDraft';
998
+ type: 'finishCreationDraft';
654
999
  } | {
655
- t: 'createCaret';
656
- pon: PageObjectNumber;
1000
+ type: 'createCaret';
1001
+ page: PageRef;
657
1002
  anchor: TextEndAnchor;
658
1003
  flags?: Partial<AnnotationFlags$1>;
659
1004
  } | {
660
- t: 'createReplaceText';
661
- pon: PageObjectNumber;
662
- quads: TextQuad$1[];
1005
+ type: 'createReplaceText';
1006
+ page: PageRef;
1007
+ quads: Quad$1[];
663
1008
  anchor: TextEndAnchor;
664
1009
  preset?: string;
665
1010
  } | {
666
- t: 'createMarkup';
667
- subtype: Subtype;
668
- pon: PageObjectNumber;
669
- quads: TextQuad$1[];
1011
+ type: 'createMarkup';
1012
+ subtype: KindName;
1013
+ page: PageRef;
1014
+ quads: Quad$1[];
670
1015
  preset?: string;
671
1016
  /** The tool's `/F` seed — merged over {@link DRAWN_FLAGS} at commit. */
672
1017
  flags?: Partial<AnnotationFlags$1>;
673
1018
  } | {
674
- t: 'setMarkupPreview';
675
- subtype: Subtype;
676
- quadsByPage: Record<number, TextQuad$1[]>;
1019
+ type: 'setMarkupPreview';
1020
+ subtype: KindName;
1021
+ /** Per-page quads keyed by `page.pageObjectNumber` (a lookup, not an address). */
1022
+ quadsByPage: Record<number, Quad$1[]>;
677
1023
  preset?: string;
678
1024
  } | {
679
- t: 'clearMarkupPreview';
1025
+ type: 'clearMarkupPreview';
680
1026
  } | {
681
- t: 'deselect';
1027
+ type: 'deselect';
682
1028
  ids?: Id[];
683
1029
  } |
684
1030
  /** Pointer entered/left an annotation (topmost hit id, or null). Pure state. */
685
1031
  {
686
- t: 'hover';
1032
+ type: 'hover';
687
1033
  id: Id | null;
688
- } |
689
- /** Force/clear session visibility for specific annotations (the actions
690
- * plane's Hide sink). Hiding also clears transient engagement (selection,
691
- * editing, hover) for the hidden ids. Unknown ids no-op. Zero effects. */
692
- /** Drop overrides for truly DELETED annotations (never for reloads). */
693
- {
694
- t: 'select';
1034
+ } | {
1035
+ type: 'select';
695
1036
  ids: Id[];
696
1037
  add?: boolean;
697
1038
  } | {
698
- t: 'setProps';
699
- patch: AnnotationPropsPatch;
1039
+ type: 'setFields';
1040
+ patches: Readonly<Record<Id, FieldValues>>;
1041
+ } | {
1042
+ type: 'setTextFormat';
1043
+ format: 'bold' | 'italic' | 'underline';
1044
+ on: boolean;
1045
+ } | {
1046
+ type: 'setLink';
1047
+ target: PdfLinkTarget | null;
700
1048
  } | {
701
- t: 'setFlags';
1049
+ type: 'setFlags';
702
1050
  patch: Partial<AnnotationFlags$1>;
703
1051
  ids?: Id[];
704
1052
  } | {
705
- t: 'setDefaults';
706
- subtype: Subtype;
707
- patch: AnnotationPropsPatch;
1053
+ type: 'setDefaults';
1054
+ preset: string;
1055
+ patch: FieldValues;
708
1056
  } | {
709
- t: 'setSnap';
1057
+ type: 'setSnap';
710
1058
  patch: Partial<SnapSettings>;
711
1059
  } | {
712
- t: 'rotate90';
713
- } | {
714
- t: 'resetRotation';
1060
+ type: 'rotateSelection';
1061
+ degrees: 90 | -90;
715
1062
  } | {
716
- t: 'delete';
1063
+ type: 'resetRotation';
717
1064
  } | {
718
- t: 'cancel';
1065
+ type: 'delete';
719
1066
  } | {
720
- t: 'loaded';
721
- annots: Annot[];
1067
+ type: 'cancel';
722
1068
  } |
723
- /**
724
- * Whole-document hydration ingest (and desync re-ingest): the snapshot is
725
- * the committed TRUTH. Incoming annots overwrite by id (gesture-locked ids
726
- * excepted, as in `upsert`); committed model entries ABSENT from the
727
- * snapshot are reaped — they were deleted while we could not watch.
728
- * Uncommitted `tmp:` drafts and gesture-locked ids are never reaped, and
729
- * an in-progress draft survives (unlike `remove`). `bumpAp` marks a
730
- * desync re-ingest: rasters may have changed invisibly during the gap,
731
- * so every replaced annotation re-fetches once.
732
- */
1069
+ /** These records left the view (deleted elsewhere, a refused create): drop every reference to them. */
733
1070
  {
734
- t: 'hydrated';
735
- annots: Annot[];
736
- bumpAp?: boolean;
737
- } | {
738
- t: 'created';
739
- tempId: Id;
740
- id: Id;
741
- ref: AnnotationRef;
742
- } | {
743
- t: 'createFailed';
744
- tempId: Id;
745
- } | {
746
- t: 'upsert';
747
- annots: Annot[];
748
- bumpAp?: boolean;
749
- } | {
750
- t: 'bumpAp';
751
- ids: Id[];
752
- } | {
753
- t: 'remove';
1071
+ type: 'forget';
754
1072
  ids: Id[];
755
1073
  } | {
756
- t: 'beginTextEdit';
1074
+ type: 'beginTextEdit';
757
1075
  id: Id;
758
1076
  } | {
759
- t: 'setText';
1077
+ type: 'setText';
760
1078
  id: Id;
761
1079
  text: string;
762
1080
  } | {
763
- t: 'setRichText';
1081
+ type: 'setRichText';
764
1082
  id: Id;
765
1083
  doc: RichTextDocumentInput;
766
1084
  } | {
767
- t: 'endTextEdit';
1085
+ type: 'endTextEdit';
768
1086
  };
769
1087
  type Effect = {
770
- fx: 'create';
1088
+ type: 'captured';
1089
+ tool: string;
1090
+ page: PageRef;
1091
+ geometry: Shape;
1092
+ } |
1093
+ /** Write a new record: the draft its annotation was read from. Its ref names the object number it takes. */
1094
+ {
1095
+ type: 'create';
771
1096
  id: Id;
772
- } | {
773
- fx: 'createGroup';
774
- primary: Id;
775
- members: Id[];
1097
+ draft: AnnotationDraft;
776
1098
  } |
777
- /** `apChanged` is set (to `true`) ONLY when this patch INVALIDATED a baked
778
- * raster — the annotation stayed `baked` (an opaque-body kind) and the edit
779
- * resized its `/AP` frame, so the engine's re-bake produces new content (in
780
- * practice: a stamp resize). The shell's resolve handler then turns it into
781
- * an `upsert` with `bumpAp`. Absent for everything else — moves/rotations
782
- * (the blit repositions the same pixels) and any kind that flipped to
783
- * `vector` (it renders live; the raster stops mattering) — so those keep the
784
- * bare `{ fx, id }` shape and trigger no appearance re-fetch. */
1099
+ /** Write one record's change: its entry in the change set's `patches`. `update`
1100
+ * asks for it for every changed record that has one, except typed text, which
1101
+ * its `text` effect writes. Whether the engine's re-baked appearance differs
1102
+ * is the engine's answer, not the core's guess. */
785
1103
  {
786
- fx: 'patch';
1104
+ type: 'patch';
787
1105
  id: Id;
788
- scope: PatchScope;
789
- apChanged?: true;
1106
+ patch: AnnotationPatch;
790
1107
  } |
791
- /** A `/F`-only engine write for one committed annotation: the shell emits a
792
- * flags-only patch (the model already holds the merged flags) and re-syncs
793
- * PRESERVING the render source — flags never re-bake an appearance. */
1108
+ /** Write the edited text of one free-text record. While the user types, the
1109
+ * plugin keeps amending one change with the latest text, and sends it after
1110
+ * a pause. */
794
1111
  {
795
- fx: 'flags';
1112
+ type: 'text';
796
1113
  id: Id;
797
1114
  } |
798
- /** The parent's `link` prop changed on a NON-link kind: reconcile its
799
- * attached link children (create / retarget / delete) against the desired
800
- * state derived from `link` + the parent's geometry. Declarative — the
801
- * shell's reconciler is the only code that spells out child operations.
802
- * (Geometry commits don't emit this; the shell re-runs the reconciler on
803
- * any `patch` of an annotation that has `linkRefs`.) */
1115
+ /** The parent's `link` prop changed on a non-link kind: reconcile its
1116
+ * attached link children (create / retarget / delete) toward `target`
1117
+ * (null = remove them). Declarative — the plugin's reconciler is the only
1118
+ * code that spells out child operations; parents store no link value, the
1119
+ * committed children are the truth (`linkOf` reads them back). Geometry
1120
+ * commits don't emit this: the plugin's write of a `patch` brings an
1121
+ * annotation's attached children along. */
804
1122
  {
805
- fx: 'syncLink';
1123
+ type: 'syncLink';
806
1124
  id: Id;
807
1125
  target: PdfLinkTarget | null;
808
- } | {
809
- fx: 'delete';
810
- ref: AnnotationRef;
1126
+ } |
1127
+ /** Delete one record from the document. */
1128
+ {
1129
+ type: 'delete';
1130
+ id: Id;
811
1131
  };
812
1132
  /** Per-annotation render data — its content geometry + style + live state. */
813
1133
  interface RenderItem {
814
1134
  id: Id;
815
1135
  ref: AnnotationRef | null;
816
- subtype: Subtype;
817
- geom: Geom;
818
1136
  /**
819
- * The VISUAL box (geometry + stroke + line endings) in content space — the SAME
820
- * `geomVisualBounds` that feeds the engine `/Rect`. The renderer paints into THIS
821
- * box and does no bounds math of its own, so the on-screen box and the baked
822
- * appearance can never drift (the v2 "patch computes the rect" rule).
1137
+ * The annotation it draws, as the user sees it (the record's, a gesture
1138
+ * in progress aside: that is in `geometry` and `box`). Absent for what isn't
1139
+ * made yet: a drawing in progress or a tool's ghost.
1140
+ */
1141
+ annotation?: Annotation;
1142
+ subtype: KindName;
1143
+ geometry: Shape;
1144
+ /**
1145
+ * The box the live drawing is painted into (its family's `drawnBounds`:
1146
+ * geometry, stroke, endings, cloud): a box's or caret's own, before its
1147
+ * turn (`rot`), the page's for the rest. The renderer paints into this box
1148
+ * and does no bounds math of its own.
823
1149
  */
824
1150
  box: Rect;
825
1151
  /**
826
- * Content-space box the engine appearance raster occupies (the AP `/Rect`),
1152
+ * Page-space box the engine appearance raster occupies (the AP `/Rect`),
827
1153
  * with the live move gesture applied — so a baked annotation's bitmap follows
828
1154
  * a drag. Only meaningful when `source === 'baked'`; absent otherwise.
829
1155
  */
830
1156
  apBox?: Rect;
831
1157
  /**
832
1158
  * Rotation (deg, CW) to apply to the baked raster as a view transform —
833
- * exactly the rotation the engine STRIPPED from it (see `Annot.apRot`).
834
- * Live for `opaqueBody` kinds (a stamp spins with the rotate gesture);
1159
+ * exactly the rotation the engine stripped from it (see `ModelAnnotation.apRot`).
1160
+ * Live for `rasterOnly` kinds (a stamp spins with the rotate gesture);
835
1161
  * absent when the raster already contains its rotation (vertex kinds).
836
1162
  */
837
1163
  apRot?: number;
@@ -840,21 +1166,67 @@ interface RenderItem {
840
1166
  * text, text/choice widgets). Lets a behavior renderer's focused editor
841
1167
  * match the baked appearance's font. */
842
1168
  text?: TextStyle;
843
- source: 'baked' | 'vector' | 'ghost';
1169
+ /**
1170
+ * How it paints: the engine's raster (`baked`), live from its description
1171
+ * (`vector`), or an annotation not made yet, drawn as it will be: a drawing
1172
+ * in progress (`draft`, solid) or the tool's ghost, what a click would
1173
+ * place (`ghost`, see-through by `ghostOpacity`).
1174
+ */
1175
+ source: 'baked' | 'vector' | 'draft' | 'ghost';
1176
+ /**
1177
+ * The version of the record's engine picture (`ModelAnnotation.pictureVersion`):
1178
+ * a picture drawn at another version shows another look of it. Absent for
1179
+ * what isn't made yet.
1180
+ */
1181
+ pictureVersion?: number;
1182
+ /**
1183
+ * A lent record's live drawing, by its lender (`ModelAnnotation.painter`),
1184
+ * upright in `box` (where a gesture has it): `scene(item)` draws it in place
1185
+ * of the kind's own. Made when it's first asked for, not before.
1186
+ */
1187
+ lentScene?: () => readonly SceneNode[];
1188
+ /** How opaque a ghost paints (0–1); a painter lets `--epdf-ghost-opacity` win over it. */
1189
+ ghostOpacity?: number;
844
1190
  selected: boolean;
845
1191
  /** The pointer is over this annotation — scene-level hover affordances
846
1192
  * (e.g. a redaction mark previews its applied look). */
847
1193
  hovered?: boolean;
848
- /** Redaction label projection (redact kind only) — see {@link Annot.label}. */
1194
+ /** Redaction label projection (redact kind only) — see {@link ModelAnnotation.label}. */
849
1195
  label?: {
850
1196
  text: string;
851
1197
  repeat: boolean;
852
1198
  };
853
1199
  /**
854
- * Applied rotation (deg, CW), or 0/undefined. For BOX kinds (`rect`/`text`)
855
- * `box` is the UNROTATED visual box and the renderer applies this rotation
856
- * about its centre (CSS/SVG transform). For VERTEX kinds the geometry is
857
- * already rotated, so this is advisory only — the renderer must NOT re-apply it.
1200
+ * The box a painter draws into: `box` before its turn, and that turn,
1201
+ * degrees clockwise about the box's middle. Everything the item draws sits
1202
+ * upright inside it: its scene, a custom look, or its raster (`raster`).
1203
+ * `scale` is how large the page draws the item relative to its own size:
1204
+ * 1, except for a body that keeps its size on screen (`noZoom`), drawn
1205
+ * smaller on the page as people zoom in. Made from `box` and `rot` by the
1206
+ * core; painters read this, not `rot`.
1207
+ */
1208
+ frame: {
1209
+ box: Rect;
1210
+ rotation: number;
1211
+ scale: number;
1212
+ };
1213
+ /**
1214
+ * The engine's raster inside the frame, for an item that has one: its box
1215
+ * relative to the frame's top-left before the frame's turn, and its own turn
1216
+ * about its middle. Made from `apBox` and `apRot`; null without a raster box.
1217
+ */
1218
+ raster: {
1219
+ box: Rect;
1220
+ rotation: number;
1221
+ } | null;
1222
+ /** A note's or file attachment's icon (`/Name`), which its live drawing draws. */
1223
+ icon?: NoteIcon | FileAttachmentIcon;
1224
+ measure?: MeasurementAppearance;
1225
+ /**
1226
+ * Applied rotation (deg, CW), or 0/undefined. For box kinds (`box`/`text-box`)
1227
+ * `box` is the unrotated visual box and the renderer applies this rotation
1228
+ * about its centre (CSS/SVG transform). For vertex kinds the geometry is
1229
+ * already rotated, so this is advisory only — the renderer must not re-apply it.
858
1230
  */
859
1231
  rot?: number;
860
1232
  /**
@@ -864,8 +1236,8 @@ interface RenderItem {
864
1236
  */
865
1237
  blend?: Exclude<BlendMode$1, 'normal'>;
866
1238
  }
867
- /** The dumb draw vocabulary the framework renderer maps to SVG (content space).
868
- * A CLOSED node (rect, ellipse, closed poly) takes the annotation's FILL colour;
1239
+ /** The dumb draw vocabulary the framework renderer maps to SVG (page space).
1240
+ * A closed node (rect, ellipse, closed poly) takes the annotation's fill colour;
869
1241
  * an open node (line, open poly — open arrows, butt, slash) is stroke-only. The
870
1242
  * stroke colour applies to every node. Closed-ness is the only fill signal. */
871
1243
  type RenderNode = {
@@ -876,11 +1248,11 @@ type RenderNode = {
876
1248
  rect: Rect;
877
1249
  } | {
878
1250
  kind: 'line';
879
- a: Vec;
880
- b: Vec;
1251
+ a: Point$1;
1252
+ b: Point$1;
881
1253
  } | {
882
1254
  kind: 'poly';
883
- points: Vec[];
1255
+ points: Point$1[];
884
1256
  closed: boolean;
885
1257
  } | {
886
1258
  kind: 'path';
@@ -888,7 +1260,7 @@ type RenderNode = {
888
1260
  };
889
1261
  /**
890
1262
  * How to paint one node. The pure core fills this in (per kind/subtype), and a
891
- * framework renderer applies it verbatim — so ALL appearance logic (markup fill vs
1263
+ * framework renderer applies it verbatim — so all appearance logic (markup fill vs
892
1264
  * stroke, blend, dash, derived widths) lives once, in the portable core, not in
893
1265
  * every framework. Omitted `fill`/`stroke` mean none.
894
1266
  */
@@ -899,16 +1271,17 @@ interface Paint {
899
1271
  opacity?: number;
900
1272
  dash?: number[];
901
1273
  blend?: Exclude<BlendMode$1, 'normal'>;
902
- cap?: 'round';
1274
+ lineCap?: 'round';
903
1275
  join?: 'round';
1276
+ fillRule?: 'evenodd';
904
1277
  }
905
1278
  /**
906
1279
  * A fully-painted draw node: geometry + paint. `scene(item)` returns these and a
907
- * per-framework painter maps each to ONE element, applying `paint` — the entire
1280
+ * per-framework painter maps each to one element, applying `paint` — the entire
908
1281
  * surface a new framework renderer must implement. Supersedes the geometry-only
909
1282
  * `RenderNode` for rendering; `geomScene` stays the internal geometry helper.
910
1283
  */
911
- type SceneNode = {
1284
+ type SceneNode = ({
912
1285
  kind: 'rect';
913
1286
  rect: Rect;
914
1287
  paint: Paint;
@@ -918,12 +1291,12 @@ type SceneNode = {
918
1291
  paint: Paint;
919
1292
  } | {
920
1293
  kind: 'line';
921
- a: Vec;
922
- b: Vec;
1294
+ a: Point$1;
1295
+ b: Point$1;
923
1296
  paint: Paint;
924
1297
  } | {
925
1298
  kind: 'poly';
926
- points: Vec[];
1299
+ points: Point$1[];
927
1300
  closed: boolean;
928
1301
  paint: Paint;
929
1302
  } | {
@@ -931,17 +1304,49 @@ type SceneNode = {
931
1304
  d: string;
932
1305
  paint: Paint;
933
1306
  } |
934
- /** Painted (non-interactive) text — `at` is the BASELINE start point in
1307
+ /** Painted (non-interactive) text — `at` is the baseline start point in
935
1308
  * content units. Editable text (free text) stays a framework element;
936
1309
  * this is for pure pixels, e.g. a redaction label preview. */
937
1310
  {
938
1311
  kind: 'text';
939
- at: Vec;
1312
+ at: Point$1;
940
1313
  text: string;
941
1314
  fontSize: number;
942
1315
  fontFamily?: string;
1316
+ rotation?: number;
1317
+ paint: Paint;
1318
+ } |
1319
+ /** Painted text placed character by character: `offsets[i]` is how far
1320
+ * along the baseline from `at` character `i` starts, so each glyph lands
1321
+ * where the PDF puts it (a form widget's painted text). `rotation` turns
1322
+ * the run about `at`, degrees clockwise. `program` is the face it draws
1323
+ * in, for whatever draws it to mount (`fontFamily` names it first). */
1324
+ {
1325
+ kind: 'glyphs';
1326
+ at: Point$1;
1327
+ text: string;
1328
+ offsets: number[];
1329
+ fontSize: number;
1330
+ fontFamily: string;
1331
+ fontWeight?: number;
1332
+ fontStyle?: 'normal' | 'italic';
1333
+ rotation?: number;
1334
+ program?: SceneFontProgram;
943
1335
  paint: Paint;
1336
+ }) & {
1337
+ /** Drawn only inside this upright rectangle (a widget's text box). */
1338
+ clip?: Rect;
944
1339
  };
1340
+ /**
1341
+ * A font program a scene's text draws in: mounted under its own family, in
1342
+ * its own weight and style, so the browser takes it as it is.
1343
+ */
1344
+ interface SceneFontProgram {
1345
+ readonly family: string;
1346
+ readonly weight: number;
1347
+ readonly style: 'normal' | 'italic';
1348
+ load(): Promise<Uint8Array>;
1349
+ }
945
1350
  /** Pure geometry settings for recognising and axis-snapping a freehand stroke. */
946
1351
  interface InkStraightenOptions {
947
1352
  /** Maximum `max perpendicular deviation / endpoint distance`. */
@@ -949,39 +1354,39 @@ interface InkStraightenOptions {
949
1354
  /** Degrees from horizontal/vertical within which the line snaps to that axis. */
950
1355
  axisSnapDegrees: number;
951
1356
  }
1357
+ /** What a selection handle reshapes: a box's corner or side, or one point of a line or a polygon. */
1358
+ type HandleRole = 'corner' | 'side' | 'point';
952
1359
  type ChromeNode = {
953
1360
  kind: 'outline';
954
1361
  rect: Rect;
955
1362
  } | {
956
1363
  kind: 'obb';
957
- corners: [Vec, Vec, Vec, Vec];
1364
+ corners: [Point$1, Point$1, Point$1, Point$1];
958
1365
  angle: number;
959
1366
  } | {
960
1367
  kind: 'handle';
961
- at: Vec;
1368
+ at: Point$1;
962
1369
  cursor: Cursor;
963
1370
  rot?: number;
1371
+ role: HandleRole;
1372
+ active: boolean;
964
1373
  } | {
965
1374
  kind: 'rotate-knob';
966
- at: Vec;
967
- from: Vec;
1375
+ at: Point$1;
1376
+ from: Point$1;
968
1377
  } | {
969
1378
  kind: 'guide';
970
1379
  axis: 'x' | 'y';
971
1380
  at: number;
972
1381
  lo: number;
973
1382
  hi: number;
974
- } | {
975
- kind: 'angle-chip';
976
- at: Vec;
977
- angle: number;
978
1383
  } | {
979
1384
  kind: 'rotate-guides';
980
- center: Vec;
1385
+ center: Point$1;
981
1386
  angle: number;
982
1387
  lines: Array<{
983
- a: Vec;
984
- b: Vec;
1388
+ a: Point$1;
1389
+ b: Point$1;
985
1390
  role: 'axis' | 'indicator';
986
1391
  }>;
987
1392
  } | {
@@ -989,6 +1394,79 @@ type ChromeNode = {
989
1394
  rect: Rect;
990
1395
  };
991
1396
  //#endregion
1397
+ //#region src/update/session.d.ts
1398
+ declare const initialSession: Session;
1399
+ /** A tool's defaults, by its preset: the engine fields its creates state. */
1400
+ declare const defaultsFor: (model: Model, preset: string) => FieldValues;
1401
+ /**
1402
+ * What a create from a tool starts from, before it has a shape: the tool's
1403
+ * defaults (by its preset) over the engine's for its `kind`, read as an
1404
+ * annotation. A ghost is drawn with its style (`styleOf`).
1405
+ */
1406
+ declare const toolAnnotation: (model: Model, kind: string, preset?: string) => Annotation;
1407
+ //#endregion
1408
+ //#region src/update/edit.d.ts
1409
+ /**
1410
+ * The live rotation of a rotate draft, snapping applied — the one angle rule
1411
+ * shared by the preview (`effGeom`), the commit (`editUp`) and the angle chip,
1412
+ * so they can never disagree. The selection's absolute angle (a single member's
1413
+ * `rot` + the raw pointer delta; a group's raw delta from 0) locks onto the
1414
+ * configured angles within the threshold; `free` (shift held) bypasses.
1415
+ * `delta` is what `geomRotateAbout` applies; `angle` is what the chip shows.
1416
+ */
1417
+ declare function rotateDraftDelta(model: Model, draft: Extract<Draft, {
1418
+ kind: 'rotate';
1419
+ }>): {
1420
+ delta: number;
1421
+ angle: number;
1422
+ snapped: boolean;
1423
+ };
1424
+ //#endregion
1425
+ //#region src/flags.d.ts
1426
+ /** The `/F` every freshly drawn annotation starts with: `print` set (Acrobat
1427
+ * parity — without it the annotation silently disappears when printed). */
1428
+ declare const DRAWN_FLAGS: AnnotationFlags;
1429
+ /** All ten flags, for iteration/equality — kept in the primitives' order. */
1430
+ declare const FLAG_KEYS: ReadonlyArray<keyof AnnotationFlags>;
1431
+ declare const flagsEqual: (left: AnnotationFlags, right: AnnotationFlags) => boolean;
1432
+ /** Merge a partial write over the current flags. */
1433
+ declare const mergeFlags: (base: AnnotationFlags, patch: Partial<AnnotationFlags>) => AnnotationFlags;
1434
+ /**
1435
+ * On-screen visibility. `hidden` beats everything; `noView` hides unless
1436
+ * `toggleNoView` and the annotation is engaged — the spec says hover/selection,
1437
+ * and v1 uses selection (the model tracks no hover).
1438
+ */
1439
+ declare const viewable: (flags: AnnotationFlags, engaged?: boolean) => boolean;
1440
+ /** Any pointer interaction (click-to-select, hover). ReadOnly kills it. */
1441
+ declare const interactive: (flags: AnnotationFlags) => boolean;
1442
+ /** The part of a ModelAnnotation these predicates read. */
1443
+ interface FlagBearer {
1444
+ /** The record's annotation: its kind and `/F` flags are read off it. */
1445
+ annotation: Annotation;
1446
+ /** Session authority projected at ingest (permissions.md). Absent =
1447
+ * unstamped (drafts, wildcard local engines, tests) = allowed. */
1448
+ authority?: {
1449
+ update: boolean;
1450
+ delete: boolean;
1451
+ };
1452
+ }
1453
+ /** Interaction gate for a concrete annotation: widget kinds ignore `readOnly`
1454
+ * (ISO 32000 — a ReadOnly form field must still be movable by a form designer;
1455
+ * the form-filling layer enforces field ReadOnly itself). */
1456
+ declare const annotInteractive: (record: FlagBearer) => boolean;
1457
+ /**
1458
+ * Geometry/style mutations — `locked` freezes the object, not its contents
1459
+ * (that's `lockedContents`), and the session must hold update authority over
1460
+ * the record. One predicate for hit-test, chrome, and props alike, so a
1461
+ * record you may not edit renders and behaves exactly like a locked one.
1462
+ */
1463
+ declare const annotTransformable: (record: FlagBearer) => boolean;
1464
+ /** Deletion — the delete half of the authority split (a narrowed grant can
1465
+ * allow update but not delete, or vice versa). Flags gate like transforms. */
1466
+ declare const annotDeletable: (record: FlagBearer) => boolean;
1467
+ /** `/Contents` text edits — the contents counterpart of `locked`. */
1468
+ declare const annotContentsEditable: (record: FlagBearer) => boolean;
1469
+ //#endregion
992
1470
  //#region src/anchor.d.ts
993
1471
  /** Which display factors this annotation is exempt from: `zoom` =
994
1472
  * screen-constant size, `upright` = screen-constant orientation. */
@@ -997,46 +1475,46 @@ interface AnchorMode {
997
1475
  upright: boolean;
998
1476
  }
999
1477
  /**
1000
- * The anchor behaviors of one annotation: its `/F` flags OR'd with the kind's
1478
+ * The anchor behaviors of one annotation: its `/F` flags or'd with the kind's
1001
1479
  * statics — the spec's "Text (note) annotations behave as if NoZoom and
1002
- * NoRotate are ALWAYS set", expressed as kind caps. Null when not anchored.
1480
+ * NoRotate are always set", expressed as kind caps. Null when not anchored.
1003
1481
  */
1004
- declare function anchorModeOf(a: FlagBearer): AnchorMode | null;
1005
- /** The fixed page point: the geometry's bounds top-left in content space
1006
- * (y-down) — which IS the spec's "upper-left corner of the annotation
1482
+ declare function anchorModeOf(record: FlagBearer): AnchorMode | null;
1483
+ /** The fixed page point: the geometry's bounds top-left in page space
1484
+ * (y-down) — which is the spec's "upper-left corner of the annotation
1007
1485
  * rectangle", since `/Rect` is emitted from these bounds. */
1008
- declare const anchorOf: (g: Geom) => Vec;
1486
+ declare const anchorOf: (geometry: Shape) => Point;
1009
1487
  /**
1010
- * THE effective content-space geometry of a screen-anchored body at `view`:
1488
+ * The effective page-space geometry of a screen-anchored body at `view`:
1011
1489
  * the stored geometry scaled by `1/max(zoom, 1)` about the anchor (`zoom`
1012
1490
  * exemption, Adobe-clamped) and counter-rotated by `-rotation` about it
1013
1491
  * (`upright`), so that after the page's own display transform the body reads
1014
1492
  * at its 100%-zoom size and its authored tilt, hanging from the fixed anchor.
1015
- * Returns `g` unchanged when there is nothing to do — callers apply it
1493
+ * Returns `geometry` unchanged when there is nothing to do — callers apply it
1016
1494
  * unconditionally.
1017
1495
  */
1018
- declare function anchoredGeom(g: Geom, mode: AnchorMode | null, view: ViewEnv | undefined): Geom;
1496
+ declare function anchoredGeom(geometry: Shape, mode: AnchorMode | null, view: ViewEnv | undefined): Shape;
1019
1497
  /**
1020
- * The exact inverse of {@link anchoredGeom} for a VIEW-space geometry a
1021
- * gesture produced: the stored geometry whose OWN projection is `target`.
1498
+ * The exact inverse of {@link anchoredGeom} for a view-space geometry a
1499
+ * gesture produced: the stored geometry whose own projection is `target`.
1022
1500
  *
1023
1501
  * Solving `anchoredGeom(stored) = target` where the projection anchors at
1024
1502
  * `stored`'s (unknown) bounds top-left `a` gives a closed form: map `target`
1025
1503
  * through the pure linear part (scale `s` + rotate `r` about the origin),
1026
1504
  * then translate so the result's bounds top-left `b` lands where the
1027
1505
  * projection needs it — at `a = R(-r)·b / s`, the unique fixed point. This is
1028
- * what makes a released gesture commit EXACTLY what its preview showed.
1506
+ * what makes a released gesture commit exactly what its preview showed.
1029
1507
  */
1030
- declare function unanchoredGeom(target: Geom, mode: AnchorMode | null, view: ViewEnv | undefined): Geom;
1508
+ declare function unanchoredGeom(target: Shape, mode: AnchorMode | null, view: ViewEnv | undefined): Shape;
1031
1509
  /** Stroke width in the projected (view) space: a `noZoom` body's line weight
1032
- * scales with its geometry so it stays screen-constant — through the SAME
1510
+ * scales with its geometry so it stays screen-constant — through the same
1033
1511
  * factors (incl. the ≤100% clamp) the geometry projects with. */
1034
1512
  declare const anchoredStrokeWidth: (width: number, mode: AnchorMode | null, view: ViewEnv | undefined) => number;
1035
1513
  /**
1036
1514
  * The similarity image of an axis-aligned box (a baked raster's AP `/Rect`)
1037
1515
  * under the anchor projection, as the blit vocabulary expects it: an
1038
- * UNROTATED box (the projected centre + scaled size) plus the rotation to
1039
- * re-apply about its centre. `anchor` is the OWNING GEOMETRY's anchor — the
1516
+ * unrotated box (the projected centre + scaled size) plus the rotation to
1517
+ * re-apply about its centre. `anchor` is the owning geometry's anchor — the
1040
1518
  * raster rides its annotation's projection, it doesn't anchor itself. Null
1041
1519
  * when the projection is the identity.
1042
1520
  */
@@ -1045,7 +1523,7 @@ declare function anchoredBox(box: {
1045
1523
  y: number;
1046
1524
  width: number;
1047
1525
  height: number;
1048
- }, anchor: Vec, mode: AnchorMode | null, view: ViewEnv | undefined): {
1526
+ }, anchor: Point, mode: AnchorMode | null, view: ViewEnv | undefined): {
1049
1527
  box: {
1050
1528
  x: number;
1051
1529
  y: number;
@@ -1055,75 +1533,84 @@ declare function anchoredBox(box: {
1055
1533
  rot: number;
1056
1534
  } | null;
1057
1535
  //#endregion
1058
- //#region src/update.d.ts
1059
- /** The click ↔ drag threshold (content units): a press-release whose width AND
1060
- * height both stay under it is a CLICK. Exported so every gesture owner (the
1061
- * draw handler, the form plugin's place handler) shares ONE definition. */
1062
- declare const MIN_DRAG = 3;
1063
- declare const initialStyle: Style;
1536
+ //#region src/update/marquee.d.ts
1537
+ /**
1538
+ * What a box on `page` selects: every annotation it touches
1539
+ * ({@link annotsInBox}), and the rest of the group of each. The marquee and
1540
+ * the capability's `selectInRect` both select this.
1541
+ */
1542
+ declare function selectionInBox(model: Model, page: PageRef, box: Rect, inert?: ReadonlySet<Id>, view?: ViewEnv): Id[];
1543
+ //#endregion
1544
+ //#region src/update/index.d.ts
1545
+ /** The model before any record or gesture: an empty view over the initial session. */
1064
1546
  declare const initialModel: Model;
1065
1547
  /**
1066
- * Resolve a tool's effective defaults as a FULL flat props bag: the base `style`
1067
- * + the font/endings base, with the per-tool override layered on top. This is
1068
- * what a defaults-editing UI reads, and what creation projects `style`/`text`
1069
- * from (`styleFromProps` / `textStyleFromProps`).
1548
+ * Run one message. The session is returned whole; the records are returned
1549
+ * as the difference the message made, so the caller decides where they live.
1070
1550
  */
1071
- declare function defaultsFor(m: Model, subtype: Subtype): AnnotationProps;
1551
+ declare function update(model: Model, message: Message): UpdateResult;
1072
1552
  /**
1073
- * The live rotation of a rotate draft, snapping applied — the ONE angle rule
1074
- * shared by the preview (`effGeom`), the commit (`editUp`) and the angle chip,
1075
- * so they can never disagree. The selection's ABSOLUTE angle (a single member's
1076
- * `rot` + the raw pointer delta; a group's raw delta from 0) locks onto the
1077
- * configured angles within the threshold; `free` (shift held) bypasses.
1078
- * `delta` is what `geomRotateAbout` applies; `angle` is what the chip shows.
1553
+ * The most records `message` can create: how many object numbers the session
1554
+ * must hold before it runs (`Session.objectNumbers`). A pointer press or
1555
+ * release of a creating tool can finish a drawing; a move never does.
1079
1556
  */
1080
- declare function rotateDraftDelta(m: Model, d: Extract<Draft, {
1081
- g: 'rotate';
1082
- }>): {
1083
- delta: number;
1084
- angle: number;
1085
- snapped: boolean;
1086
- };
1087
- declare function update(m: Model, msg: Msg): [Model, Effect[]];
1557
+ declare function newRecordsAtMost(message: Message): number;
1558
+ /** True when every session field is the same value: a message that changed nothing. */
1559
+ declare const sameSession: (left: Session, right: Session) => boolean;
1560
+ /** The change set of a message that changed no record. */
1561
+ declare const EMPTY_CHANGE: ChangeSet;
1088
1562
  //#endregion
1089
1563
  //#region src/placement.d.ts
1090
- /** A resolved click placement: what the click will occupy, page-clamped. */
1091
- type ClickPlacement = {
1092
- kind: 'box';
1093
- rect: Rect;
1094
- /** Upright counter-rotation captured for the commit (deg CW; 0 = none). */
1095
- rot: number;
1096
- } | {
1097
- kind: 'segment';
1098
- a: Vec;
1099
- b: Vec;
1100
- };
1564
+ /** The click ↔ drag threshold (content units): a press-release that stays
1565
+ * under it is a click. Every gesture owner (the draw handler, the form
1566
+ * plugin's place handler) shares this one definition. */
1567
+ declare const MIN_DRAG = 3;
1568
+ /** What a create gesture lays: a box (shapes, text boxes, form fields) or a segment (lines). */
1569
+ type GestureForm = 'box' | 'segment';
1570
+ /** The page a placement is made on, and how the person sees it. */
1571
+ interface PlacementFrame {
1572
+ /** The page's content box: a placement slides inside it. */
1573
+ pageBox?: Rect;
1574
+ /** Lay it out as the person sees the page: the tool's `upright`. */
1575
+ upright?: boolean;
1576
+ /** How far the page shows turned (document /Rotate + view rotation, degrees clockwise). */
1577
+ displayRotation?: PageRotation;
1578
+ }
1101
1579
  /** Slide a rect (as a unit) to sit inside `box`; pins at the origin edge when
1102
1580
  * it doesn't fit. Placements are page-bound; the pointer isn't. */
1103
- declare const clampRectToBox: (r: Rect, box: Rect | undefined) => Rect;
1581
+ declare const clampRectToBox: (rect: Rect, box: Rect | undefined) => Rect;
1582
+ /** Whether a gesture from `from` to `to` is a drag: a segment once it is
1583
+ * `MIN_DRAG` long, a box once either side is. */
1584
+ declare function isDrag(form: GestureForm, from: Point, to: Point): boolean;
1104
1585
  /**
1105
- * Resolve a click-create POLICY at a content point. `anchor` defaults to
1106
- * `center`; under `upright` a box counter-rotates against the page's display
1107
- * rotation exactly as the drag commit would (centre-anchored boxes transpose
1108
- * about their centre, top-left boxes anchor in the DISPLAY frame).
1586
+ * Where a create gesture from `from` to `to` puts what it makes: the dragged
1587
+ * box or segment once it is a drag, else the tool's click default at `from`
1588
+ * (`null` when a click makes nothing). A drag stays on the page: its points
1589
+ * pin to the edge. An upright drag keeps the region the author dragged: under
1590
+ * a quarter turn the box before its turn is the dragged one with its sides
1591
+ * swapped about its middle, so the turn lands it back on that region.
1109
1592
  */
1110
- declare function resolveClickPlacement(point: Vec, policy: ClickCreate, opts?: {
1111
- pageBox?: Rect;
1112
- upright?: boolean;
1113
- displayRotation?: PageRotation;
1114
- }): ClickPlacement;
1593
+ declare function gesturePlacement(form: GestureForm, from: Point, to: Point, clickCreate: ClickCreate | false | undefined, frame?: PlacementFrame): Placement | null;
1594
+ /**
1595
+ * Where a bare click at `point` puts what the tool makes (see
1596
+ * {@link ClickCreate}), slid inside the page. Under `upright` it is laid out
1597
+ * as the person sees the page: a line's direction and a box's top-left corner
1598
+ * are the screen's, and a box turns to read upright, showing `width` ×
1599
+ * `height` as configured.
1600
+ */
1601
+ declare function resolveClickPlacement(point: Point, policy: ClickCreate, frame?: PlacementFrame): Placement;
1115
1602
  /**
1116
- * ANNOTATION-ONLY: convert a placement into the `Geom` the commit stores and
1117
- * the ghost paints, for a routing kind. This is where annotation VISUAL
1118
- * semantics live — ellipse for circles, the cloudy outer-box via
1119
- * `shapeRectFor`. Forms never call this; a field box is the placement rect
1120
- * itself. Null for kinds a click cannot author.
1603
+ * What a create from a tool makes at `placement`: its kind's shape there, as
1604
+ * the kind's shape family lays it (`ShapeFamily.placed`), or `null` when the
1605
+ * kind can't be made that way (a line from a box). `annotation` is what the
1606
+ * tool creates before it has a shape (`toolAnnotation`): its kind, and what
1607
+ * the shape takes from it (a line's endings, a circle's roundness).
1121
1608
  */
1122
- declare function clickCreateGeom(subtype: Subtype, placement: ClickPlacement, def: AnnotationProps): Geom | null;
1609
+ declare function placedShape(annotation: Annotation, placement: Placement): Shape | null;
1123
1610
  //#endregion
1124
1611
  //#region src/snap.d.ts
1125
1612
  interface SnapResult {
1126
- delta: Vec;
1613
+ delta: Point;
1127
1614
  guides: Guide[];
1128
1615
  }
1129
1616
  /**
@@ -1132,10 +1619,20 @@ interface SnapResult {
1132
1619
  * closest in-threshold pair per axis as an adjustment. Targets are the page box
1133
1620
  * (edges + center) and every non-moving annotation on the page.
1134
1621
  */
1135
- declare function computeMoveSnap(m: Model, ids: Id[], pon: number, raw: Vec, threshold: number, pageBox: Rect | undefined): SnapResult;
1622
+ declare function computeMoveSnap(model: Model, ids: Id[], page: PageRef, raw: Point, threshold: number, pageBox: Rect | undefined): SnapResult;
1136
1623
  //#endregion
1137
1624
  //#region src/view.d.ts
1138
- declare function pageItems(m: Model, pon: number, view?: ViewEnv): RenderItem[];
1625
+ /** A note's or file attachment's icon: what its live drawing draws. */
1626
+ declare const iconOf: (annotation: Annotation) => Pick<RenderItem, "icon">;
1627
+ /**
1628
+ * How an annotation not made yet paints, drawn as it will be once made: a
1629
+ * drawing in progress (`draft`) or the tool's ghost (`ghost`, what a click
1630
+ * would place). `annotation` is what the tool creates, its defaults and
1631
+ * flags; `shape` is where it goes, as the made one will store it, so a
1632
+ * screen-anchored icon shows at its size on screen, as a made one does.
1633
+ */
1634
+ declare function unmadeItem(id: Id, annotation: Annotation, shape: Shape, source: 'draft' | 'ghost', view?: ViewEnv, measure?: MeasurementAppearance): RenderItem;
1635
+ declare function pageItems(model: Model, page: PageRef, view?: ViewEnv): RenderItem[];
1139
1636
  /** One free-text box, geometry only (the live move/resize gesture applied), with
1140
1637
  * its edit flag. The framework renders an editable element here; the plugin
1141
1638
  * layers the DTO-derived text style on top. */
@@ -1143,95 +1640,125 @@ interface TextBox {
1143
1640
  id: Id;
1144
1641
  box: Rect;
1145
1642
  editing: boolean;
1146
- /** Applied rotation (deg, CW). `box` is the UNROTATED text box; the framework
1643
+ /** Applied rotation (deg, CW). `box` is the unrotated text box; the framework
1147
1644
  * rotates the editable element about its centre by this. 0/undefined = none. */
1148
1645
  rot?: number;
1149
1646
  }
1150
1647
  /** The free-text boxes on a page — the text counterpart of `pageItems`. */
1151
- declare function textBoxes(m: Model, pon: number, view?: ViewEnv): TextBox[];
1648
+ declare function textBoxes(model: Model, page: PageRef, view?: ViewEnv): TextBox[];
1152
1649
  /** The currently selected annotations as render items (live gesture applied) —
1153
1650
  * cross-page, for selection-aware toolbars (style + line-ending editing). */
1154
- declare function selectedItems(m: Model, view?: ViewEnv): RenderItem[];
1651
+ declare function selectedItems(model: Model, view?: ViewEnv): RenderItem[];
1155
1652
  /**
1156
- * The rotate knob for the current selection on `pon` — the SAME knob `chrome`
1653
+ * The rotate knob for the current selection on `page` — the same knob `chrome`
1157
1654
  * draws — or null when the selection has none (non-rotatable single, or a group
1158
1655
  * whose caps aren't rotatable). A single shape's knob hangs off its OBB top edge;
1159
- * a group's off the union box. With `pageBox` the knob is placed PAGE-BOUND
1656
+ * a group's off the union box. With `pageBox` the knob is placed page-bound
1160
1657
  * (`placeRotateKnob`: flip below / clamp inside) — off-page it would be visible
1161
1658
  * but unreachable, since pointer dispatch resolves pages by containment. Shared
1162
1659
  * by `chrome` (to draw) and `selectionAnchor` (to push the menu clear of it), so
1163
1660
  * the two can never disagree on where it is.
1164
1661
  *
1165
- * The placement DECISION is made at rest; a gesture never re-decides — it RIDES.
1662
+ * The placement decision is made at rest; a gesture never re-decides — it rides.
1166
1663
  * During a live rotate the knob starts exactly where it was grabbed (the rest
1167
- * placement on the COMMITTED geometry) and turns rigidly about the pivot by the
1664
+ * placement on the committed geometry) and turns rigidly about the pivot by the
1168
1665
  * same snapped delta the shape uses; the policy re-evaluates only on release.
1169
1666
  * Re-deciding per frame would jump at grab and side-switch mid-spin.
1170
1667
  */
1171
- declare function selectionKnob(m: Model, pon: number, pageBox?: Rect, knobOffset?: number, view?: ViewEnv): {
1172
- at: Vec;
1173
- from: Vec;
1668
+ declare function selectionKnob(model: Model, page: PageRef, pageBox?: Rect, knobOffset?: number, view?: ViewEnv): {
1669
+ at: Point;
1670
+ from: Point;
1174
1671
  } | null;
1175
- declare function chrome(m: Model, pon: number, pageBox?: Rect, knobOffset?: number, view?: ViewEnv): ChromeNode[];
1176
- /** The content-space union of the selectable selected items on `pon`, or null if
1177
- * the page holds none. This is the SAME box the chrome outline draws, so a
1672
+ declare function chrome(model: Model, page: PageRef, pageBox?: Rect, knobOffset?: number, view?: ViewEnv): ChromeNode[];
1673
+ /** The page-space union of the selectable selected items on `page`, or null if
1674
+ * the page holds none. This is the same box the chrome outline draws, so a
1178
1675
  * floating menu sits exactly on the selection. */
1179
- declare function selectionBoundsOnPage(m: Model, pon: number, view?: ViewEnv): Rect | null;
1180
- /** The anchor for a selection-aware floating menu: the PRIMARY page (the first
1676
+ declare function selectionBoundsOnPage(model: Model, page: PageRef, view?: ViewEnv): Rect | null;
1677
+ /** The anchor for a selection-aware floating menu: the primary page (the first
1181
1678
  * selectable selected id) + the union box of the selection on that page (content
1182
1679
  * space). Null when nothing selectable is selected. A cross-page selection
1183
1680
  * anchors to its primary page, so there is exactly one menu. `pageBoxOf` is a
1184
1681
  * lookup (the anchor resolves its own page) feeding the page-bound knob
1185
- * placement — the menu then dodges the knob where it ACTUALLY sits. */
1186
- declare function selectionAnchor(m: Model, pageBoxOf?: (pon: number) => Rect | undefined, knobOffsetOf?: (pon: number) => number | undefined, viewOf?: (pon: number) => ViewEnv | undefined): {
1187
- pon: number;
1682
+ * placement — the menu then dodges the knob where it actually sits. */
1683
+ declare function selectionAnchor(model: Model, pageBoxOf?: (page: PageRef) => Rect | undefined, knobOffsetOf?: (page: PageRef) => number | undefined, viewOf?: (page: PageRef) => ViewEnv | undefined): {
1684
+ page: PageRef;
1188
1685
  bounds: Rect;
1189
- knob?: Vec;
1686
+ knob?: Point;
1190
1687
  } | null;
1191
1688
  /** Anchor for controls that finish/cancel an active multi-click creation draft.
1192
1689
  * It is rect-based like selectionAnchor, using the committed vertices only so
1193
1690
  * the menu remains stable while the hover preview follows the pointer. */
1194
- declare function creationDraftAnchor(m: Model): CreationDraftAnchor | null;
1691
+ /**
1692
+ * The rotation in progress, for the badge that follows the pointer: `null`
1693
+ * when nothing is being turned. The badge is a screen overlay, so it stays
1694
+ * upright however the page is shown.
1695
+ */
1696
+ declare function rotationAnchor(model: Model): RotationAnchor | null;
1697
+ declare function creationDraftAnchor(model: Model): CreationDraftAnchor | null;
1698
+ /**
1699
+ * Where UI attaches to one annotation: its page and the box around what it
1700
+ * shows, a gesture in progress included, so an overlay follows a drag. `null`
1701
+ * for a record the model doesn't have.
1702
+ */
1703
+ declare function annotationAnchor(model: Model, id: Id, view?: ViewEnv): {
1704
+ page: PageRef;
1705
+ bounds: Rect;
1706
+ } | null;
1195
1707
  //#endregion
1196
1708
  //#region src/hit.d.ts
1197
1709
  type Target = {
1198
- t: 'handle';
1710
+ kind: 'handle';
1199
1711
  id: Id;
1200
1712
  handle: string;
1201
1713
  cursor: Cursor;
1202
1714
  } | {
1203
- t: 'rotate';
1715
+ kind: 'rotate';
1204
1716
  ids: Id[];
1205
- pivot: Vec;
1717
+ pivot: Point;
1206
1718
  } | {
1207
- t: 'group-handle';
1719
+ kind: 'group-handle';
1208
1720
  ids: Id[];
1209
1721
  handle: string;
1210
1722
  cursor: Cursor;
1211
1723
  box: Rect;
1212
1724
  } | {
1213
- t: 'annot';
1725
+ kind: 'annot';
1214
1726
  id: Id;
1215
1727
  } | {
1216
- t: 'empty';
1728
+ kind: 'empty';
1217
1729
  };
1218
1730
  /** Can this annotation be clicked to select? (`/F` interaction flags override
1219
- * all caps — hidden/noView/readOnly are inert; LOCKED stays selectable, it
1731
+ * all caps — hidden/noView/readOnly are inert; locked stays selectable, it
1220
1732
  * just won't transform.) */
1221
- declare const isSelectable: (m: Model, id: Id) => boolean;
1222
- /** Can this annotation be dragged by its body to move? (`locked` freezes it.) */
1223
- declare const canMove: (m: Model, id: Id) => boolean;
1733
+ declare const isSelectable: (model: Model, id: Id) => boolean;
1734
+ /** Can this annotation be dragged by its body to move? (`locked` and the
1735
+ * session's `editable` rule freeze it.) */
1736
+ declare const canMove: (model: Model, id: Id) => boolean;
1737
+ /**
1738
+ * What an annotation paints where the pointer meets it (`painted.ts`): its
1739
+ * shape's pieces, projected as a screen-anchored body shows at `view`, and a
1740
+ * measurement's caption, dimension line and leaders. A click and the marquee
1741
+ * both read this.
1742
+ */
1743
+ declare function paintedOf(record: ModelAnnotation, view?: ViewEnv): readonly PaintedPiece[];
1224
1744
  /**
1225
- * What's under the content point.
1745
+ * Is `point` on a text box's own box, where its text is? Not on a callout's
1746
+ * line, and not in the empty rest of its frame. `margin` reaches the resize
1747
+ * handles on its border. The box is tested where the page shows it, as a
1748
+ * click tests it.
1749
+ */
1750
+ declare function isOnTextBox(record: ModelAnnotation, point: Point, margin: number, view?: ViewEnv): boolean;
1751
+ /**
1752
+ * What's under the page point.
1226
1753
  * 1. a resize/vertex handle of the single selection,
1227
- * 2. an editable annotation body — a SELECTED one anywhere in its bounds (so you
1228
- * can drag to move it), an UNSELECTED one only on its stroke/fill (margin-aware,
1754
+ * 2. an editable annotation body — a selected one anywhere in its bounds (so you
1755
+ * can drag to move it), an unselected one only on its stroke/fill (margin-aware,
1229
1756
  * so an unfilled circle is grabbed only on its outline),
1230
1757
  * 3. else empty.
1231
1758
  */
1232
- declare function hitTest(m: Model, pon: number, p: Vec, geom: ChromeGeom, strokeMargin: number, pageBox?: Rect, inert?: ReadonlySet<Id>, view?: ViewEnv): Target;
1759
+ declare function hitTest(model: Model, page: PageRef, point: Point, chromeGeometry: ChromeGeometry, strokeMargin: number, pageBox?: Rect, inert?: ReadonlySet<Id>, view?: ViewEnv): Target;
1233
1760
  /** The cursor to show on hover: a resize cursor over a handle, move/pointer over a body. */
1234
- declare function cursorAt(m: Model, pon: number, p: Vec, geom: ChromeGeom, strokeMargin: number, pageBox?: Rect, inert?: ReadonlySet<Id>, view?: ViewEnv): Cursor | null;
1761
+ declare function cursorAt(model: Model, page: PageRef, point: Point, geometry: ChromeGeometry, strokeMargin: number, pageBox?: Rect, inert?: ReadonlySet<Id>, view?: ViewEnv): Cursor | null;
1235
1762
  //#endregion
1236
1763
  //#region src/richtext.d.ts
1237
1764
  type RichTextStyleDelta = Partial<RichTextRunStyle>;
@@ -1245,7 +1772,7 @@ declare function richTextLength(doc: {
1245
1772
  paragraphs: readonly RichTextParagraph[];
1246
1773
  }): number;
1247
1774
  /** Two deltas override the same properties with the same values. */
1248
- declare function sameStyleDelta(a: RichTextStyleDelta | undefined, b: RichTextStyleDelta | undefined): boolean;
1775
+ declare function sameStyleDelta(left: RichTextStyleDelta | undefined, right: RichTextStyleDelta | undefined): boolean;
1249
1776
  /**
1250
1777
  * Merge adjacent runs whose deltas agree, drop empty runs, strip empty
1251
1778
  * deltas. A paragraph with no text keeps a single empty run so the editor
@@ -1296,13 +1823,38 @@ declare function plainTextOf(doc: {
1296
1823
  /** True when nothing in the document overrides the body: no run delta, no
1297
1824
  * paragraph property. Such a document commits as plain `contents`. */
1298
1825
  declare function isPlainRichText(doc: RichTextDocumentInput): boolean;
1826
+ /** The registered fonts an engine knows (the local engine's `fonts.list()`;
1827
+ * none on the cloud engine). */
1828
+ type FontLookup = () => readonly FontHandle[];
1829
+ /** The face a DTO font names: a standard font's family, weight and italic, a
1830
+ * registered key's identity, else the string itself as a family. */
1831
+ declare const faceForFont: (font: string, fonts?: FontLookup) => FaceRequest;
1832
+ /** The rich body the `/DA` text style describes (a draft's body before its
1833
+ * DTO exists; also the fallback for a DTO without `richText`). */
1834
+ declare function bodyFromTextStyle(style: TextStyle, fonts?: FontLookup): RichTextBody;
1835
+ /** The annotation's rich document: a free text's own, else one made from
1836
+ * its plain text and how its text is set. */
1837
+ declare function richDocOf(annotation: Annotation, fonts?: FontLookup): RichTextDocument;
1838
+ //#endregion
1839
+ //#region src/font-css.d.ts
1840
+ type StandardFamily = (typeof STANDARD_FACES)[keyof typeof STANDARD_FACES]['family'];
1841
+ /** Web stacks with the standard families' metrics. */
1842
+ declare const STANDARD_FONT_STACKS: Readonly<Record<StandardFamily, string>>;
1843
+ /**
1844
+ * The family `mountWebFont` (`@embedpdf/web`) mounts a registered font's key
1845
+ * under: namespaced, so a page's own family of the same name never stands in
1846
+ * for it.
1847
+ */
1848
+ declare const mountedFontFamily: (key: string) => string;
1849
+ /** The CSS family list for a DTO font: a standard kebab name or a registered key. */
1850
+ declare function cssFontFamilyForFont(font: string): string;
1299
1851
  //#endregion
1300
1852
  //#region src/group.d.ts
1301
1853
  /**
1302
1854
  * The transform capabilities of a multi-target (group) selection: a group can
1303
- * move/resize/rotate only if EVERY member's kind allows that group op AND no
1304
- * member is locked. This is the SINGLE resolver every adapter consumes (v2
1305
- * duplicated the `.every(...)` across three framework components). The iso-vs-
1855
+ * move/resize/rotate only if every member's kind allows that group op and no
1856
+ * member is locked or frozen by the session's `editable` rule. This is the single resolver every adapter consumes, so no
1857
+ * framework component re-derives the rule. The iso-vs-
1306
1858
  * aniso resize choice is computed separately, per-gesture, since it depends on
1307
1859
  * the live `rot` of the members, not the static caps.
1308
1860
  */
@@ -1311,135 +1863,162 @@ interface GroupCaps {
1311
1863
  resizable: boolean;
1312
1864
  rotatable: boolean;
1313
1865
  }
1314
- declare function groupCaps(m: Model, ids: Id[]): GroupCaps;
1866
+ declare function groupCaps(model: Model, ids: Id[]): GroupCaps;
1315
1867
  /**
1316
1868
  * The key of the group `id` belongs to, or `null` when it is ungrouped. A
1317
- * subordinate's key is its `group` field; a primary's key is its own id (it is
1318
- * the target of at least one member's `group`). An annotation that is neither a
1869
+ * subordinate's key is its `groupOf`; a primary's key is its own id (it is the
1870
+ * target of at least one member's `groupOf`). An annotation that is neither a
1319
1871
  * subordinate nor the target of any subordinate is ungrouped.
1320
1872
  */
1321
- declare function groupKeyOf(m: Model, id: Id): Id | null;
1873
+ declare function groupKeyOf(model: Model, id: Id): Id | null;
1322
1874
  /**
1323
1875
  * Every member of the group containing `id` (primary first), or just `[id]`
1324
1876
  * when it is ungrouped. The primary is the annotation whose id is the key; the
1325
- * rest are everything whose `group` equals the key, in `order`.
1877
+ * rest are everything whose `groupOf` equals the key, in `order`.
1326
1878
  */
1327
- declare function groupMembers(m: Model, id: Id): Id[];
1879
+ declare function groupMembers(model: Model, id: Id): Id[];
1328
1880
  /** Union of every id's full group — the selection seen as whole groups. */
1329
- declare function expandGroups(m: Model, ids: Id[]): Id[];
1881
+ declare function expandGroups(model: Model, ids: Id[]): Id[];
1330
1882
  //#endregion
1331
1883
  //#region src/plane.d.ts
1332
1884
  /** True when this annotation belongs to the conversation plane only —
1333
1885
  * never painted, hit, marquee-selected, or counted into a page's
1334
1886
  * appearance epoch. */
1335
- declare function isConversationOnly(a: Pick<Annot, 'irt' | 'group' | 'subtype' | 'data'>): boolean;
1887
+ declare function isConversationOnly(record: Pick<ModelAnnotation, 'annotation'>): boolean;
1336
1888
  /**
1337
- * A link CHILD attached to another annotation — a `/Link` grouped
1889
+ * A link child attached to another annotation — a `/Link` grouped
1338
1890
  * (`/IRT` + `/RT /Group`) under a non-link parent. Pure substrate: it is
1339
- * the parent's `link` PROPERTY while authoring (derived via `linkOf`) and
1891
+ * the parent's `link` property while authoring (derived via `linkOf`) and
1340
1892
  * the navigation plane's anchor while reading — never painted, never hit
1341
- * as itself. Its rect is RECONCILED from the parent's geometry, so direct
1893
+ * as itself. Its rect is reconciled from the parent's geometry, so direct
1342
1894
  * manipulation would be overwritten anyway.
1343
1895
  */
1344
- declare function isAttachedLink(a: Pick<Annot, 'subtype' | 'group'>): boolean;
1896
+ declare function isAttachedLink(record: Pick<ModelAnnotation, 'annotation'>): boolean;
1345
1897
  /**
1346
- * The ONE page-surface cull: everything that lives in the substrate but is
1898
+ * The one page-surface cull: everything that lives in the substrate but is
1347
1899
  * not a page visual of its own — conversation members (replies, review
1348
1900
  * states) and attached link children. Paint order, hit-testing, marquee
1349
- * and the appearance epoch all filter through THIS, never the parts.
1901
+ * and the appearance epoch all filter through this, never the parts.
1350
1902
  */
1351
- declare function isSubstrateOnly(a: Pick<Annot, 'irt' | 'group' | 'subtype' | 'data'>): boolean;
1903
+ declare function isSubstrateOnly(record: Pick<ModelAnnotation, 'annotation'>): boolean;
1352
1904
  //#endregion
1353
1905
  //#region src/links.d.ts
1354
1906
  /** Every attached link child of `parentId`, in z-order (multi-segment
1355
1907
  * markup parents carry several children with one shared target). */
1356
- declare function linkChildrenOf(m: Model, parentId: Id): Annot[];
1908
+ declare function linkChildrenOf(model: Model, parentId: Id): ModelAnnotation[];
1357
1909
  /** The parent's link target, derived from its first attached child — the
1358
1910
  * read side of the `syncLink` reconciler. Null when no child exists. */
1359
- declare function linkOf(m: Model, parentId: Id): PdfLinkTarget | null;
1911
+ declare function linkOf(model: Model, parentId: Id): PdfLinkTarget | null;
1912
+ //#endregion
1913
+ //#region src/shapes/index.d.ts
1914
+ /**
1915
+ * The family that answers for `shape`. Each family works on its own shapes
1916
+ * only; looking it up by the shape's `kind` is what hands it one.
1917
+ */
1918
+ declare const familyOf: (shape: Shape) => ShapeFamily;
1919
+ /**
1920
+ * The family of a kind whose annotation says which shape it has: `pick`
1921
+ * names the family that reads it, and the shape's own family does the rest.
1922
+ */
1923
+ declare function familyChosenBy(pick: (annotation: Annotation) => ShapeFamily): ShapeFamily;
1360
1924
  //#endregion
1361
- //#region src/kinds.d.ts
1925
+ //#region src/kinds/define.d.ts
1926
+ /** The flags an annotation carries, each a `flag` property of every kind. */
1927
+ type FlagKey = 'invisible' | 'hidden' | 'print' | 'noZoom' | 'noRotate' | 'noView' | 'readOnly' | 'locked' | 'toggleNoView' | 'lockedContents';
1928
+ /**
1929
+ * One choice of a `choice` property, as a picker shows it: the value it
1930
+ * writes, and its name, `label` in English and `labelKey` to translate it
1931
+ * (`annotation.lineEndings.openArrow`).
1932
+ */
1933
+ interface AnnotationOption {
1934
+ readonly value: string;
1935
+ readonly label: string;
1936
+ readonly labelKey: string;
1937
+ }
1362
1938
  /**
1363
- * One editable property of a kind, as a UI contract: which {@link AnnotationProps}
1364
- * key, rendered how (the union arm fixes the control + its constraints), labelled
1365
- * what by default. A property sidebar is a `switch (spec.key)` over these — the
1366
- * per-kind lists below are the v2 snippet's hand-rolled `TOOL_PROPERTIES` schema,
1367
- * promoted into the library so every consumer gets it for free.
1939
+ * One property a style panel edits, as a UI contract: which field, edited
1940
+ * with which control, labelled what. A panel switches on `control` (or on
1941
+ * `key`, for a richer editor of its own), reads `values[property.key]` and
1942
+ * writes `{ [property.key]: value }`, so each kind's list is its property
1943
+ * schema, kept in the library so every consumer gets it for free.
1368
1944
  *
1369
- * `label` is a default (English) display name — apps with i18n map `key`s to
1370
- * their own strings and ignore it. Array ORDER is display order.
1945
+ * `key` is the engine's field name, with three exceptions: the border's
1946
+ * `borderStyle` reads and writes `borderStyle`, `dashArray` and
1947
+ * `cloudyIntensity` together; `bold`, `italic` and `underline` are the rich
1948
+ * body's formatting; and `link` is an attached link, not a field of the
1949
+ * annotation.
1950
+ *
1951
+ * `label` is the English name and `labelKey` its translation key: a panel
1952
+ * shows `t(property.labelKey, { fallback: property.label })`, as a command's
1953
+ * label reads. The key names the wording, not the field, since one field is
1954
+ * called different things by different kinds: `color` is
1955
+ * `annotation.style.stroke` on a shape and `annotation.style.outline` on a
1956
+ * redaction mark. A `choice` property's options carry the same pair. Array
1957
+ * order is display order.
1371
1958
  */
1372
- type PropSpec = {
1373
- key: 'color';
1374
- label: string;
1375
- } | {
1376
- key: 'interiorColor';
1377
- label: string;
1378
- } | {
1379
- key: 'fontColor';
1380
- label: string;
1381
- } | {
1382
- key: 'opacity';
1383
- label: string;
1384
- min: number;
1385
- max: number;
1386
- step: number;
1387
- } | {
1388
- key: 'strokeWidth';
1389
- label: string;
1390
- min: number;
1391
- max: number;
1392
- step: number;
1393
- } | {
1394
- key: 'fontSize';
1395
- label: string;
1396
- min: number;
1397
- max: number;
1398
- step: number;
1959
+ type AnnotationProperty = {
1960
+ readonly key: 'color' | 'interiorColor' | 'fontColor';
1961
+ readonly control: 'color';
1962
+ readonly label: string;
1963
+ readonly labelKey: string;
1964
+ } | {
1965
+ readonly key: 'opacity' | 'strokeWidth' | 'fontSize';
1966
+ readonly control: 'number';
1967
+ readonly label: string;
1968
+ readonly labelKey: string;
1969
+ readonly min: number;
1970
+ readonly max: number;
1971
+ readonly step: number;
1399
1972
  } |
1400
- /** Border style picker; `cloudy` says whether this kind honours a cloudy border. */
1973
+ /** The border over `borderStyle`, `dashArray` and `cloudyIntensity`;
1974
+ * `cloudy` says whether this kind takes a cloudy border. */
1401
1975
  {
1402
- key: 'border';
1403
- label: string;
1404
- cloudy: boolean;
1405
- } | {
1406
- key: 'lineEndings';
1407
- label: string;
1408
- } | {
1409
- key: 'fontFamily';
1410
- label: string;
1411
- } | {
1412
- key: 'textAlign';
1413
- label: string;
1976
+ readonly key: 'borderStyle';
1977
+ readonly control: 'choice';
1978
+ readonly label: string;
1979
+ readonly labelKey: string;
1980
+ readonly options: readonly AnnotationOption[];
1981
+ readonly cloudy: boolean;
1414
1982
  } |
1415
- /** Rich-text formatting toggles (free text): the body's formatting, or —
1416
- * while the text editor has a range — that range's runs. */
1983
+ /** A value from a list: line endings, a font, an alignment, a blend mode, an icon. */
1417
1984
  {
1418
- key: 'bold';
1419
- label: string;
1420
- } | {
1421
- key: 'italic';
1422
- label: string;
1423
- } | {
1424
- key: 'underline';
1425
- label: string;
1426
- } | {
1427
- key: 'blendMode';
1428
- label: string;
1985
+ readonly key: 'lineEndings' | 'fontFamily' | 'textAlign' | 'blendMode' | 'icon';
1986
+ readonly control: 'choice';
1987
+ readonly label: string;
1988
+ readonly labelKey: string;
1989
+ readonly options: readonly AnnotationOption[];
1990
+ } |
1991
+ /** One of the annotation's flags, on or off. */
1992
+ {
1993
+ readonly key: FlagKey;
1994
+ readonly control: 'flag';
1995
+ readonly label: string;
1996
+ readonly labelKey: string;
1997
+ } |
1998
+ /** Free text, such as a redaction's label. */
1999
+ {
2000
+ readonly key: 'overlayText';
2001
+ readonly control: 'text';
2002
+ readonly label: string;
2003
+ readonly labelKey: string;
1429
2004
  } |
1430
- /** `/Name` icon picker for icon kinds; `options` are the legal names. */
2005
+ /** Bold, italic or underline of a text box: the body's formatting, or,
2006
+ * while the text editor has a range, that range's runs. */
1431
2007
  {
1432
- key: 'icon';
1433
- label: string;
1434
- options: readonly string[];
2008
+ readonly key: 'bold' | 'italic' | 'underline';
2009
+ readonly control: 'textFormat';
2010
+ readonly label: string;
2011
+ readonly labelKey: string;
2012
+ readonly format: 'bold' | 'italic' | 'underline';
1435
2013
  } |
1436
- /** Link-target editor (URL / page destination). Declared by the link kind
1437
- * (its own target) and by every kind that may carry an ATTACHED link;
1438
- * kinds that omit it (widgets, caret, redact…) simply cannot be links —
1439
- * `applyProps` drops the key and menus never show the control. */
2014
+ /** Where it links to (a website or a page). Declared by the link kind (its
2015
+ * own target) and by every kind that may carry an attached link; kinds
2016
+ * that omit it (widgets, caret, redaction…) can't be links. */
1440
2017
  {
1441
- key: 'link';
1442
- label: string;
2018
+ readonly key: 'link';
2019
+ readonly control: 'link';
2020
+ readonly label: string;
2021
+ readonly labelKey: string;
1443
2022
  };
1444
2023
  /** Orthogonal capability flags. Static data — the annotation's `/F` flags are
1445
2024
  * the runtime overrides (a locked annotation is never transformable, a hidden
@@ -1455,13 +2034,13 @@ interface KindCaps {
1455
2034
  vertexEditable: boolean;
1456
2035
  /** Can be rotated (shapes, free text, lines/polys/ink). */
1457
2036
  rotatable: boolean;
1458
- /** Can be MOVED as part of a multi-target (group) transform. */
2037
+ /** Can be moved as part of a multi-target (group) transform. */
1459
2038
  groupMovable: boolean;
1460
- /** Can be uniformly SCALED as part of a multi-target (group) transform — ON
2039
+ /** Can be uniformly scaled as part of a multi-target (group) transform — on
1461
2040
  * even for vertex kinds that have no single-shape box resize (their handles
1462
- * ARE the vertices; in a group they scale fine). */
2041
+ * are the vertices; in a group they scale fine). */
1463
2042
  groupResizable: boolean;
1464
- /** Can be ROTATED as part of a multi-target (group) transform. */
2043
+ /** Can be rotated as part of a multi-target (group) transform. */
1465
2044
  groupRotatable: boolean;
1466
2045
  /** Carries editable text content (free text, the comment popup). */
1467
2046
  textEditable: boolean;
@@ -1471,6 +2050,8 @@ interface KindCaps {
1471
2050
  hasPopup: boolean;
1472
2051
  /** Bound to underlying text (markup, caret) — never freely moved/resized. */
1473
2052
  anchored: boolean;
2053
+ /** Paints with the page's text, beneath every other kind (text markup, redaction marks). */
2054
+ paintsBeneath: boolean;
1474
2055
  /** Has an interior fill (`/IC`). */
1475
2056
  hasFill: boolean;
1476
2057
  /** Has line endings (`/LE` — line, polyline). */
@@ -1478,10 +2059,15 @@ interface KindCaps {
1478
2059
  /** Can take a cloudy border effect (`/BE` — shapes). */
1479
2060
  hasCloudy: boolean;
1480
2061
  /** The whole body is visible content, so hit-testing grabs anywhere inside
1481
- * the box (stamp images) — NOT just the stroke/fill like outline shapes. */
2062
+ * the box (stamp images, note icons) — not just the stroke/fill like
2063
+ * outline shapes. */
1482
2064
  opaqueBody: boolean;
1483
- /** The `/F` ReadOnly flag is IGNORED for this kind (ISO 32000: widgets — a
1484
- * ReadOnly form FIELD must still be movable by a form designer; the
2065
+ /** Its raster is its drawing, drawn where its shape is: the kind draws none
2066
+ * of it live (a stamp's image, a form widget, a link's border). A lent
2067
+ * record can still draw live with its lender's painter (`drawsLive`). */
2068
+ rasterOnly: boolean;
2069
+ /** The `/F` ReadOnly flag is ignored for this kind (ISO 32000: widgets — a
2070
+ * ReadOnly form field must still be movable by a form designer; the
1485
2071
  * form-filling layer enforces field ReadOnly itself). */
1486
2072
  ignoresReadOnly: boolean;
1487
2073
  /** Behaves as if `/F` NoZoom is always set (screen-constant size — the
@@ -1491,270 +2077,240 @@ interface KindCaps {
1491
2077
  * rule for Text/note icons). See anchor.ts. */
1492
2078
  noRotate: boolean;
1493
2079
  }
2080
+ /** No capability at all: a kind spreads this and turns on what it can do. */
2081
+ declare const NO_CAPS: KindCaps;
2082
+ /** One annotation kind: what it is, and what a user can do to it. */
1494
2083
  interface AnnotationKind {
1495
- subtype: Subtype;
1496
- /** Future: PDF `/IT` intent (free-text vs callout, caret insert vs replace). */
1497
- intent?: string;
1498
- variant: Geom['t'];
1499
- caps: KindCaps;
1500
- /** The kind's editable properties, in DISPLAY ORDER — the contract a property
1501
- * sidebar renders from (see {@link PropSpec}). Empty = nothing to edit. */
1502
- props: PropSpec[];
2084
+ /** Its name: the PDF subtype, or a widget's field family (`widget-text`…). */
2085
+ readonly name: string;
2086
+ /**
2087
+ * The shape family that reads its shape off the annotation (`shapes/`).
2088
+ * The shape's family then hits, handles, moves and draws it.
2089
+ */
2090
+ readonly family: ShapeFamily;
2091
+ /**
2092
+ * How it is drawn: its colours, stroke and border, read off the annotation
2093
+ * with its kind's fill-ins (`styles.ts`).
2094
+ */
2095
+ readonly style: (annotation: Annotation) => Style;
2096
+ /**
2097
+ * How its text is set, for a kind with text: its font, size, colour and
2098
+ * alignment, read off the annotation (`texts.ts`).
2099
+ */
2100
+ readonly text?: (annotation: Annotation) => TextStyle;
2101
+ /** What a user can do to it. The annotation's `/F` flags override these at runtime (flags.ts). */
2102
+ readonly caps: KindCaps;
2103
+ /**
2104
+ * What a style panel edits, in display order, keyed by the engine's field
2105
+ * names. The flags every kind has are added by `propertiesOf`.
2106
+ */
2107
+ readonly properties: readonly AnnotationProperty[];
1503
2108
  }
1504
- /** The built-in kinds. Shapes resize, lines/polys vertex-edit, markup is anchored
1505
- * (selectable + recolor/delete, never move/resize). */
1506
- declare const KINDS: Record<string, AnnotationKind>;
1507
- /** The capabilities of a subtype, or the read-only default for unknown kinds. */
1508
- declare const capsFor: (subtype: string) => KindCaps;
1509
- /** A kind's editable properties in display order — empty for unknown kinds.
1510
- * Stable references, so selectors can compare by identity. */
1511
- declare const propsFor: (subtype: string) => PropSpec[];
2109
+ /**
2110
+ * Declares a kind. It returns the kind exactly as written: nothing is
2111
+ * registered and nothing is filled in. `index.ts` lists every kind.
2112
+ */
2113
+ declare const defineKind: <const Kind extends AnnotationKind>(kind: Kind) => Kind;
2114
+ //#endregion
2115
+ //#region src/kinds/index.d.ts
2116
+ /** The kinds, by name. */
2117
+ declare const KINDS: Readonly<Record<string, AnnotationKind>>;
2118
+ /** The kind named `name`; a name no kind has reads as one that is only shown. */
2119
+ declare function kindNamed(name: string): AnnotationKind;
1512
2120
  //#endregion
1513
2121
  //#region src/props.d.ts
1514
2122
  /** Base text styling for kinds/tools with no explicit defaults. */
1515
2123
  declare const initialTextStyle: TextStyle;
1516
- /** The `Style` slice of a resolved props bag (the create-time projection). */
1517
- declare const styleFromProps: (p: AnnotationProps) => Style;
1518
- /** The `TextStyle` slice of a resolved props bag (the create-time projection). */
1519
- declare const textStyleFromProps: (p: AnnotationProps) => TextStyle;
2124
+ /** Does this kind's property table declare `link` (attachable link)? The link
2125
+ * kind itself also declares it, but stores its own `/A` instead. */
2126
+ declare const kindTakesLink: (kind: AnnotationKind) => boolean;
2127
+ /**
2128
+ * Everything a style panel edits for one kind: its own properties, then the
2129
+ * flags every kind has. The same array for a kind on every call.
2130
+ */
2131
+ declare function propertiesOf(kind: AnnotationKind): readonly AnnotationProperty[];
1520
2132
  /**
1521
- * Read one property off an annotation — from wherever it lives — or `undefined`
1522
- * when the annotation's kind doesn't carry it (a `fontSize` on a square, endings
1523
- * on a polygon). The read side of `applyProps`.
2133
+ * The ordered properties every given kind has: the schema for a mixed
2134
+ * selection, in the first kind's display order. One kind: its own list
2135
+ * (`propertiesOf`), the same array every time.
1524
2136
  */
1525
- declare function readProp<K extends PropKey>(a: Annot, key: K): AnnotationProps[K] | undefined;
2137
+ declare function sharedProperties(kinds: readonly AnnotationKind[]): readonly AnnotationProperty[];
2138
+ //#endregion
2139
+ //#region src/kinds/fields.d.ts
2140
+ /** Every line ending the engine draws. */
2141
+ declare const LINE_ENDING_NAMES: readonly ["none", "square", "circle", "diamond", "open-arrow", "closed-arrow", "butt", "r-open-arrow", "r-closed-arrow", "slash"];
2142
+ /** The flags every kind has, in the order a panel lists them. */
2143
+ declare const FLAG_PROPERTIES: readonly AnnotationProperty[];
2144
+ //#endregion
2145
+ //#region src/record/defaults.d.ts
2146
+ /** The engine subtype a client kind creates: a callout is a free text, a form tool's kind a widget. */
2147
+ declare const engineSubtypeOf: (kind: string) => AnnotationSubtype;
1526
2148
  /**
1527
- * Apply a property patch to one annotation, honouring its kind's declared keys.
1528
- * Returns the changed annotation, or `null` when nothing applied (locked /
1529
- * read-only per its `/F` flags, or no declared key in the patch) — so the
1530
- * caller emits no spurious engine write. Flags themselves are NOT props: they
1531
- * write through the `setFlags` message, which is deliberately not gated here
1532
- * (unlocking must work on a locked annotation).
2149
+ * A tool's `defaults` over the engine's defaults for `kind`: what a create
2150
+ * from the tool starts from. A form tool's reads as a widget of its field
2151
+ * family, so it reads as the tool's own kind (a radio tool's is round).
1533
2152
  */
1534
- declare function applyProps(a: Annot, patch: AnnotationPropsPatch): Annot | null;
2153
+ declare function readOfDefaults(kind: string, defaults: FieldValues): Annotation;
1535
2154
  /**
1536
- * The ordered property specs EVERY given kind declares — the schema for a mixed
1537
- * selection, in the FIRST kind's display order. One kind → its own list, verbatim.
2155
+ * A form tool's defaults as the engine's widget appearance for `doc.forms`
2156
+ * authoring: the widget's style fields among them, as they are. Absent fields
2157
+ * stay absent (the engine writes nothing for them).
1538
2158
  */
1539
- declare function sharedProps(subtypes: readonly string[]): PropSpec[];
2159
+ declare function widgetAppearanceOf(defaults: FieldValues): WidgetAppearance;
1540
2160
  //#endregion
1541
2161
  //#region src/geometry.d.ts
1542
- type RectHandle = 'nw' | 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w';
1543
- declare const RECT_HANDLES: RectHandle[];
1544
- /** The resize cursor for a handle on a box tilted by `rot` deg: rotate the
1545
- * handle's compass direction with the box and pick the nearest 45° sector.
1546
- * At `rot = 0` this reproduces {@link RECT_CURSOR} exactly. */
1547
- declare const rotatedHandleCursor: (h: RectHandle, rot: number) => Cursor;
1548
- declare const rectFromPoints: (a: Vec, b: Vec) => Rect;
1549
- declare function unionRect(pts: Vec[]): Rect;
1550
2162
  /** How far (content units) the rotate knob hangs off the top edge of the box. */
1551
2163
  declare const ROTATE_KNOB_OFFSET = 24;
1552
2164
  /** Fallback chrome geometry (content units) when the caller supplies none —
1553
2165
  * the pre-settings behavior, so bare-core callers and tests stay stable. */
1554
- declare const DEFAULT_CHROME_GEOM: {
2166
+ declare const DEFAULT_CHROME_GEOMETRY: {
1555
2167
  readonly handleTol: 6;
1556
2168
  readonly knobTol: 6;
1557
2169
  readonly knobOffset: 24;
1558
2170
  };
1559
- /** Normalize degrees into `[0, 360)`. */
1560
- declare const normalizeDeg: (d: number) => number;
1561
- /** A geom's applied rotation (deg), or 0 for the non-rotatable kinds. A
1562
- * caret's `rot` is AUTHORING metadata (its text's baseline tilt): reported
1563
- * here so the renderer and selection chrome follow it, while the caret's
1564
- * caps (not movable/resizable) keep every rotate gesture away from it. */
1565
- declare function geomRotation(g: Geom): number;
1566
- /**
1567
- * The rotation PIVOT for a single shape: a box turns about its own `rect`
1568
- * centre; a vertex shape about the CENTROID of its points (the mean). Rotating a
1569
- * point set about its centroid leaves the centroid fixed, so the advisory `rot`
1570
- * stays cleanly additive across gestures and reset is exact.
1571
- */
1572
- declare function centroidOf(g: Geom): Vec;
2171
+ /** A geom's applied rotation (deg), or 0 for text markup. A caret's turn is
2172
+ * its text's baseline tilt: reported here so the renderer and selection
2173
+ * chrome follow it, while the caret's caps (not movable/resizable) keep
2174
+ * every rotate gesture away from it. */
2175
+ declare function geomRotation(geometry: Shape): number;
2176
+ /**
2177
+ * Where a turn of a single shape pivots, as the engine turns it: a box about
2178
+ * its own `rect` centre; a line, polygon, polyline or ink about the middle of
2179
+ * the box around its points upright. A turn then changes only the angle, never
2180
+ * where the upright points are, and a caption or an arrowhead never moves the
2181
+ * pivot.
2182
+ */
2183
+ declare const turnPivotOf: (geometry: Shape) => Point;
1573
2184
  /** Does the geometry carry a meaningful `rot` (an oriented local box exists)?
1574
- * ORIENTATION is a geometry fact; whether the USER may rotate is the separate
2185
+ * Orientation is a geometry fact; whether the user may rotate is the separate
1575
2186
  * `caps.rotatable` gate — a caret is oriented (it rides its text's tilt) yet
1576
2187
  * offers no rotate gesture. */
1577
- declare function isRotatableGeom(g: Geom): boolean;
1578
- /**
1579
- * Rotate a geom by `deltaDeg` (clockwise) about `pivot`.
1580
- * - BOX (`rect`/plain `text`): orbit the box centre about the pivot (a rigid
1581
- * translation of the unrotated `rect`) and add the angle to `rot`. When the
1582
- * pivot IS the box centre this is a pure `rot += delta`.
1583
- * - VERTEX (`line`/`poly`/`ink`): map every point through the rotation AND bump
1584
- * the advisory `rot` (the points stay the authoritative visual).
1585
- * Kinds without a rotate VERB are returned unchanged: quads and callouts, and
1586
- * also the caret — oriented (`isRotatableGeom`) but text-anchored, so its tilt
1587
- * is authoring metadata that no gesture edits (`geomResetRotation` still
1588
- * clears it).
1589
- */
1590
- declare function geomRotateAbout(g: Geom, pivot: Vec, deltaDeg: number): Geom;
1591
- /** The AABB of `rect` rotated `deg` about its own centre. */
1592
- declare function rotatedAabb(rect: Rect, deg: number): Rect;
2188
+ declare const isRotatableGeom: (geometry: Shape) => boolean;
2189
+ /**
2190
+ * Rotate a geom by `deltaDeg` (clockwise) about `pivot`: a box's middle
2191
+ * orbits the pivot and its turn grows, and so does a points shape's. A shape
2192
+ * without a rotate verb comes back unchanged: quads, a callout, and the
2193
+ * caret (oriented, but it follows its text; `geomResetRotation` still clears
2194
+ * its turn).
2195
+ */
2196
+ declare function geomRotateAbout(geometry: Shape, pivot: Point, deltaDeg: number): Shape;
1593
2197
  /**
1594
2198
  * The counter-rotation (CW content degrees) that makes an annotation read
1595
2199
  * upright at `displayRotation`: on screen the two compose to 0.
1596
2200
  */
1597
2201
  declare const uprightRotation: (displayRotation: number) => number;
1598
- /** `rect` with width↔height swapped about its own centre — the unrotated box
1599
- * whose quarter-turn AABB is exactly `rect` again. */
1600
- declare function transposedAboutCenter(rect: Rect): Rect;
1601
2202
  /**
1602
- * The unrotated content rect for a DEFAULT-SIZE upright box "placed at" a
2203
+ * The unrotated content rect for a default-size upright box "placed at" a
1603
2204
  * point: positioned so that, after the upright counter-rotation, the box shows
1604
- * `width`×`height` with its TOP-LEFT at `anchor` in the ROTATED view — the
2205
+ * `width`×`height` with its top-left at `anchor` in the rotated view — the
1605
2206
  * same on-screen feel as the rotation-0 `{ x: anchor.x, y: anchor.y }` box at
1606
2207
  * every quarter-turn (a click-created free-text box always opens down-right of
1607
2208
  * the cursor as the author sees it). Derived by placing the box in the display
1608
2209
  * frame and pulling its AABB back through the quarter-turn; the page dims
1609
2210
  * cancel, so no page box is needed.
1610
2211
  */
1611
- declare function uprightAnchoredRect(anchor: Vec, width: number, height: number, displayRotation: number): Rect;
2212
+ declare function uprightAnchoredRect(anchor: Point, width: number, height: number, displayRotation: number): Rect;
1612
2213
  /**
1613
- * The LOGICAL (unrotated) content-space box for a stamp placed at `center`,
2214
+ * The logical (unrotated) page-space box for a stamp placed at `center`,
1614
2215
  * sized `desired` (points), fit onto the page and clamped fully within it —
1615
- * the v2 rubber-stamp rule: keep the image's own size unless it would overflow
2216
+ * the rubber-stamp rule: keep the image's own size unless it would overflow
1616
2217
  * the page, then scale down (aspect preserved, never up) so it just fits.
1617
2218
  *
1618
- * Under an upright quarter-turn the on-page FOOTPRINT is the box transposed
1619
- * (w↔h), so BOTH the fit and the edge clamp use that footprint — a landscape
2219
+ * Under an upright quarter-turn the on-page footprint is the box transposed
2220
+ * (w↔h), so both the fit and the edge clamp use that footprint — a landscape
1620
2221
  * stamp uprighted onto a portrait page fits by its rotated silhouette, not its
1621
2222
  * logical box. The box centre and its footprint AABB centre coincide (rotation
1622
2223
  * is about the centre), so clamping one clamps the other.
1623
2224
  */
1624
- declare function fitStampBox(center: Vec, desired: Size, page: Size, rotCW: number): Rect;
1625
- /** Reset a geom to its as-authored orientation (`rot → 0`). Box: drop `rot`.
1626
- * Vertex: spin the points by `-rot` about their centroid so they return to the
1627
- * orientation they were drawn at, in place. */
1628
- declare function geomResetRotation(g: Geom): Geom;
2225
+ declare function fitStampBox(center: Point, desired: Size, page: Size, rotCW: number): Rect;
2226
+ /** Reset a geom to its as-authored orientation (turn → 0). Box: drop the turn.
2227
+ * Points: turn back about `pivot` (the middle of the upright points by
2228
+ * default, which only clears the turn). */
2229
+ declare function geomResetRotation(geometry: Shape, pivot?: Point): Shape;
1629
2230
  /**
1630
2231
  * The oriented selection box (OBB) of a rotatable geom: four corners (in order
1631
- * nw, ne, se, sw of the LOCAL box, transformed) + the angle. For a box this is
1632
- * the `rect` rotated about its centre; for a vertex shape it is reconstructed
1633
- * from the advisory `rot` — un-rotate the points to recover the as-authored
1634
- * shape, take that tight local box, then rotate it back — giving the snug tilted
2232
+ * nw, ne, se, sw of the local box, transformed) + the angle. For a box this is
2233
+ * the box rotated about its centre; for a points shape it is the drawn bounds
2234
+ * of its upright points, turned about their middle — the snug tilted
1635
2235
  * rectangle. Returns null for non-rotatable kinds.
1636
2236
  */
1637
- declare function obbFromGeom(g: Geom, strokeWidth: number, border?: Border): {
1638
- corners: [Vec, Vec, Vec, Vec];
2237
+ declare function obbFromGeom(geometry: Shape, stroke: Stroke): {
2238
+ corners: [Point, Point, Point, Point];
1639
2239
  angle: number;
1640
2240
  } | null;
1641
- /** The fixed point of a group resize: the OPPOSITE handle's point on the box. */
1642
- declare function groupResizeAnchor(base: Rect, handle: string): Vec;
2241
+ /** The fixed point of a group resize: the opposite handle's point on the box. */
2242
+ declare function groupResizeAnchor(base: Rect, handle: string): Point;
1643
2243
  /**
1644
2244
  * The live group resize box for a drag. Anisotropic = the plain axis-aligned
1645
2245
  * `resizeRect`; isotropic = a uniform scale of `base` about the anchor by the
1646
2246
  * larger of the two drag ratios (so the preview matches the committed scale and
1647
2247
  * never shears a rotated member).
1648
2248
  */
1649
- declare function groupResizeBox(base: Rect, handle: string, to: Vec, isotropic: boolean): Rect;
2249
+ declare function groupResizeBox(base: Rect, handle: string, to: Point, isotropic: boolean): Rect;
1650
2250
  /** The (sx, sy) factors a `base`→`cur` group resize applied about its anchor. */
1651
- declare function groupResizeFactors(base: Rect, cur: Rect): {
2251
+ declare function groupResizeFactors(base: Rect, current: Rect): {
1652
2252
  sx: number;
1653
2253
  sy: number;
1654
2254
  };
1655
2255
  /**
1656
- * Scale a geom about `anchor` by `(sx, sy)`. For a rotated box (iso scale, so
1657
- * `sx === sy`) the unrotated `rect` is scaled about the anchor and `rot` is
1658
- * preserved (a uniform scale commutes with rotation). For unrotated members
1659
- * (the anisotropic case) every point/extent scales directly.
2256
+ * Scale a geom about `anchor` by `(sx, sy)`, exactly. For a rotated box (iso
2257
+ * scale, so `sx === sy`) the unrotated box is scaled about the anchor and its
2258
+ * turn is preserved (a uniform scale commutes with rotation). For unrotated
2259
+ * members (the anisotropic case) every point/extent scales directly. A resize
2260
+ * passes `minSize`, the smallest a box may end up.
1660
2261
  */
1661
- declare function geomScaleAbout(g: Geom, anchor: Vec, sx: number, sy: number): Geom;
2262
+ declare function geomScaleAbout(geometry: Shape, anchor: Point, sx: number, sy: number, minSize?: number): Shape;
1662
2263
  /** Where the rotate knob sits, given the OBB corners (nw, ne, se, sw) and the
1663
2264
  * outward offset (content units). `from` is the top-edge midpoint the connector
1664
2265
  * stalk anchors to; `at` is the grab dot, offset along the outward normal. */
1665
- declare function rotateKnob(corners: [Vec, Vec, Vec, Vec], offset: number): {
1666
- at: Vec;
1667
- from: Vec;
2266
+ declare function rotateKnob(corners: [Point, Point, Point, Point], offset: number): {
2267
+ at: Point;
2268
+ from: Point;
1668
2269
  };
1669
2270
  /**
1670
- * `rotateKnob` with a TOTAL page-bound placement policy: annotations, gestures
2271
+ * `rotateKnob` with a total page-bound placement policy: annotations, gestures
1671
2272
  * and chrome are all page-bound, so the grab dot must land inside `pageBox` —
1672
2273
  * off-page it is unreachable (the pointer dispatch resolves pages by
1673
- * containment). Policy: top edge (the default) → FLIP to the bottom edge when
1674
- * the stalk exits the page → CLAMP the top candidate inside (degenerate: the
2274
+ * containment). Policy: top edge (the default) → flip to the bottom edge when
2275
+ * the stalk exits the page → clamp the top candidate inside (degenerate: the
1675
2276
  * selection spans ~the whole page; the knob may then overlap the shape, and
1676
2277
  * hit-testing checks it first so it stays grabbable). Both render (`chrome`)
1677
- * and hit-test (`hitTest`) place the knob through THIS function, so what you
2278
+ * and hit-test (`hitTest`) place the knob through this function, so what you
1678
2279
  * see is what you can grab — by construction. No `pageBox` → the raw knob.
1679
2280
  */
1680
- declare function placeRotateKnob(corners: [Vec, Vec, Vec, Vec], offset: number, pageBox?: Rect): {
1681
- at: Vec;
1682
- from: Vec;
2281
+ declare function placeRotateKnob(corners: [Point, Point, Point, Point], offset: number, pageBox?: Rect): {
2282
+ at: Point;
2283
+ from: Point;
1683
2284
  };
1684
- /** The box-edge midpoint the leader connects to: the side `ref` (the knee, else
1685
- * the tip) points toward, by horizontal/vertical dominance vs the box centre.
1686
- * With `rot` (a callout box tilted by the upright policy) the decision runs in
1687
- * the box's LOCAL frame and the midpoint lands back on the ROTATED edge — the
1688
- * edge the user actually sees. Mirror of v2's `computeCalloutConnectionPoint`. */
1689
- declare function calloutConnection(box: Rect, ref: Vec, rot?: number): Vec;
1690
- /** The leader polyline `[tip, knee?, conn]`, with `conn` derived from the box
1691
- * (its ROTATED footprint when the box carries `rot`). */
1692
- declare function calloutLinePoints(g: Extract<Geom, {
1693
- t: 'text';
1694
- }>): Vec[];
1695
- /**
1696
- * The stored `rect` for a freshly-drawn shape, given the box the user dragged. A
1697
- * cloudy border stores the OUTER box (dragged + cloud extent), so the dragged box
1698
- * is its inner edge and the scallops bulge out to the stored box — just like a
1699
- * solid shape, whose /Rect is the dragged box. So `g.rect` is ALWAYS the outer box;
1700
- * the cloud-vs-solid difference lives only here, at creation.
1701
- */
1702
- declare function shapeRectFor(dragged: Rect, ellipse: boolean, style: Style): Rect;
1703
- declare function caretRectFromTextEnd(lineRect: Rect): Rect;
1704
- /**
1705
- * Caret box for a text-edit anchor: half the glyph's ink height, centered on
1706
- * the TRAILING baseline corner in READING direction (`advance` decides which
1707
- * end — RTL carets land on the visual left), sitting on the baseline. The box
1708
- * stays axis-aligned; use {@link caretGeomFromAnchor} for the tilt-carrying
1709
- * geometry.
1710
- */
1711
- declare function caretRectFromAnchor(anchor: TextEndAnchor): Rect;
1712
2285
  /**
1713
- * Caret GEOMETRY for a text-edit anchor: the box-family pair — an UNROTATED
1714
- * box whose centre sits half a caret-size ascent-ward of the trailing
1715
- * baseline corner, plus `rot` = the text's baseline tilt (deg, CW in y-down
1716
- * content space). Rotating the box about its centre by `rot` lands it
1717
- * hugging the rotated baseline, symbol pointing at its text. For upright
1718
- * anchors this degenerates EXACTLY to {@link caretRectFromAnchor} with no
1719
- * `rot` key — the dominant case is byte-identical.
1720
- */
1721
- declare function caretGeomFromAnchor(anchor: TextEndAnchor): Extract<Geom, {
1722
- t: 'caret';
1723
- }>;
1724
- /** Miter limit shared by the bounds math AND the SVG renderer (`stroke-miterlimit`),
1725
- * so the computed box and the drawn stroke always agree on where a sharp join
1726
- * bevels instead of spiking. 10 = the PDF default (also what the baked /AP uses). */
1727
- declare const MITER_LIMIT = 10;
1728
- /**
1729
- * A geom's VISUAL bounds: the rect that encloses the drawn appearance, so the
1730
- * baked /AP is never clipped and the selection outline wraps exactly what's drawn.
2286
+ * A geom's visual bounds: the rect that encloses the drawn appearance, so the
2287
+ * baked /AP is never clipped.
1731
2288
  *
1732
- * A shape's `rect` IS its visual box (the PDF /Rect): solid/dashed strokes draw
1733
- * inside it, and a cloudy border's scallops also inset back into it (the dragged
1734
- * inner edge sits `/RD` in from it — see `shapeRectFor`). So growing the stroke or
1735
- * the cloud thickens inward, never spilling past the handles. Lines / polylines /
1736
- * ink have no box, so they expand by the stroke (+ endings) — the SAME math feeds
1737
- * `geomScene`, so the visual box and what's drawn always agree.
2289
+ * A box's strokes draw inside it; a cloudy border's bumps reach out from it,
2290
+ * so its bounds grow by the cloud's reach (`boxDrawnBounds`), before its
2291
+ * turn. Lines / polylines / ink have no box, so they expand by the stroke
2292
+ * (+ endings) — the same math feeds `geomScene`, so the visual box and what's
2293
+ * drawn always agree.
1738
2294
  *
1739
- * `border` matters for ONE case: a CLOSED poly with a cloudy border, whose curls
1740
- * are centred on the vertex path and reach OUTWARD (there is no outer box to
1741
- * inset into) — the bounds grow by the cloud extent, and the engine `/Rect`
1742
- * (via `geomPdfBounds`) grows with them so the baked scallops are never clipped.
2295
+ * The stroke's cloud matters too: a box's, and a closed poly's, whose curls
2296
+ * are centred on the vertex path and reach outward — the bounds grow by the
2297
+ * cloud extent, and the engine's `rect` grows with them so the baked scallops
2298
+ * are never clipped.
1743
2299
  */
1744
- declare function geomVisualBounds(g: Geom, strokeWidth: number, border?: Border): Rect;
2300
+ declare const geomVisualBounds: (geometry: Shape, stroke: Stroke) => Rect;
1745
2301
  /**
1746
- * The rect the SELECTION wraps — and the region a SELECTED annotation can be grabbed
2302
+ * The rect the selection wraps — and the region a selected annotation can be grabbed
1747
2303
  * from. Centre-line geometries (line / polyline / polygon / ink) straddle their path,
1748
- * so this is their VISUAL bounds (the join-aware stroke outline + endings) — a
2304
+ * so this is their visual bounds (the join-aware stroke outline + endings) — a
1749
2305
  * polygon's outline wraps its stroke exactly like a polyline. Box kinds (square /
1750
- * circle / free-text) sit tight on their `rect` (their stroke draws INSIDE the box,
1751
- * so the 8 resize handles land on the corners). The chrome outline AND the selected
2306
+ * circle / free-text) sit tight on their box (a stroke draws inside it, and a
2307
+ * cloud's bumps reach past it), so the 8 resize handles land on its corners. The chrome outline and the selected
1752
2308
  * hit-test both call this, so what you see highlighted is exactly what you can grab —
1753
2309
  * they can never drift.
1754
2310
  */
1755
- declare function selectionBounds(g: Geom, strokeWidth: number, border?: Border): Rect;
2311
+ declare const selectionBounds: (geometry: Shape, stroke: Stroke) => Rect;
1756
2312
  /**
1757
- * The four corners of the ORIENTED selection box — the SAME quad `chrome` draws.
2313
+ * The four corners of the oriented selection box — the same quad `chrome` draws.
1758
2314
  * For a rotatable kind this is the OBB (the tilted box, from `obbFromGeom`); at
1759
2315
  * `rot == 0` those are just the axis-aligned corners of `selectionBounds`, and
1760
2316
  * for non-rotatable kinds (markup quads, callouts) it falls back to the rect
@@ -1762,77 +2318,227 @@ declare function selectionBounds(g: Geom, strokeWidth: number, border?: Border):
1762
2318
  * all consume this, so what you can grab / where the menu sits never drifts from
1763
2319
  * the outline you see.
1764
2320
  */
1765
- declare function selectionQuad(g: Geom, strokeWidth: number, border?: Border): [Vec, Vec, Vec, Vec];
2321
+ declare function selectionQuad(geometry: Shape, stroke: Stroke): [Point, Point, Point, Point];
1766
2322
  /** Is the point inside the (convex) selection quad? Even-odd ring test. */
1767
- declare const pointInQuad: (p: Vec, quad: [Vec, Vec, Vec, Vec]) => boolean;
2323
+ declare const pointInQuad: (point: Point, quad: [Point, Point, Point, Point]) => boolean;
2324
+ /** The box around the shape's own box or points, the stroke left out. */
2325
+ declare const geomBounds: (geometry: Shape) => Rect;
2326
+ /**
2327
+ * Is the page point on what the shape paints ({@link geomPainted}): within
2328
+ * `margin` of its ink, which widens with the stroke width, or inside it when
2329
+ * `filled`. A cloudy border is hit on its curves.
2330
+ */
2331
+ declare function geomHit(geometry: Shape, point: Point, margin: number, filled: boolean, stroke: Stroke): boolean;
2332
+ /**
2333
+ * What the shape paints (`painted.ts`): its ink for `stroke`, and its inside
2334
+ * when `filled`. Kept per shape object for the stroke and fill last asked
2335
+ * about, since a hover asks again on every pointer move.
2336
+ */
2337
+ declare function geomPainted(geometry: Shape, stroke: Stroke, filled: boolean): readonly PaintedPiece[];
2338
+ /** The shape's handles: resize corners and sides, a callout's tip and knee, or vertices. */
2339
+ declare const geomHandles: (geometry: Shape) => Handle[];
2340
+ declare const geomTranslate: (geometry: Shape, delta: Point) => Shape;
2341
+ declare const geomDragHandle: (geometry: Shape, handle: string, to: Point) => Shape;
2342
+ /**
2343
+ * What the shape draws for the live view. A text box's box — its fill and
2344
+ * its border — is the scene's, plain box and callout alike, so the live view
2345
+ * paints exactly what the AP generator bakes; the framework's editable
2346
+ * element owns only the text.
2347
+ */
2348
+ declare const geomScene: (geometry: Shape, stroke: Stroke) => RenderNode[];
2349
+ //#endregion
2350
+ //#region src/rect.d.ts
2351
+ type RectHandle = 'nw' | 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w';
2352
+ declare const RECT_HANDLES: RectHandle[];
2353
+ /** The resize cursor for a handle on a box tilted by `rot` deg: rotate the
2354
+ * handle's compass direction with the box and pick the nearest 45° sector.
2355
+ * At `rot = 0` this reproduces {@link RECT_CURSOR} exactly. */
2356
+ declare const rotatedHandleCursor: (handle: RectHandle, rot: number) => Cursor;
2357
+ declare const rectFromPoints: (from: Point, to: Point) => Rect;
2358
+ declare function unionRect(points: Point[]): Rect;
2359
+ /** Normalize degrees into `[0, 360)`. */
2360
+ declare const normalizeDeg: (degrees: number) => number;
2361
+ /** `rect` with width↔height swapped about its own centre — the unrotated box
2362
+ * whose quarter-turn AABB is exactly `rect` again. */
2363
+ declare function transposedAboutCenter(rect: Rect): Rect;
2364
+ /** The AABB of `rect` rotated `deg` about `pivot` (its own centre unless given). */
2365
+ declare function rotatedAabb(rect: Rect, deg: number, pivot?: Point): Rect;
2366
+ //#endregion
2367
+ //#region src/record/identity.d.ts
2368
+ /**
2369
+ * An annotation's kind (`kinds/`): the one its subtype names; for a widget,
2370
+ * the one its field family picks (`widget-text`…); for a free text with the
2371
+ * callout intent, the callout.
2372
+ */
2373
+ declare function kindOf(annotation: Annotation): AnnotationKind;
2374
+ /**
2375
+ * A record's engine ref, a new one's included (it names the object number
2376
+ * its create takes); `null` for a record the view doesn't hold.
2377
+ */
2378
+ declare const refOf: (record: ModelAnnotation | undefined) => AnnotationRef | null;
2379
+ /**
2380
+ * The key of the annotation an annotation answers (`/IRT`): a comment reply's
2381
+ * parent, or a group member's primary.
2382
+ */
2383
+ declare const irtOf: (annotation: Annotation) => Id | undefined;
1768
2384
  /**
1769
- * Does the (convex) selection quad intersect an axis-aligned rect? Separating
1770
- * axis test on the only four candidate axes — the rect's x/y plus the quad's
1771
- * two edge normals; a gap on any axis proves disjoint, otherwise they overlap
1772
- * (touching counts). This is the marquee's predicate: it must catch a tilted
1773
- * shape by the ORIENTED box that is actually drawn — the AABB of that quad has
1774
- * empty corners covering most of the unrotated footprint, so testing it selects
1775
- * shapes the marquee never touched. At `rot 0` the quad is axis-aligned and
1776
- * this degenerates to exactly `rectsIntersect`.
2385
+ * The key of the group an annotation belongs to: its primary's, for a
2386
+ * `/RT /Group` member only (a visual group acts as a unit; a comment reply
2387
+ * answers its parent but is not part of it).
1777
2388
  */
1778
- declare function quadIntersectsRect(quad: [Vec, Vec, Vec, Vec], r: Rect): boolean;
2389
+ declare const groupOf: (annotation: Annotation) => Id | undefined;
2390
+ //#endregion
2391
+ //#region src/record/shape.d.ts
1779
2392
  /**
1780
- * The centre of the ORIENTED selection box — the middle of the rect you see.
1781
- * For box kinds this is the rect centre (so squares/circles are unchanged); for
1782
- * vertex kinds it is the OBB centre, so rotation spins the shape in place rather
1783
- * than swinging it about the off-centre vertex mean (`centroidOf`).
2393
+ * The annotation's shape (`shapes/`), read by its kind's family. The same
2394
+ * shape for the same annotation, so what is worked out from a shape once
2395
+ * (where its points are drawn) is kept.
1784
2396
  */
1785
- declare function selectionCenter(g: Geom, strokeWidth: number): Vec;
1786
- declare function geomBounds(g: Geom): Rect;
2397
+ declare function shapeOf(annotation: Annotation): Shape;
1787
2398
  /**
1788
- * Is the content point ON the annotation: within `margin` of the stroke, or
1789
- * inside the fill (when `filled`). The stroke band widens with the stroke width.
2399
+ * The record with its shape changed: the engine fields that state `shape`,
2400
+ * written through `withValues`. The same record when the shape is the one
2401
+ * it has, or when the record may not change now.
2402
+ */
2403
+ declare const withShape: (record: ModelAnnotation, shape: Shape) => ModelAnnotation;
2404
+ //#endregion
2405
+ //#region src/record/style.d.ts
2406
+ /**
2407
+ * How the annotation is drawn: its colours, stroke and border, by the
2408
+ * engine's names, with its kind's fill-ins. A view: nothing is written back
2409
+ * through it. The same style for the same annotation.
2410
+ */
2411
+ declare function styleOf(annotation: Annotation): Style;
2412
+ //#endregion
2413
+ //#region src/record/text.d.ts
2414
+ /**
2415
+ * How the annotation's text is set: its font, size, colour and alignment by
2416
+ * the engine's names, and a free text's bold, italic and underline. A view:
2417
+ * nothing is written back through it. `undefined` for a kind without text.
2418
+ * The same value for the same annotation.
2419
+ */
2420
+ declare function textOf(annotation: Annotation): TextStyle | undefined;
2421
+ //#endregion
2422
+ //#region src/record/values.d.ts
2423
+ /**
2424
+ * The record with engine `values` written, as far as it takes them:
1790
2425
  *
1791
- * A shape's stroke is drawn INSIDE its box, centred on `insetRect(rect, sw/2)`
1792
- * (see `geomScene`), so the clickable band follows that inset centre-line — not
1793
- * the box edge. The fill still reaches the box. (Cloudy scallops aren't modelled
1794
- * here; with their typically thin stroke the inset is sub-pixel, so this matches.)
2426
+ * 1. Only the fields its kind has and this session may change now
2427
+ * (`mayWrite`). The rest are ignored, so one set of values restyles a
2428
+ * mixed selection.
2429
+ * 2. A `rect` is a command, resolved as the engine resolves it
2430
+ * (`resolveRectCommand`): it becomes the shape that puts the drawing
2431
+ * there, and one the engine would refuse throws.
2432
+ * 3. Merged into its annotation as given. The engine's follow rules (a
2433
+ * measurement's label, a callout's end) run once, where the change is
2434
+ * applied (`applyChange`).
2435
+ *
2436
+ * A value set to what it was is no change: the same record comes back.
1795
2437
  */
1796
- declare function geomHit(g: Geom, p: Vec, margin: number, filled: boolean, strokeWidth: number): boolean;
1797
- declare function geomHandles(g: Geom): Handle[];
1798
- declare function geomTranslate(g: Geom, d: Vec): Geom;
1799
- declare function geomDragHandle(g: Geom, handle: string, to: Vec): Geom;
2438
+ declare function withValues(record: ModelAnnotation, values: FieldValues): ModelAnnotation;
2439
+ //#endregion
2440
+ //#region src/record/links.d.ts
1800
2441
  /**
1801
- * The text PLATE inset of a free-text box: twice the border width. The plate
1802
- * — where text lays out, clips and scrolls — is the box deflated by this on
1803
- * every side: the ink band and an equal breathing band, so the text never
1804
- * touches the stroke. Acrobat's rule, measured 1–12 pt on plain boxes and
1805
- * 1–7 pt on callouts (plan `2026-09-15-free-text-plate-inset.md`); the
1806
- * engine's `FreeTextPlate` is the same formula, so the live editor sits
1807
- * exactly where the baked text lands. Acrobat's thinnest border is 1 pt; a
1808
- * width of 0 is ours alone and gives no inset (the plate is the box).
2442
+ * The desired hit rects (page space) of a parent's attached link
2443
+ * children — derived fresh from the parent's committed geometry, never
2444
+ * tracked: markup gets one child per quad (per-line hit areas), a text box
2445
+ * one child over its box, every other kind one child over its visual bounds.
2446
+ * The reconciler and the navigation lens both read this, so the clickable
2447
+ * area and the written `/Rect`s can never disagree.
2448
+ *
2449
+ * A link's `/Rect` is axis-aligned by spec (no rotation exists for links),
2450
+ * so a rotated parent gets the AABB of its rotated footprint — the
2451
+ * `selectionQuad` corners (rotation + stroke included), the same envelope
2452
+ * the selection chrome outlines. A text box's link covers only its turned
2453
+ * box: a callout's frame also takes in its line, and a link that size
2454
+ * would make the empty space around the line clickable. Exact rotated hit
2455
+ * regions need `/QuadPoints` (tier 2).
1809
2456
  */
1810
- declare function textPlateInset(strokeWidth: number): number;
1811
- declare function geomScene(g: Geom, strokeWidth?: number, border?: Border): RenderNode[];
1812
- declare const pdfToContentPoint: (p: PdfPoint, crop: PdfRect) => Vec;
1813
- declare const contentToPdfPoint: (p: Vec, crop: PdfRect) => PdfPoint;
1814
- declare const pdfToContentRect: (pdf: PdfRect, crop: PdfRect) => Rect;
1815
- declare const contentToPdfRect: (r: Rect, crop: PdfRect) => PdfRect;
1816
- /** A geom's VISUAL bounding box (geometry + stroke + line endings) as a PdfRect —
1817
- * the engine requires an explicit `rect` that encloses the baked /AP. */
1818
- declare const geomPdfBounds: (g: Geom, strokeWidth: number, crop: PdfRect, border?: Border) => PdfRect;
2457
+ declare function linkChildRects(shape: Shape, style: Style): Rect[];
2458
+ /** The writable projection of a `link` value: `goto`/`uri` pass through,
2459
+ * read-only arms (`javascript`, `named`, `goto-remote`, `launch`,
2460
+ * `unsupported`) yield `null` — they can be carried, never (re)written. */
2461
+ declare function writableTarget(target: PdfLinkTarget | null | undefined): PdfLinkTargetWritable | null;
1819
2462
  //#endregion
1820
- //#region src/cloudy.d.ts
2463
+ //#region src/record/written.d.ts
1821
2464
  /**
1822
- * The per-side inset (content units) from a shape's outer box to the cloud's
1823
- * inner boundary — the scallop radius plus half the stroke. This IS the `/RD`
1824
- * the engine stores, and it's sized so the scallop peaks reach back out to the
1825
- * box edge: the box remains the annotation's outer boundary.
2465
+ * The annotation `draft` creates, as the engine reads it back: the draft's
2466
+ * fields as the engine states them, and a drawn kind's `rect` the box around
2467
+ * its drawing. `at` names the ref it will have.
1826
2468
  */
1827
- declare function cloudyBorderExtent(intensity: number, strokeWidth: number, ellipse: boolean): number;
2469
+ declare function annotationOfNew(draft: AnnotationDraft, at: {
2470
+ readonly ref: AnnotationRef;
2471
+ }): Annotation;
1828
2472
  /**
1829
- * SVG path data for a cloudy square (rect) or circle (ellipse), in ABSOLUTE
1830
- * content coordinates. The scallops are generated on the box inset by
1831
- * `cloudyBorderExtent` and bulge back out to `box`'s edge.
2473
+ * `annotation` after `patch`, as the engine reads it back: the patch applied
2474
+ * by the engine's own rules, and the fields the engine works out as its
2475
+ * verdict on the change says (the table at the top of this file). Throws, as
2476
+ * the engine does, for a patch its rules refuse without its schemas
2477
+ * (`resolveAnnotationPatch`); values only the schemas refuse are applied, and
2478
+ * the engine refuses them when the patch reaches it.
2479
+ */
2480
+ declare function annotationAfter(annotation: Annotation, patch: AnnotationPatch): Annotation;
2481
+ //#endregion
2482
+ //#region src/record/index.d.ts
2483
+ /**
2484
+ * The record an engine annotation reads as: keyed by its ref, drawn from the
2485
+ * engine's appearance raster (`source: 'baked'`, placed by `apBox`). Whether
2486
+ * this session draws it live instead is the appearance rule's
2487
+ * (appearance.ts).
2488
+ */
2489
+ declare function fromDTO(dto: Annotation): ModelAnnotation;
2490
+ //#endregion
2491
+ //#region src/cloudy.d.ts
2492
+ /**
2493
+ * SVG path data for a cloudy square (rect) or circle (ellipse), in absolute
2494
+ * content coordinates. The scallops are generated on `box` and bulge out
2495
+ * from it.
1832
2496
  */
1833
2497
  declare function cloudyPath(box: Rect, ellipse: boolean, intensity: number, strokeWidth: number): string;
1834
2498
  //#endregion
2499
+ //#region src/appearance.d.ts
2500
+ /** The part of a record that says how it is drawn. */
2501
+ type DrawState = Pick<ModelAnnotation, 'source' | 'apBox'>;
2502
+ /** How a record this session creates is drawn at first. */
2503
+ declare const sourceOfNew: (annotation: Annotation) => ModelAnnotation["source"];
2504
+ /**
2505
+ * How a record the engine reports is drawn: live when this session drew it
2506
+ * live (`vector`, the plugin's preference) or when the file holds no
2507
+ * appearance for it; otherwise from the engine's raster.
2508
+ */
2509
+ declare const sourceOfConfirmed: (annotation: Annotation, vector: boolean) => ModelAnnotation["source"];
2510
+ /**
2511
+ * What changes in how `record` is drawn once `patch` is written: the rule at
2512
+ * the top of this file. Only the fields that change. The engine judges the
2513
+ * patch as it will write it: a `rect` command is the shape it moves to by
2514
+ * then.
2515
+ */
2516
+ declare function drawnAfter(record: ModelAnnotation, patch: AnnotationPatch): Partial<DrawState>;
2517
+ //#endregion
2518
+ //#region src/selection.d.ts
2519
+ interface SelectionFrame {
2520
+ corners: QuadRing;
2521
+ center: Point;
2522
+ angle: number;
2523
+ }
2524
+ /**
2525
+ * The frame of `record` as it is drawn, or with `live` fields laid over its
2526
+ * own (a gesture's geometry, a screen-anchored stroke).
2527
+ */
2528
+ declare function annotationSelectionFrame(record: ModelAnnotation, view?: ViewEnv, live?: {
2529
+ geometry?: Shape;
2530
+ style?: Style;
2531
+ measure?: MeasurementAppearance;
2532
+ }): SelectionFrame;
2533
+ //#endregion
1835
2534
  //#region src/scene.d.ts
2535
+ /**
2536
+ * Whether the item can be drawn live now (`scene(item)`): anything not
2537
+ * drawn from the engine's picture, a kind with a live drawing, and a lent
2538
+ * record its lender draws. A stamp or a link can't: its picture is its
2539
+ * drawing.
2540
+ */
2541
+ declare const hasLiveDrawing: (item: RenderItem) => boolean;
1836
2542
  /** The full painted scene for one annotation. */
1837
2543
  declare function scene(item: RenderItem): SceneNode[];
1838
2544
  //#endregion
@@ -1842,7 +2548,7 @@ declare function scene(item: RenderItem): SceneNode[];
1842
2548
  * close to horizontal or vertical. Diagonal strokes remain untouched: this is
1843
2549
  * a highlighter aid, not a general line simplifier.
1844
2550
  */
1845
- declare function straightenInkStroke(points: readonly Vec[], options: InkStraightenOptions): Vec[];
2551
+ declare function straightenInkStroke(points: readonly Point[], options: InkStraightenOptions): Point[];
1846
2552
  //#endregion
1847
- export { type AnchorMode, type Annot, type AnnotationFlags, type AnnotationKind, type AnnotationProps, type AnnotationPropsPatch, type BlendMode, type Border, type Callout, type ChromeGeom, type ChromeNode, type ClickCreate, type ClickPlacement, type CreationDraftAnchor, type Cursor, DEFAULT_CHROME_GEOM, DRAWN_FLAGS, type Draft, type Effect, FLAG_KEYS, type FlagBearer, type Geom, type GroupCaps, type Guide, type Handle, type Id, type InkStraightenOptions, KINDS, type KindCaps, type LineEnding, type LineEndings, MIN_DRAG, MITER_LIMIT, type Model, type Msg, NO_ANNOTATION_FLAGS, type Paint, type PatchScope, type PointerInput, type PropKey, type PropSpec, type Quad, RECT_HANDLES, ROTATE_KNOB_OFFSET, type Rect, type RectHandle, type RenderItem, type RenderNode, type RichTextRange, type RichTextStyleDelta, type SceneNode, type SnapResult, type SnapSettings, type Style, type Subtype, type Target, type TextAlign, type TextBox, type TextEndAnchor, type TextQuad, type TextStyle, type Vec, type ViewEnv, anchorModeOf, anchorOf, anchoredBox, anchoredGeom, anchoredStrokeWidth, annotContentsEditable, annotDeletable, annotInteractive, annotTransformable, applyProps, applyStyleToRange, calloutConnection, calloutLinePoints, canMove, capsFor, caretGeomFromAnchor, caretRectFromAnchor, caretRectFromTextEnd, centroidOf, chrome, clampRectToBox, clickCreateGeom, cloudyBorderExtent, cloudyPath, computeMoveSnap, contentToPdfPoint, contentToPdfRect, creationDraftAnchor, cursorAt, defaultsFor, expandGroups, fitStampBox, flagsEqual, geomBounds, geomDragHandle, geomHandles, geomHit, geomPdfBounds, geomResetRotation, geomRotateAbout, geomRotation, geomScaleAbout, geomScene, geomTranslate, geomVisualBounds, groupCaps, groupKeyOf, groupMembers, groupResizeAnchor, groupResizeBox, groupResizeFactors, hitTest, initialModel, initialStyle, initialTextStyle, interactive, isAttachedLink, isConversationOnly, isPlainRichText, isRotatableGeom, isSelectable, isSubstrateOnly, linkChildrenOf, linkOf, locateOffset, mergeFlags, normalizeDeg, normalizeRuns, obbFromGeom, pageItems, paragraphsFromPlainText, pdfToContentPoint, pdfToContentRect, placeRotateKnob, plainTextOf, pointInQuad, propsFor, quadIntersectsRect, rangeHasStyle, readProp, rectFromPoints, resolveClickPlacement, richTextLength, rotateDraftDelta, rotateKnob, rotatedAabb, rotatedHandleCursor, sameStyleDelta, scene, selectedItems, selectionAnchor, selectionBounds, selectionBoundsOnPage, selectionCenter, selectionKnob, selectionQuad, shapeRectFor, sharedProps, splitRunsAt, straightenInkStroke, styleAt, styleFromProps, textBoxes, textPlateInset, textStyleFromProps, transposedAboutCenter, unanchoredGeom, unionRect, update, uprightAnchoredRect, uprightRotation, viewable };
2553
+ export { type AnchorMode, type AnnotationFlags, type AnnotationKind, type AnnotationOption, type AnnotationProperty, type AnnotationView, type BlendMode, type BoxShape, type CalloutLine, type CaretShape, type ChangeSet, type ChromeGeometry, type ChromeNode, type ClickCreate, type Corners, type CreationDraftAnchor, type Cursor, DEFAULT_CHROME_GEOMETRY, DRAWN_FLAGS, DistanceAppearance, DistanceCaptionLayout, DistanceLayout, DistanceSegment, type Draft, type DrawState, EMPTY_CHANGE, type EditableKind, type EditableKinds, type Effect, FLAG_KEYS, FLAG_PROPERTIES, type FieldValues, type FlagBearer, type FlagKey, type FontLookup, type GestureForm, type GroupCaps, type Guide, type Handle, type HandleRole, type Id, type InkShape, type InkStraightenOptions, KINDS, type KindCaps, type KindName, LINE_ENDING_NAMES, type LineEnding, type LineEndings, type LineShape, MIN_DRAG, MITER_LIMIT, MeasurementAppearance, type MeasurementIntent, type Message, type Model, type ModelAnnotation, NO_ANNOTATION_FLAGS, NO_CAPS, type Paint, type PaintedPiece, type Placement, type PlacementFrame, type Point, type PointerInput, type PointsShape, type PolyShape, type Quad, type QuadRing, type QuadsShape, RECT_HANDLES, ROTATE_KNOB_OFFSET, type Rect, type RectHandle, type RenderItem, type RenderNode, type RichTextRange, type RichTextStyleDelta, type RotationAnchor, STANDARD_FONT_STACKS, type SceneFontProgram, type SceneNode, type SelectionFrame, type Session, type Shape, type ShapeFamily, ShapeMeasurementAppearance, ShapeMeasurementLayout, type SnapResult, type SnapSettings, type Stroke, type Style, type Target, type TextAlign, type TextBox, type TextBoxShape, type TextEndAnchor, type TextStyle, type TurnedBox, type UpdateResult, type ViewEnv, anchorModeOf, anchorOf, anchoredBox, anchoredGeom, anchoredStrokeWidth, annotContentsEditable, annotDeletable, annotInteractive, annotTransformable, annotationAfter, annotationAnchor, annotationOfNew, annotationSelectionFrame, applyStyleToRange, automaticShapeCaptionCenter, bodyFromTextStyle, bodyPieces, boxFamily, calloutEnd, canMove, captionPainted, caretFamily, caretFromAnchor, caretRectFromAnchor, chrome, clampRectToBox, cloudyPath, computeMoveSnap, creationDraftAnchor, cssFontFamilyForFont, cursorAt, defaultsFor, defineKind, distanceCaptionAt, distanceCaptionHit, distanceDrawnBounds, distanceHandles, distanceLabel, distanceLayout, distanceLeaderLength, distancePainted, distanceScene, distanceSelectionQuad, drawnAfter, drawnStrokesOf, editableKindOf, engineSubtypeOf, expandDistanceBounds, expandGroups, faceForFont, familyChosenBy, familyOf, fitStampBox, flagsEqual, fromDTO, geomBounds, geomDragHandle, geomHandles, geomHit, geomPainted, geomResetRotation, geomRotateAbout, geomRotation, geomScaleAbout, geomScene, geomTranslate, geomVisualBounds, gesturePlacement, groupCaps, groupKeyOf, groupMembers, groupOf, groupResizeAnchor, groupResizeBox, groupResizeFactors, hasLiveDrawing, hitTest, iconOf, initialModel, initialSession, initialTextStyle, interactive, irtOf, isAttachedLink, isContentsEditableByHand, isConversationOnly, isDeletableByHand, isDrag, isEditableByHand, isOnTextBox, isPlainRichText, isRotatableGeom, isSelectable, isSubstrateOnly, isTransformableByHand, kindNamed, kindOf, kindTakesLink, lineEndingsOf, linkChildRects, linkChildrenOf, linkOf, locateOffset, measurementCaptionScene, measurementDraftFields, measurementLayout, measurementOf, measurementSelectionQuad, mergeFlags, mountedFontFamily, moveDistanceCaption, moveMeasurementCaption, newRecordsAtMost, normalizeDeg, normalizeRuns, obbFromGeom, pageItems, paintedNear, paintedOf, paintedTouches, paragraphsFromPlainText, placeRotateKnob, placedShape, plainTextOf, pointInQuad, pointsFamily, propertiesOf, quadsFamily, rangeHasStyle, readOfDefaults, rectFromPoints, refOf, resolveClickPlacement, richDocOf, richTextLength, rotateDraftDelta, rotateKnob, rotatedAabb, rotatedHandleCursor, rotationAnchor, sameSession, sameStyleDelta, scene, selectedItems, selectionAnchor, selectionBounds, selectionBoundsOnPage, selectionInBox, selectionKnob, selectionQuad, shapeMeasurementLabel, shapeMeasurementLayout, shapeMeasurementReadout, shapeOf, sharedProperties, sourceOfConfirmed, sourceOfNew, splitRunsAt, straightenInkStroke, styleAt, styleOf, textBoxFamily, textBoxes, textOf, textPlateInset, toolAnnotation, transposedAboutCenter, turnPivotOf, unanchoredGeom, unionRect, unmadeItem, update, uprightAnchoredRect, uprightRotation, viewable, widgetAppearanceOf, withShape, withValues, writableTarget };
1848
2554
  //# sourceMappingURL=index.d.ts.map