@embedpdf/core-annotation 3.0.0-next.0
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/LICENSE +192 -0
- package/dist/index.cjs +5781 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1636 -0
- package/dist/index.d.ts +1636 -0
- package/dist/index.js +5680 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,1636 @@
|
|
|
1
|
+
import { AnnotationDTO, AnnotationFlags, AnnotationFlags as AnnotationFlags$1, AnnotationRef, BlendMode, BlendMode as BlendMode$1, CaretIntent, InkIntent, LineEnding, LineEndings, NO_ANNOTATION_FLAGS, PdfLinkTarget, PdfPoint, PdfRect, StrikeoutIntent } from "@embedpdf/engine-core/runtime";
|
|
2
|
+
import { PageObjectNumber } from "@embedpdf/core";
|
|
3
|
+
import { PageRotation, Point, Rect as Rect$1, Size } from "@embedpdf/core-geometry";
|
|
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;
|
|
13
|
+
/**
|
|
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).
|
|
17
|
+
*/
|
|
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
|
+
}
|
|
26
|
+
/** Interaction gate for a concrete annotation: widget kinds ignore `readOnly`
|
|
27
|
+
* (ISO 32000 — a ReadOnly form field must still be movable by a form designer;
|
|
28
|
+
* the form-filling layer enforces field ReadOnly itself). */
|
|
29
|
+
declare const annotInteractive: (a: FlagBearer) => boolean;
|
|
30
|
+
/** Geometry/style/delete mutations — `locked` freezes the OBJECT, not its
|
|
31
|
+
* contents (that's `lockedContents`). */
|
|
32
|
+
declare const annotTransformable: (a: FlagBearer) => boolean;
|
|
33
|
+
/** `/Contents` text edits — the contents counterpart of `locked`. */
|
|
34
|
+
declare const annotContentsEditable: (a: FlagBearer) => boolean;
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/types.d.ts
|
|
37
|
+
/**
|
|
38
|
+
* Content-space point/rect (y-down, PDF points, crop-relative) — the viewer space.
|
|
39
|
+
* These are the shared `@embedpdf/core-geometry` primitives: a `Vec` IS a `Point`
|
|
40
|
+
* and `Rect` IS geometry's `Rect`, so the whole stack speaks one vocabulary and
|
|
41
|
+
* the coordinate math lives in ONE package.
|
|
42
|
+
*/
|
|
43
|
+
type Vec = Point;
|
|
44
|
+
type Rect = Rect$1;
|
|
45
|
+
/** Four content-space points (a /QuadPoints quad). */
|
|
46
|
+
type Quad = [Vec, Vec, Vec, Vec];
|
|
47
|
+
type Id = string;
|
|
48
|
+
type Cursor = string;
|
|
49
|
+
type PolySubtype = 'polygon' | 'polyline';
|
|
50
|
+
/**
|
|
51
|
+
* The per-page view environment the screen-anchor projection consumes:
|
|
52
|
+
* `zoom` — the page's zoom RELATIVE to its 100% baseline (dimensionless;
|
|
53
|
+
* 1 = Acrobat's 100% = `viewUnitsPerPoint × userUnit` view px per point) —
|
|
54
|
+
* and the TOTAL display rotation (document /Rotate + view rotation).
|
|
55
|
+
* Deliberately NOT a px-per-point scale: `noZoom` means "hold the body at its
|
|
56
|
+
* 100%-zoom size", a statement about zoom, so feeding it a units conversion
|
|
57
|
+
* (the old mistake) shrank bodies at 100%. Per-event / per-call context (the
|
|
58
|
+
* `pageBox` pattern) — never stored on the model; the pointer drafts capture
|
|
59
|
+
* it at DOWN so anchored gestures project and commit with the view they
|
|
60
|
+
* started under. See anchor.ts.
|
|
61
|
+
*/
|
|
62
|
+
interface ViewEnv {
|
|
63
|
+
zoom: number;
|
|
64
|
+
rotation: PageRotation;
|
|
65
|
+
}
|
|
66
|
+
type Subtype = 'highlight' | 'underline' | 'squiggly' | 'strikeout' | 'square' | 'circle' | 'line' | 'polygon' | 'polyline' | 'caret' | 'ink' | 'stamp' | 'text' | 'file-attachment' | 'redact' | 'link' | (string & {});
|
|
67
|
+
/**
|
|
68
|
+
* Content-space geometry — the ONE thing hit-testing, editing, and rendering work
|
|
69
|
+
* on. A small closed union covers every kind: shapes (rect/ellipse), line,
|
|
70
|
+
* polygon/polyline (poly), text markup (quads), and caret.
|
|
71
|
+
*/
|
|
72
|
+
/**
|
|
73
|
+
* A free-text callout's leader: the `/CL` line + `/LE` arrow. `tip` is the
|
|
74
|
+
* called-out point (the arrow is drawn here); `knee` is the optional elbow. The
|
|
75
|
+
* point where the leader meets the text box (the third `/CL` point) is NEVER
|
|
76
|
+
* stored — it is DERIVED from the box + the knee (see `calloutConnection`), so it
|
|
77
|
+
* can't drift when the box or knee moves. Content space (y-down).
|
|
78
|
+
*/
|
|
79
|
+
interface Callout {
|
|
80
|
+
tip: Vec;
|
|
81
|
+
knee?: Vec;
|
|
82
|
+
ending: LineEnding;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Rotation (degrees, clockwise in content space, normalized `[0,360)`) carried by
|
|
86
|
+
* the rotatable `Geom` variants. The semantics differ by family, which is exactly
|
|
87
|
+
* the box-vs-vertex split:
|
|
88
|
+
*
|
|
89
|
+
* - **Box** (`rect`, `text`): `rect` is the UNROTATED local box and `rot` is the
|
|
90
|
+
* applied tilt — together they reconstruct the visual. The repository emits
|
|
91
|
+
* `rect` as `unrotatedRect`, `rot` as the rendered `/EMBD_Metadata/Rotation`,
|
|
92
|
+
* and the rotated visual AABB as `/Rect`, so PDFium bakes a portable `/AP`.
|
|
93
|
+
* - **Vertex** (`line`, `poly`, `ink`): the points are ALREADY rotated (they are
|
|
94
|
+
* the portable visual), so `rot` is an ADVISORY scalar — the cumulative tilt the
|
|
95
|
+
* user applied since authoring. It lets EmbedPDF reconstruct an oriented
|
|
96
|
+
* selection box (`obbFromTheta`) and offer reset-to-0; it is inert for
|
|
97
|
+
* rendering (PDFium ignores a lone `Rotation` with no `UnrotatedRect`).
|
|
98
|
+
*/
|
|
99
|
+
type Geom = {
|
|
100
|
+
t: 'rect';
|
|
101
|
+
rect: Rect;
|
|
102
|
+
ellipse: boolean;
|
|
103
|
+
rot?: number;
|
|
104
|
+
} | {
|
|
105
|
+
t: 'line';
|
|
106
|
+
a: Vec;
|
|
107
|
+
b: Vec;
|
|
108
|
+
ends?: LineEndings;
|
|
109
|
+
rot?: number;
|
|
110
|
+
} | {
|
|
111
|
+
t: 'poly';
|
|
112
|
+
points: Vec[];
|
|
113
|
+
closed: boolean;
|
|
114
|
+
ends?: LineEndings;
|
|
115
|
+
rot?: number;
|
|
116
|
+
} | {
|
|
117
|
+
t: 'quads';
|
|
118
|
+
quads: Quad[];
|
|
119
|
+
} | {
|
|
120
|
+
t: 'caret';
|
|
121
|
+
rect: Rect;
|
|
122
|
+
} | {
|
|
123
|
+
t: 'ink';
|
|
124
|
+
strokes: Vec[][];
|
|
125
|
+
rot?: number;
|
|
126
|
+
} | {
|
|
127
|
+
t: 'text';
|
|
128
|
+
rect: Rect;
|
|
129
|
+
callout?: Callout;
|
|
130
|
+
rot?: number;
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* How a shape's outline is stroked. A discriminated union so illegal combinations
|
|
134
|
+
* — a dash array on a cloudy border, an intensity on a dashed one — are simply
|
|
135
|
+
* unrepresentable. Maps onto the engine's `/BS /S` (`borderStyle`), `/BS /D`
|
|
136
|
+
* (`dashArray`), and `/BE /I` (`cloudyIntensity`) wire fields. Cloudy is only
|
|
137
|
+
* honoured for shapes (square/circle); other kinds treat it as solid.
|
|
138
|
+
*/
|
|
139
|
+
type Border = {
|
|
140
|
+
kind: 'solid';
|
|
141
|
+
} | {
|
|
142
|
+
kind: 'dashed';
|
|
143
|
+
dash: number[];
|
|
144
|
+
} | {
|
|
145
|
+
kind: 'cloudy';
|
|
146
|
+
intensity: number;
|
|
147
|
+
};
|
|
148
|
+
interface Style {
|
|
149
|
+
/** `/C` colour — stroke for geometric kinds, highlight colour for markup. */
|
|
150
|
+
color: string;
|
|
151
|
+
/** `/IC` interior (fill) colour. `null` when the annotation has no fill. */
|
|
152
|
+
interiorColor: string | null;
|
|
153
|
+
strokeWidth: number;
|
|
154
|
+
opacity: number;
|
|
155
|
+
/** Effective blend mode of the annotation's normal appearance. */
|
|
156
|
+
blendMode: BlendMode$1;
|
|
157
|
+
/** Outline style — defaults to `{ kind: 'solid' }`. */
|
|
158
|
+
border: Border;
|
|
159
|
+
}
|
|
160
|
+
type TextAlign = 'left' | 'center' | 'right';
|
|
161
|
+
/**
|
|
162
|
+
* Content-space text styling for a text-editable kind (free text) — the text
|
|
163
|
+
* counterpart of {@link Style}, projected from the DTO's `/DA` fields the same
|
|
164
|
+
* way `style` is projected from `/C`/`/CA`/`/BS`. CSS colour string; the engine
|
|
165
|
+
* `Color` seam is crossed only in the plugin repository.
|
|
166
|
+
*/
|
|
167
|
+
interface TextStyle {
|
|
168
|
+
/** A PDF standard font name or a registered font key. */
|
|
169
|
+
fontFamily: string;
|
|
170
|
+
/** Content units (PDF points). */
|
|
171
|
+
fontSize: number;
|
|
172
|
+
fontColor: string;
|
|
173
|
+
textAlign: TextAlign;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* The ONE flat vocabulary for editable appearance properties — what property
|
|
177
|
+
* sidebars/toolbars read and write, regardless of where each property is stored
|
|
178
|
+
* internally (`style`, `geom.ends`, or `text`). Which keys apply to a kind, and
|
|
179
|
+
* in what UI order, is declared per kind in the KIND table (`propsFor`).
|
|
180
|
+
*/
|
|
181
|
+
interface AnnotationProps extends Style, TextStyle {
|
|
182
|
+
/** `/LE` endings (line / polyline). */
|
|
183
|
+
lineEndings: LineEndings;
|
|
184
|
+
/**
|
|
185
|
+
* `/Name` icon of an icon kind (text note, file attachment). Optional in
|
|
186
|
+
* the bag — each kind falls back to its own spec default ('comment',
|
|
187
|
+
* 'paperclip') at creation, so one flat vocabulary needs no per-kind base
|
|
188
|
+
* value. Valid values are the `options` of the kind's `icon` PropSpec.
|
|
189
|
+
*/
|
|
190
|
+
icon?: string;
|
|
191
|
+
/**
|
|
192
|
+
* Where this annotation LINKS to, or `null` for none. Optional in the bag
|
|
193
|
+
* like `icon`. One key, two storages (the props.ts routing rule): on the
|
|
194
|
+
* link kind it is the annotation's OWN target (`/A`); on every other
|
|
195
|
+
* linkable kind it is the target of the ATTACHED link child(ren) —
|
|
196
|
+
* grouped `/Link` annotations the model folds into their parent (the
|
|
197
|
+
* serialization the PDF spec forces, since only Link annotations are
|
|
198
|
+
* clickable). Writing it creates/retargets/deletes those children through
|
|
199
|
+
* the ONE `syncLink` seam.
|
|
200
|
+
*/
|
|
201
|
+
link?: PdfLinkTarget | null;
|
|
202
|
+
}
|
|
203
|
+
type PropKey = keyof AnnotationProps;
|
|
204
|
+
/**
|
|
205
|
+
* What a committed edit CHANGED — carried on the `patch` effect so the shell
|
|
206
|
+
* emits exactly that intent (repository `toScopedPatch`) instead of
|
|
207
|
+
* reconstructing a full projection. `geometry` covers every gesture commit
|
|
208
|
+
* (move/resize/rotate/vertex edit); `props` forwards the user's patch keys
|
|
209
|
+
* verbatim. Text content never rides this effect (the debounced text-edit
|
|
210
|
+
* write owns `contents`).
|
|
211
|
+
*/
|
|
212
|
+
type PatchScope = {
|
|
213
|
+
kind: 'geometry';
|
|
214
|
+
} | {
|
|
215
|
+
kind: 'props';
|
|
216
|
+
keys: PropKey[];
|
|
217
|
+
};
|
|
218
|
+
/** A partial property write. `lineEndings` merges per side (set just `end`
|
|
219
|
+
* without knowing `start`); every other key overwrites. */
|
|
220
|
+
type AnnotationPropsPatch = { [K in PropKey]?: K extends 'lineEndings' ? Partial<LineEndings> : AnnotationProps[K]; };
|
|
221
|
+
interface Annot {
|
|
222
|
+
id: Id;
|
|
223
|
+
ref: AnnotationRef | null;
|
|
224
|
+
pon: PageObjectNumber;
|
|
225
|
+
subtype: Subtype;
|
|
226
|
+
geom: Geom;
|
|
227
|
+
style: Style;
|
|
228
|
+
/** Text styling — present only for text-editable kinds (free text). Like
|
|
229
|
+
* `style`, a content-space projection of `data`, editable via `setProps`. */
|
|
230
|
+
text?: TextStyle;
|
|
231
|
+
/** Redaction label (`/OverlayText` + `/Repeat`) — redact kind only. A
|
|
232
|
+
* projection of `data` like `text`; the hover preview scene draws it. */
|
|
233
|
+
label?: {
|
|
234
|
+
text: string;
|
|
235
|
+
repeat: boolean;
|
|
236
|
+
};
|
|
237
|
+
/** `/Name` icon — present only for icon kinds (text note, file attachment).
|
|
238
|
+
* Like `style`, a projection of `data`, editable via `setProps`. */
|
|
239
|
+
icon?: string;
|
|
240
|
+
/**
|
|
241
|
+
* The `/F` annotation flags, verbatim from the DTO (freshly drawn annotations
|
|
242
|
+
* start at {@link DRAWN_FLAGS} — `print` set). NEVER read individual keys to
|
|
243
|
+
* gate behavior — the predicates in `flags.ts` (`annotInteractive`,
|
|
244
|
+
* `annotTransformable`, `annotContentsEditable`, `viewable`) are the one
|
|
245
|
+
* interpretation of the spec, and `anchorModeOf` owns `noZoom`/`noRotate`.
|
|
246
|
+
*/
|
|
247
|
+
flags: AnnotationFlags$1;
|
|
248
|
+
source: 'baked' | 'vector';
|
|
249
|
+
/**
|
|
250
|
+
* Content-space box of the engine appearance raster (the AP `/Rect`), set when
|
|
251
|
+
* the annotation is derived from a DTO. While `source === 'baked'` the renderer
|
|
252
|
+
* blits the engine bitmap into this box; a move translates it (a rigid shift
|
|
253
|
+
* keeps the raster valid), so the bitmap rides along without re-rendering.
|
|
254
|
+
* Ignored once `source === 'vector'`.
|
|
255
|
+
*/
|
|
256
|
+
apBox?: Rect;
|
|
257
|
+
/**
|
|
258
|
+
* Rotation (deg, CW) that was STRIPPED from the baked raster — present only
|
|
259
|
+
* when the engine rendered this appearance rotation-free (a box-family kind
|
|
260
|
+
* whose DTO carries BOTH `rotation` and `unrotatedRect`; `apBox` is then the
|
|
261
|
+
* unrotated box). The blit re-applies it as a view transform about the box
|
|
262
|
+
* centre. Vertex kinds pre-rotate their geometry, so this stays unset there
|
|
263
|
+
* and their rasters blit untransformed.
|
|
264
|
+
*/
|
|
265
|
+
apRot?: number;
|
|
266
|
+
/**
|
|
267
|
+
* Revision of the engine-baked `/AP` CONTENT (absent ≡ 0). The raster a baked
|
|
268
|
+
* annotation shows depends on exactly this and the render scale — never on
|
|
269
|
+
* position (`apBox` translates the blit) or rotation (`apRot` transforms it) —
|
|
270
|
+
* so the shell re-fetches appearances precisely when it changes. Bumped by the
|
|
271
|
+
* `upsert` that confirms an engine re-bake with new content: a geometry patch
|
|
272
|
+
* that changed the authoring frame's SIZE resolving (`Effect.apChanged`), or a
|
|
273
|
+
* remote edit folding in. A move or rotate leaves it untouched: the old raster
|
|
274
|
+
* is still pixel-exact, so those cost zero re-renders.
|
|
275
|
+
*/
|
|
276
|
+
apVersion?: number;
|
|
277
|
+
/**
|
|
278
|
+
* The canonical engine DTO this annotation was derived from (PDF-space, sRGB)
|
|
279
|
+
* — the single source of truth for its data. `geom` and `style` are
|
|
280
|
+
* content-space RENDER PROJECTIONS of it, recomputed (never edited directly)
|
|
281
|
+
* whenever `data` changes, so the two can't drift. Absent only for a vector
|
|
282
|
+
* draft that hasn't been committed to the engine yet (no DTO exists).
|
|
283
|
+
*/
|
|
284
|
+
data?: AnnotationDTO;
|
|
285
|
+
/** Normalized PDF `/IT` for intent-bearing annotations authored before a DTO exists. */
|
|
286
|
+
intent?: CaretIntent | StrikeoutIntent | InkIntent;
|
|
287
|
+
/**
|
|
288
|
+
* The `link` prop's value (see {@link AnnotationProps.link}): the link
|
|
289
|
+
* kind's own target, or — on other linkable kinds — the folded attached
|
|
290
|
+
* link's target. Editable via `setProps`.
|
|
291
|
+
*/
|
|
292
|
+
link?: PdfLinkTarget | null;
|
|
293
|
+
/**
|
|
294
|
+
* INTERNAL join keys, never a prop: the engine refs of the attached link
|
|
295
|
+
* child annotation(s) this parent's `link` value is materialized as (one
|
|
296
|
+
* per visual segment for markup). Maintained by the repository fold and
|
|
297
|
+
* consumed by the shell's `syncLink` reconciler + delete expansion — the
|
|
298
|
+
* ONLY code allowed to touch those children. Absent on the link kind
|
|
299
|
+
* itself and on anything without an attached link.
|
|
300
|
+
*/
|
|
301
|
+
linkRefs?: AnnotationRef[];
|
|
302
|
+
/**
|
|
303
|
+
* Relationship to another annotation. `irt` ("in reply to") links a child to a
|
|
304
|
+
* parent — a reply in a comment thread, or a caret bound to its strikeout in a
|
|
305
|
+
* replace-text pair. `group` ties a set into one composite unit (created and,
|
|
306
|
+
* typically, deleted together). Both are unused until comments / replace-text
|
|
307
|
+
* land, but the field lives here from the start so select/delete/persistence
|
|
308
|
+
* never have to be retrofitted around it.
|
|
309
|
+
*/
|
|
310
|
+
irt?: Id;
|
|
311
|
+
group?: string;
|
|
312
|
+
}
|
|
313
|
+
/** A draggable handle: a resize corner/edge (rect) or a vertex (line/poly). */
|
|
314
|
+
interface Handle {
|
|
315
|
+
id: string;
|
|
316
|
+
at: Vec;
|
|
317
|
+
cursor: Cursor;
|
|
318
|
+
}
|
|
319
|
+
/** An alignment guide produced by move-snapping: a vertical (`axis: 'x'`) or
|
|
320
|
+
* horizontal (`axis: 'y'`) line at `at`, spanning `lo..hi` (content units). */
|
|
321
|
+
interface Guide {
|
|
322
|
+
axis: 'x' | 'y';
|
|
323
|
+
at: number;
|
|
324
|
+
lo: number;
|
|
325
|
+
hi: number;
|
|
326
|
+
}
|
|
327
|
+
/** Snapping behaviour — seeded from the plugin config, live-adjustable via the
|
|
328
|
+
* `setSnap` msg (so an app can wire a UI toggle). */
|
|
329
|
+
interface SnapSettings {
|
|
330
|
+
/** Alignment guides while moving (snap to other annotations + the page). */
|
|
331
|
+
guides: boolean;
|
|
332
|
+
/** Guide snap tolerance, content units (PDF pt) — the `hitMargin` convention. */
|
|
333
|
+
guideThreshold: number;
|
|
334
|
+
/** Snap the rotate gesture onto `rotationAngles`. */
|
|
335
|
+
rotation: boolean;
|
|
336
|
+
rotationAngles: number[];
|
|
337
|
+
/** Rotation snap tolerance, degrees. */
|
|
338
|
+
rotationThreshold: number;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* The `defaults` key a creation draft resolves its props from — the authoring
|
|
342
|
+
* TOOL that started it, which may differ from the PDF `subtype`. Two tools can
|
|
343
|
+
* share a subtype but carry distinct defaults (an "arrow" is a `line` with an
|
|
344
|
+
* arrowhead default); the preset keeps them apart. Absent → fall back to
|
|
345
|
+
* `subtype` (a headless / programmatic caller that isn't tool-driven), so the
|
|
346
|
+
* built-in tools where preset === subtype behave exactly as before.
|
|
347
|
+
*/
|
|
348
|
+
type Draft = {
|
|
349
|
+
g: 'create-rect';
|
|
350
|
+
subtype: Subtype;
|
|
351
|
+
preset?: string;
|
|
352
|
+
pon: PageObjectNumber;
|
|
353
|
+
from: Vec;
|
|
354
|
+
to: Vec;
|
|
355
|
+
ellipse: boolean;
|
|
356
|
+
/** Display rotation + upright policy captured at DOWN (the gesture's home
|
|
357
|
+
* page), so the commit counter-rotates against what the author SAW even
|
|
358
|
+
* if the up sample resolves elsewhere. See {@link PointerInput}. */
|
|
359
|
+
displayRotation?: PageRotation;
|
|
360
|
+
upright?: boolean;
|
|
361
|
+
/** The tool's click-create policy, captured at DOWN like `upright`. */
|
|
362
|
+
clickCreate?: ClickCreate | false;
|
|
363
|
+
/** The tool's `/F` seed, captured at DOWN like `upright` — merged over
|
|
364
|
+
* {@link DRAWN_FLAGS} at commit (a note tool sets noZoom/noRotate). */
|
|
365
|
+
flags?: Partial<AnnotationFlags$1>;
|
|
366
|
+
} | {
|
|
367
|
+
g: 'create-line';
|
|
368
|
+
subtype: Subtype;
|
|
369
|
+
preset?: string;
|
|
370
|
+
pon: PageObjectNumber;
|
|
371
|
+
from: Vec;
|
|
372
|
+
to: Vec;
|
|
373
|
+
/** The tool's click-create policy, captured at DOWN like `upright`. */
|
|
374
|
+
clickCreate?: ClickCreate | false;
|
|
375
|
+
/** The tool's `/F` seed, captured at DOWN (see the rect draft). */
|
|
376
|
+
flags?: Partial<AnnotationFlags$1>;
|
|
377
|
+
} | {
|
|
378
|
+
g: 'create-poly';
|
|
379
|
+
subtype: Subtype;
|
|
380
|
+
preset?: string;
|
|
381
|
+
pon: PageObjectNumber;
|
|
382
|
+
points: Vec[];
|
|
383
|
+
cur: Vec;
|
|
384
|
+
closed: boolean;
|
|
385
|
+
/** The tool's `/F` seed, captured at DOWN (see the rect draft). */
|
|
386
|
+
flags?: Partial<AnnotationFlags$1>;
|
|
387
|
+
} | {
|
|
388
|
+
g: 'create-ink';
|
|
389
|
+
subtype: Subtype;
|
|
390
|
+
preset?: string;
|
|
391
|
+
pon: PageObjectNumber;
|
|
392
|
+
strokes: Vec[][];
|
|
393
|
+
intent?: InkIntent;
|
|
394
|
+
/** The tool's `/F` seed, captured at DOWN (see the rect draft). */
|
|
395
|
+
flags?: Partial<AnnotationFlags$1>;
|
|
396
|
+
} | {
|
|
397
|
+
g: 'create-callout';
|
|
398
|
+
subtype: Subtype;
|
|
399
|
+
preset?: string;
|
|
400
|
+
pon: PageObjectNumber;
|
|
401
|
+
step: 'knee' | 'box';
|
|
402
|
+
tip: Vec;
|
|
403
|
+
knee?: Vec;
|
|
404
|
+
cur: Vec;
|
|
405
|
+
boxFrom?: Vec;
|
|
406
|
+
boxTo?: Vec;
|
|
407
|
+
/** Display rotation + upright policy captured at the TIP click (the
|
|
408
|
+
* gesture's home page) — the text BOX commits counter-rotated so it
|
|
409
|
+
* reads upright; the leader (tip/knee) is page-space and never turns.
|
|
410
|
+
* Same capture rule as the rect draft. */
|
|
411
|
+
displayRotation?: PageRotation;
|
|
412
|
+
upright?: boolean;
|
|
413
|
+
/** The tool's `/F` seed, captured at DOWN (see the rect draft). */
|
|
414
|
+
flags?: Partial<AnnotationFlags$1>;
|
|
415
|
+
} | {
|
|
416
|
+
g: 'move';
|
|
417
|
+
ids: Id[];
|
|
418
|
+
start: Vec;
|
|
419
|
+
delta: Vec;
|
|
420
|
+
guides: Guide[];
|
|
421
|
+
} | {
|
|
422
|
+
g: 'handle';
|
|
423
|
+
id: Id;
|
|
424
|
+
handle: string;
|
|
425
|
+
base: Geom;
|
|
426
|
+
cur: Geom;
|
|
427
|
+
view?: ViewEnv;
|
|
428
|
+
} | {
|
|
429
|
+
g: 'rotate';
|
|
430
|
+
ids: Id[];
|
|
431
|
+
pivot: Vec;
|
|
432
|
+
start: Vec;
|
|
433
|
+
cur: Vec;
|
|
434
|
+
free?: boolean;
|
|
435
|
+
view?: ViewEnv;
|
|
436
|
+
} | {
|
|
437
|
+
g: 'group';
|
|
438
|
+
op: 'resize';
|
|
439
|
+
ids: Id[];
|
|
440
|
+
handle: string;
|
|
441
|
+
anchor: Vec;
|
|
442
|
+
base: Rect;
|
|
443
|
+
cur: Rect;
|
|
444
|
+
view?: ViewEnv;
|
|
445
|
+
} | {
|
|
446
|
+
g: 'marquee';
|
|
447
|
+
pon: PageObjectNumber;
|
|
448
|
+
from: Vec;
|
|
449
|
+
to: Vec;
|
|
450
|
+
};
|
|
451
|
+
/** A live text-markup preview (the in-progress selection rendered as the markup it
|
|
452
|
+
* will become). Per page, since a selection can span pages. */
|
|
453
|
+
interface MarkupPreview {
|
|
454
|
+
subtype: Subtype;
|
|
455
|
+
/** Defaults key, distinct from subtype for presets such as replace-text. */
|
|
456
|
+
preset: string;
|
|
457
|
+
byPage: Record<number, Quad[]>;
|
|
458
|
+
}
|
|
459
|
+
/** Anchor + affordance state for UI that controls an in-progress creation draft. */
|
|
460
|
+
interface CreationDraftAnchor {
|
|
461
|
+
kind: 'poly';
|
|
462
|
+
subtype: PolySubtype;
|
|
463
|
+
pon: PageObjectNumber;
|
|
464
|
+
bounds: Rect;
|
|
465
|
+
pointCount: number;
|
|
466
|
+
minPoints: number;
|
|
467
|
+
canFinish: boolean;
|
|
468
|
+
}
|
|
469
|
+
interface Model {
|
|
470
|
+
byId: Record<Id, Annot>;
|
|
471
|
+
order: Id[];
|
|
472
|
+
selected: Id[];
|
|
473
|
+
/** The annotation under the pointer (topmost hit), or null. View-model
|
|
474
|
+
* state like `selected` — drives hover affordances (a redaction mark's
|
|
475
|
+
* applied-look preview) purely from the scene. Updated on CHANGE only
|
|
476
|
+
* (enter/leave cadence, never per-move). */
|
|
477
|
+
hovered: Id | null;
|
|
478
|
+
draft: Draft | null;
|
|
479
|
+
/** Transient ghost of an in-progress markup selection (null when idle). */
|
|
480
|
+
preview: MarkupPreview | null;
|
|
481
|
+
seq: number;
|
|
482
|
+
/** The base style new annotations inherit (per-tool `defaults` layer on top). */
|
|
483
|
+
style: Style;
|
|
484
|
+
/** Per-tool (keyed by subtype / tool id) property overrides for newly drawn
|
|
485
|
+
* annotations — the SAME flat vocabulary `setProps` uses. `lineEndings` is
|
|
486
|
+
* stored fully resolved (merged at `setDefaults` time). */
|
|
487
|
+
defaults: Record<string, AnnotationPropsPatch>;
|
|
488
|
+
/** Extra clickable margin (content units) around a stroke — bump it for touch. */
|
|
489
|
+
hitMargin: number;
|
|
490
|
+
/** The free-text annotation currently in TEXT-EDIT mode (its `contentEditable`
|
|
491
|
+
* is focused), or null. Distinct from `selected`: you select to move/resize,
|
|
492
|
+
* you edit to type. */
|
|
493
|
+
editing: Id | null;
|
|
494
|
+
/** Snapping behaviour (alignment guides + rotation). */
|
|
495
|
+
snap: SnapSettings;
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Selection-chrome geometry in CONTENT units — the grab zones and the knob
|
|
499
|
+
* stalk. The core is unit-agnostic and zoom-free: callers own the px→content
|
|
500
|
+
* conversion (settings are CSS px; divide by the page's view scale), so grab
|
|
501
|
+
* zones stay screen-constant across zoom.
|
|
502
|
+
*/
|
|
503
|
+
interface ChromeGeom {
|
|
504
|
+
/** Half-side of a resize/vertex handle's square grab zone. */
|
|
505
|
+
handleTol: number;
|
|
506
|
+
/** Half-side of the rotate knob's square grab zone. */
|
|
507
|
+
knobTol: number;
|
|
508
|
+
/** How far the rotate knob hangs off the selection edge. */
|
|
509
|
+
knobOffset: number;
|
|
510
|
+
}
|
|
511
|
+
interface PointerInput {
|
|
512
|
+
pon: PageObjectNumber;
|
|
513
|
+
point: Vec;
|
|
514
|
+
shift: boolean;
|
|
515
|
+
finish?: boolean;
|
|
516
|
+
/**
|
|
517
|
+
* The page's content box (`{0, 0, crop.width, crop.height}`) — when present,
|
|
518
|
+
* gestures are CLAMPED to it: a move keeps the selection's bounds inside the
|
|
519
|
+
* page (sliding along the edge when the pointer overshoots), resize/create
|
|
520
|
+
* points pin to the edge. Annotations are page-bound; the pointer isn't.
|
|
521
|
+
*/
|
|
522
|
+
pageBox?: Rect;
|
|
523
|
+
/**
|
|
524
|
+
* Chrome grab-zone geometry for this event — per-event environmental context
|
|
525
|
+
* exactly like `pageBox` (the caller converts its CSS-px settings by the
|
|
526
|
+
* page's view scale at dispatch). Absent → `DEFAULT_CHROME_GEOM`.
|
|
527
|
+
*/
|
|
528
|
+
chrome?: ChromeGeom;
|
|
529
|
+
/**
|
|
530
|
+
* The page's TOTAL display rotation (document /Rotate + view rotation) at the
|
|
531
|
+
* gesture — per-event environmental context like `pageBox`. A creation DOWN
|
|
532
|
+
* captures it on the draft (an `upright` commit counter-rotates against how
|
|
533
|
+
* the page was DISPLAYED); edit gestures read it per sample, paired with
|
|
534
|
+
* `scale`, so screen-anchored (`noZoom`/`noRotate`) annotations hit-test and
|
|
535
|
+
* clamp at their EFFECTIVE geometry. Content space itself never rotates.
|
|
536
|
+
*/
|
|
537
|
+
displayRotation?: PageRotation;
|
|
538
|
+
/**
|
|
539
|
+
* The page's zoom RELATIVE to its 100% baseline at the event — the other
|
|
540
|
+
* half of the {@link ViewEnv} pair with `displayRotation` (dimensionless,
|
|
541
|
+
* `transform.zoom`; NOT the px-per-point scale the caller uses for its
|
|
542
|
+
* CSS-px chrome conversion). Absent → 1 (headless callers; screen-anchored
|
|
543
|
+
* bodies then hit-test at rect size).
|
|
544
|
+
*/
|
|
545
|
+
zoom?: number;
|
|
546
|
+
/**
|
|
547
|
+
* Counter-rotate the created annotation so it reads upright at
|
|
548
|
+
* `displayRotation` — the authoring TOOL's `upright` policy, resolved by the
|
|
549
|
+
* caller (the core knows subtypes, not tools). Box kinds only (free-text /
|
|
550
|
+
* stamp are the ones with a natural reading orientation); vertex kinds and
|
|
551
|
+
* callouts ignore it.
|
|
552
|
+
*/
|
|
553
|
+
upright?: boolean;
|
|
554
|
+
/**
|
|
555
|
+
* Ids invisible to hit-testing and marquee for THIS event — per-event
|
|
556
|
+
* environmental context like `pageBox`. The caller (plugin shell) resolves
|
|
557
|
+
* its engaged Behaviors (form widgets under a fill tool render their own
|
|
558
|
+
* DOM and must not select/move), so the core stays behavior-agnostic.
|
|
559
|
+
*/
|
|
560
|
+
inert?: ReadonlySet<Id>;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* What a bare CLICK (a press-release under the drag threshold) creates for a
|
|
564
|
+
* tool: a default-size box with an EXPLICIT anchor (`center` unless stated —
|
|
565
|
+
* free text declares `top-left` so the box hangs where you'll type,
|
|
566
|
+
* display-frame-aware under `upright`), or a default-length line from the
|
|
567
|
+
* point (`angleDeg` 0 = rightward, CW-positive in y-down space). Resolved
|
|
568
|
+
* from the TOOL by the caller and passed on the message — the core knows
|
|
569
|
+
* subtypes, not tools (the `upright` pattern). `false` suppresses a kind's
|
|
570
|
+
* own click fallback (free text always click-creates by default).
|
|
571
|
+
* Anchoring is policy DATA, never inferred from the kind — the same policy
|
|
572
|
+
* drives annotation commits, footprint ghosts, and form-field placement
|
|
573
|
+
* (see `resolveClickPlacement`).
|
|
574
|
+
*/
|
|
575
|
+
type ClickCreate = {
|
|
576
|
+
width: number;
|
|
577
|
+
height: number;
|
|
578
|
+
anchor?: 'center' | 'top-left';
|
|
579
|
+
} | {
|
|
580
|
+
length: number;
|
|
581
|
+
angleDeg?: number;
|
|
582
|
+
};
|
|
583
|
+
type Msg = {
|
|
584
|
+
t: 'editPointer';
|
|
585
|
+
phase: 'down' | 'move' | 'up';
|
|
586
|
+
in: PointerInput;
|
|
587
|
+
} | {
|
|
588
|
+
t: 'marqueePointer';
|
|
589
|
+
phase: 'down' | 'move' | 'up';
|
|
590
|
+
in: PointerInput;
|
|
591
|
+
} | {
|
|
592
|
+
t: 'createPointer';
|
|
593
|
+
phase: 'down' | 'move' | 'up';
|
|
594
|
+
subtype: Subtype;
|
|
595
|
+
/** The authoring tool's `defaults` key (see {@link Draft}). Defaults to `subtype`. */
|
|
596
|
+
preset?: string;
|
|
597
|
+
/** PDF intent carried by an ink authoring preset. */
|
|
598
|
+
intent?: InkIntent;
|
|
599
|
+
/** The tool's click-create policy (see {@link ClickCreate}). */
|
|
600
|
+
clickCreate?: ClickCreate | false;
|
|
601
|
+
/** The tool's `/F` seed — merged over {@link DRAWN_FLAGS} at commit (a
|
|
602
|
+
* note tool passes `{ noZoom: true, noRotate: true }`). */
|
|
603
|
+
flags?: Partial<AnnotationFlags$1>;
|
|
604
|
+
/** Keep a completed ink stroke in the draft until `finishInkDraft`. */
|
|
605
|
+
deferInkCommit?: boolean;
|
|
606
|
+
/** Optional pure straight-line recognition applied to each completed stroke. */
|
|
607
|
+
straightenInk?: InkStraightenOptions;
|
|
608
|
+
in: PointerInput;
|
|
609
|
+
} | {
|
|
610
|
+
t: 'finishInkDraft';
|
|
611
|
+
} | {
|
|
612
|
+
t: 'finishCreationDraft';
|
|
613
|
+
} | {
|
|
614
|
+
t: 'createCaret';
|
|
615
|
+
pon: PageObjectNumber;
|
|
616
|
+
rect: Rect;
|
|
617
|
+
flags?: Partial<AnnotationFlags$1>;
|
|
618
|
+
} | {
|
|
619
|
+
t: 'createReplaceText';
|
|
620
|
+
pon: PageObjectNumber;
|
|
621
|
+
rects: Rect[];
|
|
622
|
+
endRect: Rect;
|
|
623
|
+
preset?: string;
|
|
624
|
+
} | {
|
|
625
|
+
t: 'createMarkup';
|
|
626
|
+
subtype: Subtype;
|
|
627
|
+
pon: PageObjectNumber;
|
|
628
|
+
rects: Rect[];
|
|
629
|
+
preset?: string;
|
|
630
|
+
/** The tool's `/F` seed — merged over {@link DRAWN_FLAGS} at commit. */
|
|
631
|
+
flags?: Partial<AnnotationFlags$1>;
|
|
632
|
+
} | {
|
|
633
|
+
t: 'setMarkupPreview';
|
|
634
|
+
subtype: Subtype;
|
|
635
|
+
rectsByPage: Record<number, Rect[]>;
|
|
636
|
+
preset?: string;
|
|
637
|
+
} | {
|
|
638
|
+
t: 'clearMarkupPreview';
|
|
639
|
+
} | {
|
|
640
|
+
t: 'deselect';
|
|
641
|
+
ids?: Id[];
|
|
642
|
+
} |
|
|
643
|
+
/** Pointer entered/left an annotation (topmost hit id, or null). Pure state. */
|
|
644
|
+
{
|
|
645
|
+
t: 'hover';
|
|
646
|
+
id: Id | null;
|
|
647
|
+
} | {
|
|
648
|
+
t: 'select';
|
|
649
|
+
ids: Id[];
|
|
650
|
+
add?: boolean;
|
|
651
|
+
} | {
|
|
652
|
+
t: 'setProps';
|
|
653
|
+
patch: AnnotationPropsPatch;
|
|
654
|
+
} | {
|
|
655
|
+
t: 'setFlags';
|
|
656
|
+
patch: Partial<AnnotationFlags$1>;
|
|
657
|
+
ids?: Id[];
|
|
658
|
+
} | {
|
|
659
|
+
t: 'setDefaults';
|
|
660
|
+
subtype: Subtype;
|
|
661
|
+
patch: AnnotationPropsPatch;
|
|
662
|
+
} | {
|
|
663
|
+
t: 'setSnap';
|
|
664
|
+
patch: Partial<SnapSettings>;
|
|
665
|
+
} | {
|
|
666
|
+
t: 'rotate90';
|
|
667
|
+
} | {
|
|
668
|
+
t: 'resetRotation';
|
|
669
|
+
} | {
|
|
670
|
+
t: 'delete';
|
|
671
|
+
} | {
|
|
672
|
+
t: 'cancel';
|
|
673
|
+
} | {
|
|
674
|
+
t: 'loaded';
|
|
675
|
+
annots: Annot[];
|
|
676
|
+
} | {
|
|
677
|
+
t: 'created';
|
|
678
|
+
tempId: Id;
|
|
679
|
+
id: Id;
|
|
680
|
+
ref: AnnotationRef;
|
|
681
|
+
} | {
|
|
682
|
+
t: 'createFailed';
|
|
683
|
+
tempId: Id;
|
|
684
|
+
} | {
|
|
685
|
+
t: 'upsert';
|
|
686
|
+
annots: Annot[];
|
|
687
|
+
bumpAp?: boolean;
|
|
688
|
+
} | {
|
|
689
|
+
t: 'bumpAp';
|
|
690
|
+
ids: Id[];
|
|
691
|
+
} | {
|
|
692
|
+
t: 'remove';
|
|
693
|
+
ids: Id[];
|
|
694
|
+
} | {
|
|
695
|
+
t: 'beginTextEdit';
|
|
696
|
+
id: Id;
|
|
697
|
+
} | {
|
|
698
|
+
t: 'setText';
|
|
699
|
+
id: Id;
|
|
700
|
+
text: string;
|
|
701
|
+
} | {
|
|
702
|
+
t: 'endTextEdit';
|
|
703
|
+
};
|
|
704
|
+
type Effect = {
|
|
705
|
+
fx: 'create';
|
|
706
|
+
id: Id;
|
|
707
|
+
} | {
|
|
708
|
+
fx: 'createGroup';
|
|
709
|
+
primary: Id;
|
|
710
|
+
members: Id[];
|
|
711
|
+
} |
|
|
712
|
+
/** `apChanged` is set (to `true`) ONLY when this patch INVALIDATED a baked
|
|
713
|
+
* raster — the annotation stayed `baked` (an opaque-body kind) and the edit
|
|
714
|
+
* resized its `/AP` frame, so the engine's re-bake produces new content (in
|
|
715
|
+
* practice: a stamp resize). The shell's resolve handler then turns it into
|
|
716
|
+
* an `upsert` with `bumpAp`. Absent for everything else — moves/rotations
|
|
717
|
+
* (the blit repositions the same pixels) and any kind that flipped to
|
|
718
|
+
* `vector` (it renders live; the raster stops mattering) — so those keep the
|
|
719
|
+
* bare `{ fx, id }` shape and trigger no appearance re-fetch. */
|
|
720
|
+
{
|
|
721
|
+
fx: 'patch';
|
|
722
|
+
id: Id;
|
|
723
|
+
scope: PatchScope;
|
|
724
|
+
apChanged?: true;
|
|
725
|
+
} |
|
|
726
|
+
/** A `/F`-only engine write for one committed annotation: the shell emits a
|
|
727
|
+
* flags-only patch (the model already holds the merged flags) and re-syncs
|
|
728
|
+
* PRESERVING the render source — flags never re-bake an appearance. */
|
|
729
|
+
{
|
|
730
|
+
fx: 'flags';
|
|
731
|
+
id: Id;
|
|
732
|
+
} |
|
|
733
|
+
/** The parent's `link` prop changed on a NON-link kind: reconcile its
|
|
734
|
+
* attached link children (create / retarget / delete) against the desired
|
|
735
|
+
* state derived from `link` + the parent's geometry. Declarative — the
|
|
736
|
+
* shell's reconciler is the only code that spells out child operations.
|
|
737
|
+
* (Geometry commits don't emit this; the shell re-runs the reconciler on
|
|
738
|
+
* any `patch` of an annotation that has `linkRefs`.) */
|
|
739
|
+
{
|
|
740
|
+
fx: 'syncLink';
|
|
741
|
+
id: Id;
|
|
742
|
+
} | {
|
|
743
|
+
fx: 'delete';
|
|
744
|
+
ref: AnnotationRef;
|
|
745
|
+
};
|
|
746
|
+
/** Per-annotation render data — its content geometry + style + live state. */
|
|
747
|
+
interface RenderItem {
|
|
748
|
+
id: Id;
|
|
749
|
+
ref: AnnotationRef | null;
|
|
750
|
+
subtype: Subtype;
|
|
751
|
+
geom: Geom;
|
|
752
|
+
/**
|
|
753
|
+
* The VISUAL box (geometry + stroke + line endings) in content space — the SAME
|
|
754
|
+
* `geomVisualBounds` that feeds the engine `/Rect`. The renderer paints into THIS
|
|
755
|
+
* box and does no bounds math of its own, so the on-screen box and the baked
|
|
756
|
+
* appearance can never drift (the v2 "patch computes the rect" rule).
|
|
757
|
+
*/
|
|
758
|
+
box: Rect;
|
|
759
|
+
/**
|
|
760
|
+
* Content-space box the engine appearance raster occupies (the AP `/Rect`),
|
|
761
|
+
* with the live move gesture applied — so a baked annotation's bitmap follows
|
|
762
|
+
* a drag. Only meaningful when `source === 'baked'`; absent otherwise.
|
|
763
|
+
*/
|
|
764
|
+
apBox?: Rect;
|
|
765
|
+
/**
|
|
766
|
+
* Rotation (deg, CW) to apply to the baked raster as a view transform —
|
|
767
|
+
* exactly the rotation the engine STRIPPED from it (see `Annot.apRot`).
|
|
768
|
+
* Live for `opaqueBody` kinds (a stamp spins with the rotate gesture);
|
|
769
|
+
* absent when the raster already contains its rotation (vertex kinds).
|
|
770
|
+
*/
|
|
771
|
+
apRot?: number;
|
|
772
|
+
style: Style;
|
|
773
|
+
/** Text styling (/DA projection) — present for text-bearing kinds (free
|
|
774
|
+
* text, text/choice widgets). Lets a behavior renderer's focused editor
|
|
775
|
+
* match the baked appearance's font. */
|
|
776
|
+
text?: TextStyle;
|
|
777
|
+
source: 'baked' | 'vector' | 'ghost';
|
|
778
|
+
selected: boolean;
|
|
779
|
+
/** The pointer is over this annotation — scene-level hover affordances
|
|
780
|
+
* (e.g. a redaction mark previews its applied look). */
|
|
781
|
+
hovered?: boolean;
|
|
782
|
+
/** Redaction label projection (redact kind only) — see {@link Annot.label}. */
|
|
783
|
+
label?: {
|
|
784
|
+
text: string;
|
|
785
|
+
repeat: boolean;
|
|
786
|
+
};
|
|
787
|
+
/**
|
|
788
|
+
* Applied rotation (deg, CW), or 0/undefined. For BOX kinds (`rect`/`text`)
|
|
789
|
+
* `box` is the UNROTATED visual box and the renderer applies this rotation
|
|
790
|
+
* about its centre (CSS/SVG transform). For VERTEX kinds the geometry is
|
|
791
|
+
* already rotated, so this is advisory only — the renderer must NOT re-apply it.
|
|
792
|
+
*/
|
|
793
|
+
rot?: number;
|
|
794
|
+
/**
|
|
795
|
+
* Mix-blend-mode the annotation composites with against the page (highlights
|
|
796
|
+
* multiply). The vector painter reads blend per scene node; the baked /AP image
|
|
797
|
+
* has no scene, so it reads this. Undefined = normal compositing.
|
|
798
|
+
*/
|
|
799
|
+
blend?: Exclude<BlendMode$1, 'normal'>;
|
|
800
|
+
}
|
|
801
|
+
/** The dumb draw vocabulary the framework renderer maps to SVG (content space).
|
|
802
|
+
* A CLOSED node (rect, ellipse, closed poly) takes the annotation's FILL colour;
|
|
803
|
+
* an open node (line, open poly — open arrows, butt, slash) is stroke-only. The
|
|
804
|
+
* stroke colour applies to every node. Closed-ness is the only fill signal. */
|
|
805
|
+
type RenderNode = {
|
|
806
|
+
kind: 'rect';
|
|
807
|
+
rect: Rect;
|
|
808
|
+
} | {
|
|
809
|
+
kind: 'ellipse';
|
|
810
|
+
rect: Rect;
|
|
811
|
+
} | {
|
|
812
|
+
kind: 'line';
|
|
813
|
+
a: Vec;
|
|
814
|
+
b: Vec;
|
|
815
|
+
} | {
|
|
816
|
+
kind: 'poly';
|
|
817
|
+
points: Vec[];
|
|
818
|
+
closed: boolean;
|
|
819
|
+
} | {
|
|
820
|
+
kind: 'path';
|
|
821
|
+
d: string;
|
|
822
|
+
};
|
|
823
|
+
/**
|
|
824
|
+
* How to paint one node. The pure core fills this in (per kind/subtype), and a
|
|
825
|
+
* framework renderer applies it verbatim — so ALL appearance logic (markup fill vs
|
|
826
|
+
* stroke, blend, dash, derived widths) lives once, in the portable core, not in
|
|
827
|
+
* every framework. Omitted `fill`/`stroke` mean none.
|
|
828
|
+
*/
|
|
829
|
+
interface Paint {
|
|
830
|
+
fill?: string;
|
|
831
|
+
stroke?: string;
|
|
832
|
+
width?: number;
|
|
833
|
+
opacity?: number;
|
|
834
|
+
dash?: number[];
|
|
835
|
+
blend?: Exclude<BlendMode$1, 'normal'>;
|
|
836
|
+
cap?: 'round';
|
|
837
|
+
join?: 'round';
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* A fully-painted draw node: geometry + paint. `scene(item)` returns these and a
|
|
841
|
+
* per-framework painter maps each to ONE element, applying `paint` — the entire
|
|
842
|
+
* surface a new framework renderer must implement. Supersedes the geometry-only
|
|
843
|
+
* `RenderNode` for rendering; `geomScene` stays the internal geometry helper.
|
|
844
|
+
*/
|
|
845
|
+
type SceneNode = {
|
|
846
|
+
kind: 'rect';
|
|
847
|
+
rect: Rect;
|
|
848
|
+
paint: Paint;
|
|
849
|
+
} | {
|
|
850
|
+
kind: 'ellipse';
|
|
851
|
+
rect: Rect;
|
|
852
|
+
paint: Paint;
|
|
853
|
+
} | {
|
|
854
|
+
kind: 'line';
|
|
855
|
+
a: Vec;
|
|
856
|
+
b: Vec;
|
|
857
|
+
paint: Paint;
|
|
858
|
+
} | {
|
|
859
|
+
kind: 'poly';
|
|
860
|
+
points: Vec[];
|
|
861
|
+
closed: boolean;
|
|
862
|
+
paint: Paint;
|
|
863
|
+
} | {
|
|
864
|
+
kind: 'path';
|
|
865
|
+
d: string;
|
|
866
|
+
paint: Paint;
|
|
867
|
+
} |
|
|
868
|
+
/** Painted (non-interactive) text — `at` is the BASELINE start point in
|
|
869
|
+
* content units. Editable text (free text) stays a framework element;
|
|
870
|
+
* this is for pure pixels, e.g. a redaction label preview. */
|
|
871
|
+
{
|
|
872
|
+
kind: 'text';
|
|
873
|
+
at: Vec;
|
|
874
|
+
text: string;
|
|
875
|
+
fontSize: number;
|
|
876
|
+
fontFamily?: string;
|
|
877
|
+
paint: Paint;
|
|
878
|
+
};
|
|
879
|
+
/** Pure geometry settings for recognising and axis-snapping a freehand stroke. */
|
|
880
|
+
interface InkStraightenOptions {
|
|
881
|
+
/** Maximum `max perpendicular deviation / endpoint distance`. */
|
|
882
|
+
deviationThreshold: number;
|
|
883
|
+
/** Degrees from horizontal/vertical within which the line snaps to that axis. */
|
|
884
|
+
axisSnapDegrees: number;
|
|
885
|
+
}
|
|
886
|
+
type ChromeNode = {
|
|
887
|
+
kind: 'outline';
|
|
888
|
+
rect: Rect;
|
|
889
|
+
} | {
|
|
890
|
+
kind: 'obb';
|
|
891
|
+
corners: [Vec, Vec, Vec, Vec];
|
|
892
|
+
angle: number;
|
|
893
|
+
} | {
|
|
894
|
+
kind: 'handle';
|
|
895
|
+
at: Vec;
|
|
896
|
+
cursor: Cursor;
|
|
897
|
+
rot?: number;
|
|
898
|
+
} | {
|
|
899
|
+
kind: 'rotate-knob';
|
|
900
|
+
at: Vec;
|
|
901
|
+
from: Vec;
|
|
902
|
+
} | {
|
|
903
|
+
kind: 'guide';
|
|
904
|
+
axis: 'x' | 'y';
|
|
905
|
+
at: number;
|
|
906
|
+
lo: number;
|
|
907
|
+
hi: number;
|
|
908
|
+
} | {
|
|
909
|
+
kind: 'angle-chip';
|
|
910
|
+
at: Vec;
|
|
911
|
+
angle: number;
|
|
912
|
+
} | {
|
|
913
|
+
kind: 'rotate-guides';
|
|
914
|
+
center: Vec;
|
|
915
|
+
angle: number;
|
|
916
|
+
lines: Array<{
|
|
917
|
+
a: Vec;
|
|
918
|
+
b: Vec;
|
|
919
|
+
role: 'axis' | 'indicator';
|
|
920
|
+
}>;
|
|
921
|
+
} | {
|
|
922
|
+
kind: 'marquee';
|
|
923
|
+
rect: Rect;
|
|
924
|
+
};
|
|
925
|
+
//#endregion
|
|
926
|
+
//#region src/anchor.d.ts
|
|
927
|
+
/** Which display factors this annotation is exempt from: `zoom` =
|
|
928
|
+
* screen-constant size, `upright` = screen-constant orientation. */
|
|
929
|
+
interface AnchorMode {
|
|
930
|
+
zoom: boolean;
|
|
931
|
+
upright: boolean;
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* The anchor behaviors of one annotation: its `/F` flags OR'd with the kind's
|
|
935
|
+
* statics — the spec's "Text (note) annotations behave as if NoZoom and
|
|
936
|
+
* NoRotate are ALWAYS set", expressed as kind caps. Null when not anchored.
|
|
937
|
+
*/
|
|
938
|
+
declare function anchorModeOf(a: FlagBearer): AnchorMode | null;
|
|
939
|
+
/** The fixed page point: the geometry's bounds top-left in content space
|
|
940
|
+
* (y-down) — which IS the spec's "upper-left corner of the annotation
|
|
941
|
+
* rectangle", since `/Rect` is emitted from these bounds. */
|
|
942
|
+
declare const anchorOf: (g: Geom) => Vec;
|
|
943
|
+
/**
|
|
944
|
+
* THE effective content-space geometry of a screen-anchored body at `view`:
|
|
945
|
+
* the stored geometry scaled by `1/max(zoom, 1)` about the anchor (`zoom`
|
|
946
|
+
* exemption, Adobe-clamped) and counter-rotated by `-rotation` about it
|
|
947
|
+
* (`upright`), so that after the page's own display transform the body reads
|
|
948
|
+
* at its 100%-zoom size and its authored tilt, hanging from the fixed anchor.
|
|
949
|
+
* Returns `g` unchanged when there is nothing to do — callers apply it
|
|
950
|
+
* unconditionally.
|
|
951
|
+
*/
|
|
952
|
+
declare function anchoredGeom(g: Geom, mode: AnchorMode | null, view: ViewEnv | undefined): Geom;
|
|
953
|
+
/**
|
|
954
|
+
* The exact inverse of {@link anchoredGeom} for a VIEW-space geometry a
|
|
955
|
+
* gesture produced: the stored geometry whose OWN projection is `target`.
|
|
956
|
+
*
|
|
957
|
+
* Solving `anchoredGeom(stored) = target` where the projection anchors at
|
|
958
|
+
* `stored`'s (unknown) bounds top-left `a` gives a closed form: map `target`
|
|
959
|
+
* through the pure linear part (scale `s` + rotate `r` about the origin),
|
|
960
|
+
* then translate so the result's bounds top-left `b` lands where the
|
|
961
|
+
* projection needs it — at `a = R(-r)·b / s`, the unique fixed point. This is
|
|
962
|
+
* what makes a released gesture commit EXACTLY what its preview showed.
|
|
963
|
+
*/
|
|
964
|
+
declare function unanchoredGeom(target: Geom, mode: AnchorMode | null, view: ViewEnv | undefined): Geom;
|
|
965
|
+
/** Stroke width in the projected (view) space: a `noZoom` body's line weight
|
|
966
|
+
* scales with its geometry so it stays screen-constant — through the SAME
|
|
967
|
+
* factors (incl. the ≤100% clamp) the geometry projects with. */
|
|
968
|
+
declare const anchoredStrokeWidth: (width: number, mode: AnchorMode | null, view: ViewEnv | undefined) => number;
|
|
969
|
+
/**
|
|
970
|
+
* The similarity image of an axis-aligned box (a baked raster's AP `/Rect`)
|
|
971
|
+
* under the anchor projection, as the blit vocabulary expects it: an
|
|
972
|
+
* UNROTATED box (the projected centre + scaled size) plus the rotation to
|
|
973
|
+
* re-apply about its centre. `anchor` is the OWNING GEOMETRY's anchor — the
|
|
974
|
+
* raster rides its annotation's projection, it doesn't anchor itself. Null
|
|
975
|
+
* when the projection is the identity.
|
|
976
|
+
*/
|
|
977
|
+
declare function anchoredBox(box: {
|
|
978
|
+
x: number;
|
|
979
|
+
y: number;
|
|
980
|
+
width: number;
|
|
981
|
+
height: number;
|
|
982
|
+
}, anchor: Vec, mode: AnchorMode | null, view: ViewEnv | undefined): {
|
|
983
|
+
box: {
|
|
984
|
+
x: number;
|
|
985
|
+
y: number;
|
|
986
|
+
width: number;
|
|
987
|
+
height: number;
|
|
988
|
+
};
|
|
989
|
+
rot: number;
|
|
990
|
+
} | null;
|
|
991
|
+
//#endregion
|
|
992
|
+
//#region src/update.d.ts
|
|
993
|
+
/** The click ↔ drag threshold (content units): a press-release whose width AND
|
|
994
|
+
* height both stay under it is a CLICK. Exported so every gesture owner (the
|
|
995
|
+
* draw handler, the form plugin's place handler) shares ONE definition. */
|
|
996
|
+
declare const MIN_DRAG = 3;
|
|
997
|
+
declare const initialStyle: Style;
|
|
998
|
+
declare const initialModel: Model;
|
|
999
|
+
/**
|
|
1000
|
+
* Resolve a tool's effective defaults as a FULL flat props bag: the base `style`
|
|
1001
|
+
* + the font/endings base, with the per-tool override layered on top. This is
|
|
1002
|
+
* what a defaults-editing UI reads, and what creation projects `style`/`text`
|
|
1003
|
+
* from (`styleFromProps` / `textStyleFromProps`).
|
|
1004
|
+
*/
|
|
1005
|
+
declare function defaultsFor(m: Model, subtype: Subtype): AnnotationProps;
|
|
1006
|
+
/**
|
|
1007
|
+
* The live rotation of a rotate draft, snapping applied — the ONE angle rule
|
|
1008
|
+
* shared by the preview (`effGeom`), the commit (`editUp`) and the angle chip,
|
|
1009
|
+
* so they can never disagree. The selection's ABSOLUTE angle (a single member's
|
|
1010
|
+
* `rot` + the raw pointer delta; a group's raw delta from 0) locks onto the
|
|
1011
|
+
* configured angles within the threshold; `free` (shift held) bypasses.
|
|
1012
|
+
* `delta` is what `geomRotateAbout` applies; `angle` is what the chip shows.
|
|
1013
|
+
*/
|
|
1014
|
+
declare function rotateDraftDelta(m: Model, d: Extract<Draft, {
|
|
1015
|
+
g: 'rotate';
|
|
1016
|
+
}>): {
|
|
1017
|
+
delta: number;
|
|
1018
|
+
angle: number;
|
|
1019
|
+
snapped: boolean;
|
|
1020
|
+
};
|
|
1021
|
+
declare function update(m: Model, msg: Msg): [Model, Effect[]];
|
|
1022
|
+
//#endregion
|
|
1023
|
+
//#region src/placement.d.ts
|
|
1024
|
+
/** A resolved click placement: what the click will occupy, page-clamped. */
|
|
1025
|
+
type ClickPlacement = {
|
|
1026
|
+
kind: 'box';
|
|
1027
|
+
rect: Rect;
|
|
1028
|
+
/** Upright counter-rotation captured for the commit (deg CW; 0 = none). */
|
|
1029
|
+
rot: number;
|
|
1030
|
+
} | {
|
|
1031
|
+
kind: 'segment';
|
|
1032
|
+
a: Vec;
|
|
1033
|
+
b: Vec;
|
|
1034
|
+
};
|
|
1035
|
+
/** Slide a rect (as a unit) to sit inside `box`; pins at the origin edge when
|
|
1036
|
+
* it doesn't fit. Placements are page-bound; the pointer isn't. */
|
|
1037
|
+
declare const clampRectToBox: (r: Rect, box: Rect | undefined) => Rect;
|
|
1038
|
+
/**
|
|
1039
|
+
* Resolve a click-create POLICY at a content point. `anchor` defaults to
|
|
1040
|
+
* `center`; under `upright` a box counter-rotates against the page's display
|
|
1041
|
+
* rotation exactly as the drag commit would (centre-anchored boxes transpose
|
|
1042
|
+
* about their centre, top-left boxes anchor in the DISPLAY frame).
|
|
1043
|
+
*/
|
|
1044
|
+
declare function resolveClickPlacement(point: Vec, policy: ClickCreate, opts?: {
|
|
1045
|
+
pageBox?: Rect;
|
|
1046
|
+
upright?: boolean;
|
|
1047
|
+
displayRotation?: PageRotation;
|
|
1048
|
+
}): ClickPlacement;
|
|
1049
|
+
/**
|
|
1050
|
+
* ANNOTATION-ONLY: convert a placement into the `Geom` the commit stores and
|
|
1051
|
+
* the ghost paints, for a routing kind. This is where annotation VISUAL
|
|
1052
|
+
* semantics live — ellipse for circles, the cloudy outer-box via
|
|
1053
|
+
* `shapeRectFor`. Forms never call this; a field box is the placement rect
|
|
1054
|
+
* itself. Null for kinds a click cannot author.
|
|
1055
|
+
*/
|
|
1056
|
+
declare function clickCreateGeom(subtype: Subtype, placement: ClickPlacement, def: AnnotationProps): Geom | null;
|
|
1057
|
+
//#endregion
|
|
1058
|
+
//#region src/snap.d.ts
|
|
1059
|
+
interface SnapResult {
|
|
1060
|
+
delta: Vec;
|
|
1061
|
+
guides: Guide[];
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Snap a move delta: shift the selection's union bounds by `raw`, compare its
|
|
1065
|
+
* 3 keys per axis (min / center / max) against every target's, and take the
|
|
1066
|
+
* closest in-threshold pair per axis as an adjustment. Targets are the page box
|
|
1067
|
+
* (edges + center) and every non-moving annotation on the page.
|
|
1068
|
+
*/
|
|
1069
|
+
declare function computeMoveSnap(m: Model, ids: Id[], pon: number, raw: Vec, threshold: number, pageBox: Rect | undefined): SnapResult;
|
|
1070
|
+
//#endregion
|
|
1071
|
+
//#region src/view.d.ts
|
|
1072
|
+
declare function pageItems(m: Model, pon: number, view?: ViewEnv): RenderItem[];
|
|
1073
|
+
/** One free-text box, geometry only (the live move/resize gesture applied), with
|
|
1074
|
+
* its edit flag. The framework renders an editable element here; the plugin
|
|
1075
|
+
* layers the DTO-derived text style on top. */
|
|
1076
|
+
interface TextBox {
|
|
1077
|
+
id: Id;
|
|
1078
|
+
box: Rect;
|
|
1079
|
+
editing: boolean;
|
|
1080
|
+
/** Applied rotation (deg, CW). `box` is the UNROTATED text box; the framework
|
|
1081
|
+
* rotates the editable element about its centre by this. 0/undefined = none. */
|
|
1082
|
+
rot?: number;
|
|
1083
|
+
}
|
|
1084
|
+
/** The free-text boxes on a page — the text counterpart of `pageItems`. */
|
|
1085
|
+
declare function textBoxes(m: Model, pon: number, view?: ViewEnv): TextBox[];
|
|
1086
|
+
/** The currently selected annotations as render items (live gesture applied) —
|
|
1087
|
+
* cross-page, for selection-aware toolbars (style + line-ending editing). */
|
|
1088
|
+
declare function selectedItems(m: Model, view?: ViewEnv): RenderItem[];
|
|
1089
|
+
/**
|
|
1090
|
+
* The rotate knob for the current selection on `pon` — the SAME knob `chrome`
|
|
1091
|
+
* draws — or null when the selection has none (non-rotatable single, or a group
|
|
1092
|
+
* whose caps aren't rotatable). A single shape's knob hangs off its OBB top edge;
|
|
1093
|
+
* a group's off the union box. With `pageBox` the knob is placed PAGE-BOUND
|
|
1094
|
+
* (`placeRotateKnob`: flip below / clamp inside) — off-page it would be visible
|
|
1095
|
+
* but unreachable, since pointer dispatch resolves pages by containment. Shared
|
|
1096
|
+
* by `chrome` (to draw) and `selectionAnchor` (to push the menu clear of it), so
|
|
1097
|
+
* the two can never disagree on where it is.
|
|
1098
|
+
*
|
|
1099
|
+
* The placement DECISION is made at rest; a gesture never re-decides — it RIDES.
|
|
1100
|
+
* During a live rotate the knob starts exactly where it was grabbed (the rest
|
|
1101
|
+
* placement on the COMMITTED geometry) and turns rigidly about the pivot by the
|
|
1102
|
+
* same snapped delta the shape uses; the policy re-evaluates only on release.
|
|
1103
|
+
* Re-deciding per frame would jump at grab and side-switch mid-spin.
|
|
1104
|
+
*/
|
|
1105
|
+
declare function selectionKnob(m: Model, pon: number, pageBox?: Rect, knobOffset?: number, view?: ViewEnv): {
|
|
1106
|
+
at: Vec;
|
|
1107
|
+
from: Vec;
|
|
1108
|
+
} | null;
|
|
1109
|
+
declare function chrome(m: Model, pon: number, pageBox?: Rect, knobOffset?: number, view?: ViewEnv): ChromeNode[];
|
|
1110
|
+
/** The content-space union of the selectable selected items on `pon`, or null if
|
|
1111
|
+
* the page holds none. This is the SAME box the chrome outline draws, so a
|
|
1112
|
+
* floating menu sits exactly on the selection. */
|
|
1113
|
+
declare function selectionBoundsOnPage(m: Model, pon: number, view?: ViewEnv): Rect | null;
|
|
1114
|
+
/** The anchor for a selection-aware floating menu: the PRIMARY page (the first
|
|
1115
|
+
* selectable selected id) + the union box of the selection on that page (content
|
|
1116
|
+
* space). Null when nothing selectable is selected. A cross-page selection
|
|
1117
|
+
* anchors to its primary page, so there is exactly one menu. `pageBoxOf` is a
|
|
1118
|
+
* lookup (the anchor resolves its own page) feeding the page-bound knob
|
|
1119
|
+
* placement — the menu then dodges the knob where it ACTUALLY sits. */
|
|
1120
|
+
declare function selectionAnchor(m: Model, pageBoxOf?: (pon: number) => Rect | undefined, knobOffsetOf?: (pon: number) => number | undefined, viewOf?: (pon: number) => ViewEnv | undefined): {
|
|
1121
|
+
pon: number;
|
|
1122
|
+
bounds: Rect;
|
|
1123
|
+
knob?: Vec;
|
|
1124
|
+
} | null;
|
|
1125
|
+
/** Anchor for controls that finish/cancel an active multi-click creation draft.
|
|
1126
|
+
* It is rect-based like selectionAnchor, using the committed vertices only so
|
|
1127
|
+
* the menu remains stable while the hover preview follows the pointer. */
|
|
1128
|
+
declare function creationDraftAnchor(m: Model): CreationDraftAnchor | null;
|
|
1129
|
+
//#endregion
|
|
1130
|
+
//#region src/hit.d.ts
|
|
1131
|
+
type Target = {
|
|
1132
|
+
t: 'handle';
|
|
1133
|
+
id: Id;
|
|
1134
|
+
handle: string;
|
|
1135
|
+
cursor: Cursor;
|
|
1136
|
+
} | {
|
|
1137
|
+
t: 'rotate';
|
|
1138
|
+
ids: Id[];
|
|
1139
|
+
pivot: Vec;
|
|
1140
|
+
} | {
|
|
1141
|
+
t: 'group-handle';
|
|
1142
|
+
ids: Id[];
|
|
1143
|
+
handle: string;
|
|
1144
|
+
cursor: Cursor;
|
|
1145
|
+
box: Rect;
|
|
1146
|
+
} | {
|
|
1147
|
+
t: 'annot';
|
|
1148
|
+
id: Id;
|
|
1149
|
+
} | {
|
|
1150
|
+
t: 'empty';
|
|
1151
|
+
};
|
|
1152
|
+
/** Can this annotation be clicked to select? (`/F` interaction flags override
|
|
1153
|
+
* all caps — hidden/noView/readOnly are inert; LOCKED stays selectable, it
|
|
1154
|
+
* just won't transform.) */
|
|
1155
|
+
declare const isSelectable: (m: Model, id: Id) => boolean;
|
|
1156
|
+
/** Can this annotation be dragged by its body to move? (`locked` freezes it.) */
|
|
1157
|
+
declare const canMove: (m: Model, id: Id) => boolean;
|
|
1158
|
+
/**
|
|
1159
|
+
* What's under the content point.
|
|
1160
|
+
* 1. a resize/vertex handle of the single selection,
|
|
1161
|
+
* 2. an editable annotation body — a SELECTED one anywhere in its bounds (so you
|
|
1162
|
+
* can drag to move it), an UNSELECTED one only on its stroke/fill (margin-aware,
|
|
1163
|
+
* so an unfilled circle is grabbed only on its outline),
|
|
1164
|
+
* 3. else empty.
|
|
1165
|
+
*/
|
|
1166
|
+
declare function hitTest(m: Model, pon: number, p: Vec, geom: ChromeGeom, strokeMargin: number, pageBox?: Rect, inert?: ReadonlySet<Id>, view?: ViewEnv): Target;
|
|
1167
|
+
/** The cursor to show on hover: a resize cursor over a handle, move/pointer over a body. */
|
|
1168
|
+
declare function cursorAt(m: Model, pon: number, p: Vec, geom: ChromeGeom, strokeMargin: number, pageBox?: Rect, inert?: ReadonlySet<Id>, view?: ViewEnv): Cursor | null;
|
|
1169
|
+
//#endregion
|
|
1170
|
+
//#region src/group.d.ts
|
|
1171
|
+
/**
|
|
1172
|
+
* The transform capabilities of a multi-target (group) selection: a group can
|
|
1173
|
+
* move/resize/rotate only if EVERY member's kind allows that group op AND no
|
|
1174
|
+
* member is locked. This is the SINGLE resolver every adapter consumes (v2
|
|
1175
|
+
* duplicated the `.every(...)` across three framework components). The iso-vs-
|
|
1176
|
+
* aniso resize choice is computed separately, per-gesture, since it depends on
|
|
1177
|
+
* the live `rot` of the members, not the static caps.
|
|
1178
|
+
*/
|
|
1179
|
+
interface GroupCaps {
|
|
1180
|
+
movable: boolean;
|
|
1181
|
+
resizable: boolean;
|
|
1182
|
+
rotatable: boolean;
|
|
1183
|
+
}
|
|
1184
|
+
declare function groupCaps(m: Model, ids: Id[]): GroupCaps;
|
|
1185
|
+
/**
|
|
1186
|
+
* The key of the group `id` belongs to, or `null` when it is ungrouped. A
|
|
1187
|
+
* subordinate's key is its `group` field; a primary's key is its own id (it is
|
|
1188
|
+
* the target of at least one member's `group`). An annotation that is neither a
|
|
1189
|
+
* subordinate nor the target of any subordinate is ungrouped.
|
|
1190
|
+
*/
|
|
1191
|
+
declare function groupKeyOf(m: Model, id: Id): Id | null;
|
|
1192
|
+
/**
|
|
1193
|
+
* Every member of the group containing `id` (primary first), or just `[id]`
|
|
1194
|
+
* when it is ungrouped. The primary is the annotation whose id is the key; the
|
|
1195
|
+
* rest are everything whose `group` equals the key, in `order`.
|
|
1196
|
+
*/
|
|
1197
|
+
declare function groupMembers(m: Model, id: Id): Id[];
|
|
1198
|
+
/** Union of every id's full group — the selection seen as whole groups. */
|
|
1199
|
+
declare function expandGroups(m: Model, ids: Id[]): Id[];
|
|
1200
|
+
//#endregion
|
|
1201
|
+
//#region src/kinds.d.ts
|
|
1202
|
+
/**
|
|
1203
|
+
* One editable property of a kind, as a UI contract: which {@link AnnotationProps}
|
|
1204
|
+
* key, rendered how (the union arm fixes the control + its constraints), labelled
|
|
1205
|
+
* what by default. A property sidebar is a `switch (spec.key)` over these — the
|
|
1206
|
+
* per-kind lists below are the v2 snippet's hand-rolled `TOOL_PROPERTIES` schema,
|
|
1207
|
+
* promoted into the library so every consumer gets it for free.
|
|
1208
|
+
*
|
|
1209
|
+
* `label` is a default (English) display name — apps with i18n map `key`s to
|
|
1210
|
+
* their own strings and ignore it. Array ORDER is display order.
|
|
1211
|
+
*/
|
|
1212
|
+
type PropSpec = {
|
|
1213
|
+
key: 'color';
|
|
1214
|
+
label: string;
|
|
1215
|
+
} | {
|
|
1216
|
+
key: 'interiorColor';
|
|
1217
|
+
label: string;
|
|
1218
|
+
} | {
|
|
1219
|
+
key: 'fontColor';
|
|
1220
|
+
label: string;
|
|
1221
|
+
} | {
|
|
1222
|
+
key: 'opacity';
|
|
1223
|
+
label: string;
|
|
1224
|
+
min: number;
|
|
1225
|
+
max: number;
|
|
1226
|
+
step: number;
|
|
1227
|
+
} | {
|
|
1228
|
+
key: 'strokeWidth';
|
|
1229
|
+
label: string;
|
|
1230
|
+
min: number;
|
|
1231
|
+
max: number;
|
|
1232
|
+
step: number;
|
|
1233
|
+
} | {
|
|
1234
|
+
key: 'fontSize';
|
|
1235
|
+
label: string;
|
|
1236
|
+
min: number;
|
|
1237
|
+
max: number;
|
|
1238
|
+
step: number;
|
|
1239
|
+
} |
|
|
1240
|
+
/** Border style picker; `cloudy` says whether this kind honours a cloudy border. */
|
|
1241
|
+
{
|
|
1242
|
+
key: 'border';
|
|
1243
|
+
label: string;
|
|
1244
|
+
cloudy: boolean;
|
|
1245
|
+
} | {
|
|
1246
|
+
key: 'lineEndings';
|
|
1247
|
+
label: string;
|
|
1248
|
+
} | {
|
|
1249
|
+
key: 'fontFamily';
|
|
1250
|
+
label: string;
|
|
1251
|
+
} | {
|
|
1252
|
+
key: 'textAlign';
|
|
1253
|
+
label: string;
|
|
1254
|
+
} | {
|
|
1255
|
+
key: 'blendMode';
|
|
1256
|
+
label: string;
|
|
1257
|
+
} |
|
|
1258
|
+
/** `/Name` icon picker for icon kinds; `options` are the legal names. */
|
|
1259
|
+
{
|
|
1260
|
+
key: 'icon';
|
|
1261
|
+
label: string;
|
|
1262
|
+
options: readonly string[];
|
|
1263
|
+
} |
|
|
1264
|
+
/** Link-target editor (URL / page destination). Declared by the link kind
|
|
1265
|
+
* (its own target) and by every kind that may carry an ATTACHED link;
|
|
1266
|
+
* kinds that omit it (widgets, caret, redact…) simply cannot be links —
|
|
1267
|
+
* `applyProps` drops the key and menus never show the control. */
|
|
1268
|
+
{
|
|
1269
|
+
key: 'link';
|
|
1270
|
+
label: string;
|
|
1271
|
+
};
|
|
1272
|
+
/** Orthogonal capability flags. Static data — the annotation's `/F` flags are
|
|
1273
|
+
* the runtime overrides (a locked annotation is never transformable, a hidden
|
|
1274
|
+
* one never renders, regardless of these — see flags.ts). */
|
|
1275
|
+
interface KindCaps {
|
|
1276
|
+
/** Can be clicked to select. */
|
|
1277
|
+
selectable: boolean;
|
|
1278
|
+
/** Can be dragged (by its body) to translate. */
|
|
1279
|
+
movable: boolean;
|
|
1280
|
+
/** Exposes the 8 box resize handles (shapes). */
|
|
1281
|
+
resizable: boolean;
|
|
1282
|
+
/** Exposes per-vertex handles (line endpoints, polygon/polyline vertices). */
|
|
1283
|
+
vertexEditable: boolean;
|
|
1284
|
+
/** Can be rotated (shapes, free text, lines/polys/ink). */
|
|
1285
|
+
rotatable: boolean;
|
|
1286
|
+
/** Can be MOVED as part of a multi-target (group) transform. */
|
|
1287
|
+
groupMovable: boolean;
|
|
1288
|
+
/** Can be uniformly SCALED as part of a multi-target (group) transform — ON
|
|
1289
|
+
* even for vertex kinds that have no single-shape box resize (their handles
|
|
1290
|
+
* ARE the vertices; in a group they scale fine). */
|
|
1291
|
+
groupResizable: boolean;
|
|
1292
|
+
/** Can be ROTATED as part of a multi-target (group) transform. */
|
|
1293
|
+
groupRotatable: boolean;
|
|
1294
|
+
/** Carries editable text content (free text, the comment popup). */
|
|
1295
|
+
textEditable: boolean;
|
|
1296
|
+
/** Can carry a comment/note + threaded replies (`/Contents` + `/Popup`). */
|
|
1297
|
+
commentable: boolean;
|
|
1298
|
+
/** Has a popup as its primary surface (the comment/Text icon). */
|
|
1299
|
+
hasPopup: boolean;
|
|
1300
|
+
/** Bound to underlying text (markup, caret) — never freely moved/resized. */
|
|
1301
|
+
anchored: boolean;
|
|
1302
|
+
/** Has an interior fill (`/IC`). */
|
|
1303
|
+
hasFill: boolean;
|
|
1304
|
+
/** Has line endings (`/LE` — line, polyline). */
|
|
1305
|
+
hasEndings: boolean;
|
|
1306
|
+
/** Can take a cloudy border effect (`/BE` — shapes). */
|
|
1307
|
+
hasCloudy: boolean;
|
|
1308
|
+
/** The whole body is visible content, so hit-testing grabs anywhere inside
|
|
1309
|
+
* the box (stamp images) — NOT just the stroke/fill like outline shapes. */
|
|
1310
|
+
opaqueBody: boolean;
|
|
1311
|
+
/** The `/F` ReadOnly flag is IGNORED for this kind (ISO 32000: widgets — a
|
|
1312
|
+
* ReadOnly form FIELD must still be movable by a form designer; the
|
|
1313
|
+
* form-filling layer enforces field ReadOnly itself). */
|
|
1314
|
+
ignoresReadOnly: boolean;
|
|
1315
|
+
/** Behaves as if `/F` NoZoom is always set (screen-constant size — the
|
|
1316
|
+
* spec's rule for Text/note icons). See anchor.ts. */
|
|
1317
|
+
noZoom: boolean;
|
|
1318
|
+
/** Behaves as if `/F` NoRotate is always set (screen-upright — the spec's
|
|
1319
|
+
* rule for Text/note icons). See anchor.ts. */
|
|
1320
|
+
noRotate: boolean;
|
|
1321
|
+
}
|
|
1322
|
+
interface AnnotationKind {
|
|
1323
|
+
subtype: Subtype;
|
|
1324
|
+
/** Future: PDF `/IT` intent (free-text vs callout, caret insert vs replace). */
|
|
1325
|
+
intent?: string;
|
|
1326
|
+
variant: Geom['t'];
|
|
1327
|
+
caps: KindCaps;
|
|
1328
|
+
/** The kind's editable properties, in DISPLAY ORDER — the contract a property
|
|
1329
|
+
* sidebar renders from (see {@link PropSpec}). Empty = nothing to edit. */
|
|
1330
|
+
props: PropSpec[];
|
|
1331
|
+
}
|
|
1332
|
+
/** The built-in kinds. Shapes resize, lines/polys vertex-edit, markup is anchored
|
|
1333
|
+
* (selectable + recolor/delete, never move/resize). */
|
|
1334
|
+
declare const KINDS: Record<string, AnnotationKind>;
|
|
1335
|
+
/** The capabilities of a subtype, or the read-only default for unknown kinds. */
|
|
1336
|
+
declare const capsFor: (subtype: string) => KindCaps;
|
|
1337
|
+
/** A kind's editable properties in display order — empty for unknown kinds.
|
|
1338
|
+
* Stable references, so selectors can compare by identity. */
|
|
1339
|
+
declare const propsFor: (subtype: string) => PropSpec[];
|
|
1340
|
+
//#endregion
|
|
1341
|
+
//#region src/props.d.ts
|
|
1342
|
+
/** Base text styling for kinds/tools with no explicit defaults. */
|
|
1343
|
+
declare const initialTextStyle: TextStyle;
|
|
1344
|
+
/** The `Style` slice of a resolved props bag (the create-time projection). */
|
|
1345
|
+
declare const styleFromProps: (p: AnnotationProps) => Style;
|
|
1346
|
+
/** The `TextStyle` slice of a resolved props bag (the create-time projection). */
|
|
1347
|
+
declare const textStyleFromProps: (p: AnnotationProps) => TextStyle;
|
|
1348
|
+
/**
|
|
1349
|
+
* Read one property off an annotation — from wherever it lives — or `undefined`
|
|
1350
|
+
* when the annotation's kind doesn't carry it (a `fontSize` on a square, endings
|
|
1351
|
+
* on a polygon). The read side of `applyProps`.
|
|
1352
|
+
*/
|
|
1353
|
+
declare function readProp<K extends PropKey>(a: Annot, key: K): AnnotationProps[K] | undefined;
|
|
1354
|
+
/**
|
|
1355
|
+
* Apply a property patch to one annotation, honouring its kind's declared keys.
|
|
1356
|
+
* Returns the changed annotation, or `null` when nothing applied (locked /
|
|
1357
|
+
* read-only per its `/F` flags, or no declared key in the patch) — so the
|
|
1358
|
+
* caller emits no spurious engine write. Flags themselves are NOT props: they
|
|
1359
|
+
* write through the `setFlags` message, which is deliberately not gated here
|
|
1360
|
+
* (unlocking must work on a locked annotation).
|
|
1361
|
+
*/
|
|
1362
|
+
declare function applyProps(a: Annot, patch: AnnotationPropsPatch): Annot | null;
|
|
1363
|
+
/**
|
|
1364
|
+
* The ordered property specs EVERY given kind declares — the schema for a mixed
|
|
1365
|
+
* selection, in the FIRST kind's display order. One kind → its own list, verbatim.
|
|
1366
|
+
*/
|
|
1367
|
+
declare function sharedProps(subtypes: readonly string[]): PropSpec[];
|
|
1368
|
+
//#endregion
|
|
1369
|
+
//#region src/geometry.d.ts
|
|
1370
|
+
type RectHandle = 'nw' | 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w';
|
|
1371
|
+
declare const RECT_HANDLES: RectHandle[];
|
|
1372
|
+
/** The resize cursor for a handle on a box tilted by `rot` deg: rotate the
|
|
1373
|
+
* handle's compass direction with the box and pick the nearest 45° sector.
|
|
1374
|
+
* At `rot = 0` this reproduces {@link RECT_CURSOR} exactly. */
|
|
1375
|
+
declare const rotatedHandleCursor: (h: RectHandle, rot: number) => Cursor;
|
|
1376
|
+
declare const rectFromPoints: (a: Vec, b: Vec) => Rect;
|
|
1377
|
+
declare function unionRect(pts: Vec[]): Rect;
|
|
1378
|
+
/** How far (content units) the rotate knob hangs off the top edge of the box. */
|
|
1379
|
+
declare const ROTATE_KNOB_OFFSET = 24;
|
|
1380
|
+
/** Fallback chrome geometry (content units) when the caller supplies none —
|
|
1381
|
+
* the pre-settings behavior, so bare-core callers and tests stay stable. */
|
|
1382
|
+
declare const DEFAULT_CHROME_GEOM: {
|
|
1383
|
+
readonly handleTol: 6;
|
|
1384
|
+
readonly knobTol: 6;
|
|
1385
|
+
readonly knobOffset: 24;
|
|
1386
|
+
};
|
|
1387
|
+
/** Normalize degrees into `[0, 360)`. */
|
|
1388
|
+
declare const normalizeDeg: (d: number) => number;
|
|
1389
|
+
/** A geom's applied rotation (deg), or 0 for the non-rotatable kinds. */
|
|
1390
|
+
declare function geomRotation(g: Geom): number;
|
|
1391
|
+
/**
|
|
1392
|
+
* The rotation PIVOT for a single shape: a box turns about its own `rect`
|
|
1393
|
+
* centre; a vertex shape about the CENTROID of its points (the mean). Rotating a
|
|
1394
|
+
* point set about its centroid leaves the centroid fixed, so the advisory `rot`
|
|
1395
|
+
* stays cleanly additive across gestures and reset is exact.
|
|
1396
|
+
*/
|
|
1397
|
+
declare function centroidOf(g: Geom): Vec;
|
|
1398
|
+
/** Is this kind rotatable (the geometry carries a meaningful `rot`)? */
|
|
1399
|
+
declare function isRotatableGeom(g: Geom): boolean;
|
|
1400
|
+
/**
|
|
1401
|
+
* Rotate a geom by `deltaDeg` (clockwise) about `pivot`.
|
|
1402
|
+
* - BOX (`rect`/plain `text`): orbit the box centre about the pivot (a rigid
|
|
1403
|
+
* translation of the unrotated `rect`) and add the angle to `rot`. When the
|
|
1404
|
+
* pivot IS the box centre this is a pure `rot += delta`.
|
|
1405
|
+
* - VERTEX (`line`/`poly`/`ink`): map every point through the rotation AND bump
|
|
1406
|
+
* the advisory `rot` (the points stay the authoritative visual).
|
|
1407
|
+
* Non-rotatable kinds (caret/quads, callouts) are returned unchanged.
|
|
1408
|
+
*/
|
|
1409
|
+
declare function geomRotateAbout(g: Geom, pivot: Vec, deltaDeg: number): Geom;
|
|
1410
|
+
/** The AABB of `rect` rotated `deg` about its own centre. */
|
|
1411
|
+
declare function rotatedAabb(rect: Rect, deg: number): Rect;
|
|
1412
|
+
/**
|
|
1413
|
+
* The counter-rotation (CW content degrees) that makes an annotation read
|
|
1414
|
+
* upright at `displayRotation`: on screen the two compose to 0.
|
|
1415
|
+
*/
|
|
1416
|
+
declare const uprightRotation: (displayRotation: number) => number;
|
|
1417
|
+
/** `rect` with width↔height swapped about its own centre — the unrotated box
|
|
1418
|
+
* whose quarter-turn AABB is exactly `rect` again. */
|
|
1419
|
+
declare function transposedAboutCenter(rect: Rect): Rect;
|
|
1420
|
+
/**
|
|
1421
|
+
* The unrotated content rect for a DEFAULT-SIZE upright box "placed at" a
|
|
1422
|
+
* point: positioned so that, after the upright counter-rotation, the box shows
|
|
1423
|
+
* `width`×`height` with its TOP-LEFT at `anchor` in the ROTATED view — the
|
|
1424
|
+
* same on-screen feel as the rotation-0 `{ x: anchor.x, y: anchor.y }` box at
|
|
1425
|
+
* every quarter-turn (a click-created free-text box always opens down-right of
|
|
1426
|
+
* the cursor as the author sees it). Derived by placing the box in the display
|
|
1427
|
+
* frame and pulling its AABB back through the quarter-turn; the page dims
|
|
1428
|
+
* cancel, so no page box is needed.
|
|
1429
|
+
*/
|
|
1430
|
+
declare function uprightAnchoredRect(anchor: Vec, width: number, height: number, displayRotation: number): Rect;
|
|
1431
|
+
/**
|
|
1432
|
+
* The LOGICAL (unrotated) content-space box for a stamp placed at `center`,
|
|
1433
|
+
* sized `desired` (points), fit onto the page and clamped fully within it —
|
|
1434
|
+
* the v2 rubber-stamp rule: keep the image's own size unless it would overflow
|
|
1435
|
+
* the page, then scale down (aspect preserved, never up) so it just fits.
|
|
1436
|
+
*
|
|
1437
|
+
* Under an upright quarter-turn the on-page FOOTPRINT is the box transposed
|
|
1438
|
+
* (w↔h), so BOTH the fit and the edge clamp use that footprint — a landscape
|
|
1439
|
+
* stamp uprighted onto a portrait page fits by its rotated silhouette, not its
|
|
1440
|
+
* logical box. The box centre and its footprint AABB centre coincide (rotation
|
|
1441
|
+
* is about the centre), so clamping one clamps the other.
|
|
1442
|
+
*/
|
|
1443
|
+
declare function fitStampBox(center: Vec, desired: Size, page: Size, rotCW: number): Rect;
|
|
1444
|
+
/** Reset a geom to its as-authored orientation (`rot → 0`). Box: drop `rot`.
|
|
1445
|
+
* Vertex: spin the points by `-rot` about their centroid so they return to the
|
|
1446
|
+
* orientation they were drawn at, in place. */
|
|
1447
|
+
declare function geomResetRotation(g: Geom): Geom;
|
|
1448
|
+
/**
|
|
1449
|
+
* The oriented selection box (OBB) of a rotatable geom: four corners (in order
|
|
1450
|
+
* nw, ne, se, sw of the LOCAL box, transformed) + the angle. For a box this is
|
|
1451
|
+
* the `rect` rotated about its centre; for a vertex shape it is reconstructed
|
|
1452
|
+
* from the advisory `rot` — un-rotate the points to recover the as-authored
|
|
1453
|
+
* shape, take that tight local box, then rotate it back — giving the snug tilted
|
|
1454
|
+
* rectangle. Returns null for non-rotatable kinds.
|
|
1455
|
+
*/
|
|
1456
|
+
declare function obbFromGeom(g: Geom, strokeWidth: number, border?: Border): {
|
|
1457
|
+
corners: [Vec, Vec, Vec, Vec];
|
|
1458
|
+
angle: number;
|
|
1459
|
+
} | null;
|
|
1460
|
+
/** The fixed point of a group resize: the OPPOSITE handle's point on the box. */
|
|
1461
|
+
declare function groupResizeAnchor(base: Rect, handle: string): Vec;
|
|
1462
|
+
/**
|
|
1463
|
+
* The live group resize box for a drag. Anisotropic = the plain axis-aligned
|
|
1464
|
+
* `resizeRect`; isotropic = a uniform scale of `base` about the anchor by the
|
|
1465
|
+
* larger of the two drag ratios (so the preview matches the committed scale and
|
|
1466
|
+
* never shears a rotated member).
|
|
1467
|
+
*/
|
|
1468
|
+
declare function groupResizeBox(base: Rect, handle: string, to: Vec, isotropic: boolean): Rect;
|
|
1469
|
+
/** The (sx, sy) factors a `base`→`cur` group resize applied about its anchor. */
|
|
1470
|
+
declare function groupResizeFactors(base: Rect, cur: Rect): {
|
|
1471
|
+
sx: number;
|
|
1472
|
+
sy: number;
|
|
1473
|
+
};
|
|
1474
|
+
/**
|
|
1475
|
+
* Scale a geom about `anchor` by `(sx, sy)`. For a rotated box (iso scale, so
|
|
1476
|
+
* `sx === sy`) the unrotated `rect` is scaled about the anchor and `rot` is
|
|
1477
|
+
* preserved (a uniform scale commutes with rotation). For unrotated members
|
|
1478
|
+
* (the anisotropic case) every point/extent scales directly.
|
|
1479
|
+
*/
|
|
1480
|
+
declare function geomScaleAbout(g: Geom, anchor: Vec, sx: number, sy: number): Geom;
|
|
1481
|
+
/** Where the rotate knob sits, given the OBB corners (nw, ne, se, sw) and the
|
|
1482
|
+
* outward offset (content units). `from` is the top-edge midpoint the connector
|
|
1483
|
+
* stalk anchors to; `at` is the grab dot, offset along the outward normal. */
|
|
1484
|
+
declare function rotateKnob(corners: [Vec, Vec, Vec, Vec], offset: number): {
|
|
1485
|
+
at: Vec;
|
|
1486
|
+
from: Vec;
|
|
1487
|
+
};
|
|
1488
|
+
/**
|
|
1489
|
+
* `rotateKnob` with a TOTAL page-bound placement policy: annotations, gestures
|
|
1490
|
+
* and chrome are all page-bound, so the grab dot must land inside `pageBox` —
|
|
1491
|
+
* off-page it is unreachable (the pointer dispatch resolves pages by
|
|
1492
|
+
* containment). Policy: top edge (the default) → FLIP to the bottom edge when
|
|
1493
|
+
* the stalk exits the page → CLAMP the top candidate inside (degenerate: the
|
|
1494
|
+
* selection spans ~the whole page; the knob may then overlap the shape, and
|
|
1495
|
+
* hit-testing checks it first so it stays grabbable). Both render (`chrome`)
|
|
1496
|
+
* and hit-test (`hitTest`) place the knob through THIS function, so what you
|
|
1497
|
+
* see is what you can grab — by construction. No `pageBox` → the raw knob.
|
|
1498
|
+
*/
|
|
1499
|
+
declare function placeRotateKnob(corners: [Vec, Vec, Vec, Vec], offset: number, pageBox?: Rect): {
|
|
1500
|
+
at: Vec;
|
|
1501
|
+
from: Vec;
|
|
1502
|
+
};
|
|
1503
|
+
/** The box-edge midpoint the leader connects to: the side `ref` (the knee, else
|
|
1504
|
+
* the tip) points toward, by horizontal/vertical dominance vs the box centre.
|
|
1505
|
+
* With `rot` (a callout box tilted by the upright policy) the decision runs in
|
|
1506
|
+
* the box's LOCAL frame and the midpoint lands back on the ROTATED edge — the
|
|
1507
|
+
* edge the user actually sees. Mirror of v2's `computeCalloutConnectionPoint`. */
|
|
1508
|
+
declare function calloutConnection(box: Rect, ref: Vec, rot?: number): Vec;
|
|
1509
|
+
/** The leader polyline `[tip, knee?, conn]`, with `conn` derived from the box
|
|
1510
|
+
* (its ROTATED footprint when the box carries `rot`). */
|
|
1511
|
+
declare function calloutLinePoints(g: Extract<Geom, {
|
|
1512
|
+
t: 'text';
|
|
1513
|
+
}>): Vec[];
|
|
1514
|
+
/**
|
|
1515
|
+
* The stored `rect` for a freshly-drawn shape, given the box the user dragged. A
|
|
1516
|
+
* cloudy border stores the OUTER box (dragged + cloud extent), so the dragged box
|
|
1517
|
+
* is its inner edge and the scallops bulge out to the stored box — just like a
|
|
1518
|
+
* solid shape, whose /Rect is the dragged box. So `g.rect` is ALWAYS the outer box;
|
|
1519
|
+
* the cloud-vs-solid difference lives only here, at creation.
|
|
1520
|
+
*/
|
|
1521
|
+
declare function shapeRectFor(dragged: Rect, ellipse: boolean, style: Style): Rect;
|
|
1522
|
+
declare function caretRectFromTextEnd(lineRect: Rect): Rect;
|
|
1523
|
+
/** Miter limit shared by the bounds math AND the SVG renderer (`stroke-miterlimit`),
|
|
1524
|
+
* so the computed box and the drawn stroke always agree on where a sharp join
|
|
1525
|
+
* bevels instead of spiking. 10 = the PDF default (also what the baked /AP uses). */
|
|
1526
|
+
declare const MITER_LIMIT = 10;
|
|
1527
|
+
/**
|
|
1528
|
+
* A geom's VISUAL bounds: the rect that encloses the drawn appearance, so the
|
|
1529
|
+
* baked /AP is never clipped and the selection outline wraps exactly what's drawn.
|
|
1530
|
+
*
|
|
1531
|
+
* A shape's `rect` IS its visual box (the PDF /Rect): solid/dashed strokes draw
|
|
1532
|
+
* inside it, and a cloudy border's scallops also inset back into it (the dragged
|
|
1533
|
+
* inner edge sits `/RD` in from it — see `shapeRectFor`). So growing the stroke or
|
|
1534
|
+
* the cloud thickens inward, never spilling past the handles. Lines / polylines /
|
|
1535
|
+
* ink have no box, so they expand by the stroke (+ endings) — the SAME math feeds
|
|
1536
|
+
* `geomScene`, so the visual box and what's drawn always agree.
|
|
1537
|
+
*
|
|
1538
|
+
* `border` matters for ONE case: a CLOSED poly with a cloudy border, whose curls
|
|
1539
|
+
* are centred on the vertex path and reach OUTWARD (there is no outer box to
|
|
1540
|
+
* inset into) — the bounds grow by the cloud extent, and the engine `/Rect`
|
|
1541
|
+
* (via `geomPdfBounds`) grows with them so the baked scallops are never clipped.
|
|
1542
|
+
*/
|
|
1543
|
+
declare function geomVisualBounds(g: Geom, strokeWidth: number, border?: Border): Rect;
|
|
1544
|
+
/**
|
|
1545
|
+
* The rect the SELECTION wraps — and the region a SELECTED annotation can be grabbed
|
|
1546
|
+
* from. Centre-line geometries (line / polyline / polygon / ink) straddle their path,
|
|
1547
|
+
* so this is their VISUAL bounds (the join-aware stroke outline + endings) — a
|
|
1548
|
+
* polygon's outline wraps its stroke exactly like a polyline. Box kinds (square /
|
|
1549
|
+
* circle / free-text) sit tight on their `rect` (their stroke draws INSIDE the box,
|
|
1550
|
+
* so the 8 resize handles land on the corners). The chrome outline AND the selected
|
|
1551
|
+
* hit-test both call this, so what you see highlighted is exactly what you can grab —
|
|
1552
|
+
* they can never drift.
|
|
1553
|
+
*/
|
|
1554
|
+
declare function selectionBounds(g: Geom, strokeWidth: number, border?: Border): Rect;
|
|
1555
|
+
/**
|
|
1556
|
+
* The four corners of the ORIENTED selection box — the SAME quad `chrome` draws.
|
|
1557
|
+
* For a rotatable kind this is the OBB (the tilted box, from `obbFromGeom`); at
|
|
1558
|
+
* `rot == 0` those are just the axis-aligned corners of `selectionBounds`, and
|
|
1559
|
+
* for non-rotatable kinds (markup quads, callouts) it falls back to the rect
|
|
1560
|
+
* corners. The grab region, the floating-menu anchor and the multi/group union
|
|
1561
|
+
* all consume this, so what you can grab / where the menu sits never drifts from
|
|
1562
|
+
* the outline you see.
|
|
1563
|
+
*/
|
|
1564
|
+
declare function selectionQuad(g: Geom, strokeWidth: number, border?: Border): [Vec, Vec, Vec, Vec];
|
|
1565
|
+
/** Is the point inside the (convex) selection quad? Even-odd ring test. */
|
|
1566
|
+
declare const pointInQuad: (p: Vec, quad: [Vec, Vec, Vec, Vec]) => boolean;
|
|
1567
|
+
/**
|
|
1568
|
+
* Does the (convex) selection quad intersect an axis-aligned rect? Separating
|
|
1569
|
+
* axis test on the only four candidate axes — the rect's x/y plus the quad's
|
|
1570
|
+
* two edge normals; a gap on any axis proves disjoint, otherwise they overlap
|
|
1571
|
+
* (touching counts). This is the marquee's predicate: it must catch a tilted
|
|
1572
|
+
* shape by the ORIENTED box that is actually drawn — the AABB of that quad has
|
|
1573
|
+
* empty corners covering most of the unrotated footprint, so testing it selects
|
|
1574
|
+
* shapes the marquee never touched. At `rot 0` the quad is axis-aligned and
|
|
1575
|
+
* this degenerates to exactly `rectsIntersect`.
|
|
1576
|
+
*/
|
|
1577
|
+
declare function quadIntersectsRect(quad: [Vec, Vec, Vec, Vec], r: Rect): boolean;
|
|
1578
|
+
/**
|
|
1579
|
+
* The centre of the ORIENTED selection box — the middle of the rect you see.
|
|
1580
|
+
* For box kinds this is the rect centre (so squares/circles are unchanged); for
|
|
1581
|
+
* vertex kinds it is the OBB centre, so rotation spins the shape in place rather
|
|
1582
|
+
* than swinging it about the off-centre vertex mean (`centroidOf`).
|
|
1583
|
+
*/
|
|
1584
|
+
declare function selectionCenter(g: Geom, strokeWidth: number): Vec;
|
|
1585
|
+
declare function geomBounds(g: Geom): Rect;
|
|
1586
|
+
/**
|
|
1587
|
+
* Is the content point ON the annotation: within `margin` of the stroke, or
|
|
1588
|
+
* inside the fill (when `filled`). The stroke band widens with the stroke width.
|
|
1589
|
+
*
|
|
1590
|
+
* A shape's stroke is drawn INSIDE its box, centred on `insetRect(rect, sw/2)`
|
|
1591
|
+
* (see `geomScene`), so the clickable band follows that inset centre-line — not
|
|
1592
|
+
* the box edge. The fill still reaches the box. (Cloudy scallops aren't modelled
|
|
1593
|
+
* here; with their typically thin stroke the inset is sub-pixel, so this matches.)
|
|
1594
|
+
*/
|
|
1595
|
+
declare function geomHit(g: Geom, p: Vec, margin: number, filled: boolean, strokeWidth: number): boolean;
|
|
1596
|
+
declare function geomHandles(g: Geom): Handle[];
|
|
1597
|
+
declare function geomTranslate(g: Geom, d: Vec): Geom;
|
|
1598
|
+
declare function geomDragHandle(g: Geom, handle: string, to: Vec): Geom;
|
|
1599
|
+
declare function geomScene(g: Geom, strokeWidth?: number, border?: Border): RenderNode[];
|
|
1600
|
+
declare const pdfToContentPoint: (p: PdfPoint, crop: PdfRect) => Vec;
|
|
1601
|
+
declare const contentToPdfPoint: (p: Vec, crop: PdfRect) => PdfPoint;
|
|
1602
|
+
declare const pdfToContentRect: (pdf: PdfRect, crop: PdfRect) => Rect;
|
|
1603
|
+
declare const contentToPdfRect: (r: Rect, crop: PdfRect) => PdfRect;
|
|
1604
|
+
/** A geom's VISUAL bounding box (geometry + stroke + line endings) as a PdfRect —
|
|
1605
|
+
* the engine requires an explicit `rect` that encloses the baked /AP. */
|
|
1606
|
+
declare const geomPdfBounds: (g: Geom, strokeWidth: number, crop: PdfRect, border?: Border) => PdfRect;
|
|
1607
|
+
//#endregion
|
|
1608
|
+
//#region src/cloudy.d.ts
|
|
1609
|
+
/**
|
|
1610
|
+
* The per-side inset (content units) from a shape's outer box to the cloud's
|
|
1611
|
+
* inner boundary — the scallop radius plus half the stroke. This IS the `/RD`
|
|
1612
|
+
* the engine stores, and it's sized so the scallop peaks reach back out to the
|
|
1613
|
+
* box edge: the box remains the annotation's outer boundary.
|
|
1614
|
+
*/
|
|
1615
|
+
declare function cloudyBorderExtent(intensity: number, strokeWidth: number, ellipse: boolean): number;
|
|
1616
|
+
/**
|
|
1617
|
+
* SVG path data for a cloudy square (rect) or circle (ellipse), in ABSOLUTE
|
|
1618
|
+
* content coordinates. The scallops are generated on the box inset by
|
|
1619
|
+
* `cloudyBorderExtent` and bulge back out to `box`'s edge.
|
|
1620
|
+
*/
|
|
1621
|
+
declare function cloudyPath(box: Rect, ellipse: boolean, intensity: number, strokeWidth: number): string;
|
|
1622
|
+
//#endregion
|
|
1623
|
+
//#region src/scene.d.ts
|
|
1624
|
+
/** The full painted scene for one annotation. */
|
|
1625
|
+
declare function scene(item: RenderItem): SceneNode[];
|
|
1626
|
+
//#endregion
|
|
1627
|
+
//#region src/ink.d.ts
|
|
1628
|
+
/**
|
|
1629
|
+
* Recognise a sufficiently straight freehand stroke and axis-snap it when it is
|
|
1630
|
+
* close to horizontal or vertical. Diagonal strokes remain untouched: this is
|
|
1631
|
+
* a highlighter aid, not a general line simplifier.
|
|
1632
|
+
*/
|
|
1633
|
+
declare function straightenInkStroke(points: readonly Vec[], options: InkStraightenOptions): Vec[];
|
|
1634
|
+
//#endregion
|
|
1635
|
+
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 SceneNode, type SnapResult, type SnapSettings, type Style, type Subtype, type Target, type TextAlign, type TextBox, type TextStyle, type Vec, type ViewEnv, anchorModeOf, anchorOf, anchoredBox, anchoredGeom, anchoredStrokeWidth, annotContentsEditable, annotInteractive, annotTransformable, applyProps, calloutConnection, calloutLinePoints, canMove, capsFor, 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, isRotatableGeom, isSelectable, mergeFlags, normalizeDeg, obbFromGeom, pageItems, pdfToContentPoint, pdfToContentRect, placeRotateKnob, pointInQuad, propsFor, quadIntersectsRect, readProp, rectFromPoints, resolveClickPlacement, rotateDraftDelta, rotateKnob, rotatedAabb, rotatedHandleCursor, scene, selectedItems, selectionAnchor, selectionBounds, selectionBoundsOnPage, selectionCenter, selectionKnob, selectionQuad, shapeRectFor, sharedProps, straightenInkStroke, styleFromProps, textBoxes, textStyleFromProps, transposedAboutCenter, unanchoredGeom, unionRect, update, uprightAnchoredRect, uprightRotation, viewable };
|
|
1636
|
+
//# sourceMappingURL=index.d.cts.map
|