@ingcreators/annot-core 0.1.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +201 -0
  3. package/README.md +56 -0
  4. package/dist/auto-capture-options.d.ts +78 -0
  5. package/dist/editor/arrow-markers.d.ts +142 -0
  6. package/dist/editor/bake-translate.d.ts +192 -0
  7. package/dist/editor/font-registry.d.ts +58 -0
  8. package/dist/editor/gradient-utils.d.ts +23 -0
  9. package/dist/editor/history-core.d.ts +48 -0
  10. package/dist/editor/icons/brand-icons.d.ts +53 -0
  11. package/dist/editor/icons/material-symbols.d.ts +105 -0
  12. package/dist/editor/icons/registry.d.ts +179 -0
  13. package/dist/editor/icons/render.d.ts +22 -0
  14. package/dist/editor/icons/sanitize.d.ts +60 -0
  15. package/dist/editor/index.d.ts +13 -0
  16. package/dist/editor/path-utils.d.ts +32 -0
  17. package/dist/editor/property-schema.d.ts +263 -0
  18. package/dist/editor/rich-text-mapper.d.ts +8 -0
  19. package/dist/editor/selection-geometry.d.ts +66 -0
  20. package/dist/editor/shape-utils.d.ts +27 -0
  21. package/dist/editor/svg-format.d.ts +68 -0
  22. package/dist/editor/svg-id-utils.d.ts +37 -0
  23. package/dist/editor/svg-to-annotation-shapes.d.ts +34 -0
  24. package/dist/editor/text-utils.d.ts +212 -0
  25. package/dist/editor/tool-lifecycle.d.ts +56 -0
  26. package/dist/editor/tool-options.d.ts +126 -0
  27. package/dist/editor/tool-panel-adapter.d.ts +105 -0
  28. package/dist/editor/tool-preset-serde.d.ts +43 -0
  29. package/dist/editor/tool-registry.d.ts +320 -0
  30. package/dist/editor/tool-style-reader.d.ts +36 -0
  31. package/dist/editor/tool-style-writer.d.ts +33 -0
  32. package/dist/editor/toolbar-icons.d.ts +84 -0
  33. package/dist/editor/transform-utils.d.ts +127 -0
  34. package/dist/editor/viewport-math.d.ts +69 -0
  35. package/dist/encode/index.d.ts +4 -0
  36. package/dist/encode/options.d.ts +79 -0
  37. package/dist/encode/png8.d.ts +10 -0
  38. package/dist/headless.d.ts +29 -0
  39. package/dist/icons/index.d.ts +15 -0
  40. package/dist/icons/types.d.ts +108 -0
  41. package/dist/index.d.ts +1 -0
  42. package/dist/index.js +3164 -0
  43. package/dist/storage/errors.d.ts +59 -0
  44. package/dist/storage/index.d.ts +6 -0
  45. package/dist/storage/metadata-cache.d.ts +231 -0
  46. package/dist/storage/path.d.ts +49 -0
  47. package/dist/storage/thumbnail-cache.d.ts +110 -0
  48. package/dist/storage/thumbnail.d.ts +31 -0
  49. package/dist/storage/types.d.ts +677 -0
  50. package/dist/utils/assert.d.ts +31 -0
  51. package/dist/utils/constants.d.ts +10 -0
  52. package/dist/utils/dash-utils.d.ts +6 -0
  53. package/dist/utils/desktop-bridge.d.ts +349 -0
  54. package/dist/utils/filename.d.ts +48 -0
  55. package/dist/utils/id.d.ts +21 -0
  56. package/dist/utils/index.d.ts +5 -0
  57. package/dist/utils/types.d.ts +20 -0
  58. package/dist/xmp/xmp-browser.d.ts +39 -0
  59. package/dist/zip/zip-builder.d.ts +8 -0
  60. package/dist/zip/zip-bytes.d.ts +22 -0
  61. package/package.json +58 -0
  62. package/styles/editor.css +1912 -0
  63. package/styles/fonts.css +46 -0
  64. package/styles/property-panel.css +779 -0
  65. package/styles/toolbar.css +673 -0
@@ -0,0 +1,263 @@
1
+ /**
2
+ * Coarse property-panel category an SVG element falls into. The
3
+ * editor's PropertyPanel uses the category to pick which control
4
+ * set to render; the values intentionally mirror the historical
5
+ * `if (isTextbox) ... else if (isMarker) ... else ...` chain.
6
+ */
7
+ export type PropertyCategory = "textbox" | "marker" | "redact-mosaic" | "redact-solid" | "redact-blur" | "highlight" | "group" | "shape";
8
+ /** Stable IDs used both as map keys in the panel and as test
9
+ * fixtures so a follow-up "schema-driven render" can swap the
10
+ * imperative `#renderXxxControls` chain for a declarative table. */
11
+ export declare const PROPERTY_CONTROL_IDS: {
12
+ readonly shapeTypePicker: "shapeTypePicker";
13
+ readonly arrowVariantPicker: "arrowVariantPicker";
14
+ readonly drawStylePicker: "drawStylePicker";
15
+ readonly fillColor: "fillColor";
16
+ readonly strokeColor: "strokeColor";
17
+ readonly strokeWidth: "strokeWidth";
18
+ readonly strokeStyle: "strokeStyle";
19
+ readonly textVariantPicker: "textVariantPicker";
20
+ readonly textColor: "textColor";
21
+ readonly fontFamily: "fontFamily";
22
+ readonly fontSize: "fontSize";
23
+ /** Per-character bold toggle for the selected textbox. Phase 4
24
+ * of `docs/plans/rich-text-and-shape-text.md`. Modelled as a
25
+ * two-option `select` ("Off" / "On") so the existing
26
+ * `select`-type renderer is reused; future Phase-4-prime work
27
+ * can introduce a dedicated `toggle` control type if visual
28
+ * polish demands a different affordance. */
29
+ readonly textBold: "textBold";
30
+ readonly textItalic: "textItalic";
31
+ readonly textUnderline: "textUnderline";
32
+ /** Horizontal text alignment inside the shape — start / middle /
33
+ * end. Writes `data-text-anchor` on the wrapper `<g>` and
34
+ * re-runs the layout pass. */
35
+ readonly textAnchor: "textAnchor";
36
+ /** Vertical text alignment inside the shape — top / middle /
37
+ * bottom. Writes `data-text-vanchor` on the wrapper `<g>` and
38
+ * re-runs the layout pass. */
39
+ readonly textVerticalAnchor: "textVerticalAnchor";
40
+ /** PowerPoint-style autofit policy — none / shrink / resize.
41
+ * `resize` extends the wrapper geometry's height so the
42
+ * rendered run block fits without clipping; `shrink` records
43
+ * the intent (the layout side that scales the font-size is a
44
+ * follow-up); `none` lets the box clip to its current
45
+ * dimensions. Stored as `data-text-autofit` on the wrapper. */
46
+ readonly textAutofit: "textAutofit";
47
+ /** Per-side text-box margin in user-space units (mirrors
48
+ * PowerPoint's left / right / top / bottom margins). Stored
49
+ * as `data-text-margin-{l,r,t,b}` on the wrapper; the layout
50
+ * pass reserves the matching pixel inset around the run
51
+ * block. */
52
+ readonly textMarginLeft: "textMarginLeft";
53
+ readonly textMarginRight: "textMarginRight";
54
+ readonly textMarginTop: "textMarginTop";
55
+ readonly textMarginBottom: "textMarginBottom";
56
+ readonly redactStylePicker: "redactStylePicker";
57
+ readonly redactSolidColor: "redactSolidColor";
58
+ readonly highlightColorPicker: "highlightColorPicker";
59
+ readonly highlightTransparency: "highlightTransparency";
60
+ readonly markerShapePicker: "markerShapePicker";
61
+ readonly markerSize: "markerSize";
62
+ readonly markerBgFillColor: "markerBgFillColor";
63
+ readonly markerBgStrokeColor: "markerBgStrokeColor";
64
+ readonly markerBgStrokeWidth: "markerBgStrokeWidth";
65
+ readonly markerBgStrokeStyle: "markerBgStrokeStyle";
66
+ readonly markerLabelValue: "markerLabelValue";
67
+ readonly fillOpacity: "fillOpacity";
68
+ readonly strokeOpacity: "strokeOpacity";
69
+ readonly strokeLinecap: "strokeLinecap";
70
+ readonly arrowStartShape: "arrowStartShape";
71
+ readonly arrowStartSize: "arrowStartSize";
72
+ readonly arrowEndShape: "arrowEndShape";
73
+ readonly arrowEndSize: "arrowEndSize";
74
+ };
75
+ export type PropertyControlId = (typeof PROPERTY_CONTROL_IDS)[keyof typeof PROPERTY_CONTROL_IDS];
76
+ /**
77
+ * The (potentially-applicable) controls for each category. Whether
78
+ * a given control actually shows up still depends on per-element
79
+ * fine-grained checks (e.g. "Fill is hidden for stroke-only shapes
80
+ * like <line>"). The shape registry is the upper bound that drives
81
+ * eventual schema-driven rendering; the panel is free to filter
82
+ * further at render time.
83
+ */
84
+ export declare const CATEGORY_CONTROL_SHAPE: Readonly<Record<PropertyCategory, readonly PropertyControlId[]>>;
85
+ /** Discriminator for how the renderer should display a control. The
86
+ * values map onto leaf widget families that already exist in
87
+ * `@ingcreators/annot-editor` (color pull-buttons, pp-number inputs,
88
+ * custom-select dropdowns, prop-choice-chip rows). Sections are
89
+ * layout-only and don't appear here — they're a renderer concern,
90
+ * not a control. */
91
+ export type PropertyControlType = "color" | "number" | "select" | "variantPicker";
92
+ /**
93
+ * Identifier for a Tier C-only side-effect a control's mutation requires.
94
+ *
95
+ * Several setters (arrow head regeneration, freehand pen↔highlighter
96
+ * conversion, marker geometry rescale, redact mosaic/blur baking) live
97
+ * in `@ingcreators/annot-editor` because they either need the live
98
+ * `<canvas>` for pixel sampling or wrap helpers that grew up co-located
99
+ * with the tool classes. Tier B can't import those without creating a
100
+ * reverse package dependency, so the registry references them by
101
+ * stable id and Phase 2's renderer (in Tier C) supplies the matching
102
+ * implementations.
103
+ */
104
+ export declare const PROPERTY_EFFECT_IDS: {
105
+ /** Switch an arrow's per-end variant (none / end / both) — calls
106
+ * `applyArrowHead` from `tools/arrow-tool` which regenerates the
107
+ * composed `<path>`'s `d` attribute. */
108
+ readonly applyArrowVariant: "applyArrowVariant";
109
+ /** Toggle a freehand element between pen and highlighter — calls
110
+ * `applyDrawStyle` from `tools/freehand-tool` which sets stroke
111
+ * attrs across the wrapper group's `<path>` children. */
112
+ readonly applyDrawStyle: "applyDrawStyle";
113
+ /** Swap a marker's bg primitive (circle ↔ rect ↔ rounded) — calls
114
+ * `convertMarkerShape` from `tools/marker-tool` which mutates the
115
+ * outer `<g>` in place. */
116
+ readonly applyMarkerShape: "applyMarkerShape";
117
+ /** Resize a marker proportionally — calls `resizeMarker` from
118
+ * `tools/marker-tool` which scales bg + text together. */
119
+ readonly resizeMarker: "resizeMarker";
120
+ /** Convert a redact element between mosaic / solid / blur — calls
121
+ * `convertRedactStyle` from `redact-utils`, which is **async** and
122
+ * needs `CanvasManager` access to sample the underlying base image
123
+ * for mosaic / blur baking. */
124
+ readonly applyRedactStyle: "applyRedactStyle";
125
+ /** Set a textbox's text color and (for sticky / callout variants
126
+ * whose bg is derived from the text color) regenerate the bg via
127
+ * `convertTextVariant`. Plain textboxes get the simple attribute
128
+ * write; sticky / callout get a bg-recreation pass that produces
129
+ * a fresh element so the per-target `oldEl !== newEl` swap is
130
+ * picked up by `onTargetReplaced`. */
131
+ readonly applyTextColor: "applyTextColor";
132
+ /** Per-end arrow effect handlers (Phase C). Each updates a single
133
+ * field of `detectArrowEnds(el)` and re-applies the spec via
134
+ * `applyArrowHead`. Split into 4 ids (rather than one shared
135
+ * handler with a diff-object value) so each registry def's
136
+ * `effect` field still encodes "which end + which field" without
137
+ * the renderer needing to thread extra context to the handler. */
138
+ readonly applyArrowStartShape: "applyArrowStartShape";
139
+ readonly applyArrowStartSize: "applyArrowStartSize";
140
+ readonly applyArrowEndShape: "applyArrowEndShape";
141
+ readonly applyArrowEndSize: "applyArrowEndSize";
142
+ };
143
+ export type PropertyEffectId = (typeof PROPERTY_EFFECT_IDS)[keyof typeof PROPERTY_EFFECT_IDS];
144
+ /** Display affordance for a single option in a `variantPicker` /
145
+ * `select` control. Exactly one of `materialIcon` / `iconSvg` /
146
+ * `swatchColor` should be set; the renderer dispatches on whichever
147
+ * is present. */
148
+ export interface PropertyControlOption<T = unknown> {
149
+ value: T;
150
+ label: string;
151
+ /** Builtin icon id (e.g. "text_fields"). Rendered via
152
+ * `renderIconHtml(builtinIcon(materialIcon))` from the
153
+ * `@ingcreators/annot-core/editor/icons` registry. The legacy
154
+ * `materialIcon` field name is preserved so `PROPERTY_CONTROLS`
155
+ * data tables don't have to be rewritten. */
156
+ materialIcon?: string;
157
+ /** Inline SVG markup (already-serialized string). Used when the
158
+ * glyph isn't available as a Material Symbol or where the icon
159
+ * family needs hand-rolled geometry for clarity (shape / arrow /
160
+ * counter pickers). */
161
+ iconSvg?: string;
162
+ /** Color swatch — paints `--swatch-color` on the chip's swatch
163
+ * element. Used by the highlight color picker, where the variant
164
+ * IS the color. */
165
+ swatchColor?: string;
166
+ }
167
+ /**
168
+ * Declarative descriptor for a single PropertyPanel control. The
169
+ * registry below maps every `PropertyControlId` to one of these.
170
+ *
171
+ * **Mutation contract:** each def supplies exactly ONE of:
172
+ * - `setValue(el, value)` — in-place attribute mutation, Tier B-only
173
+ * logic. The renderer calls this directly.
174
+ * - `replace(el, value)` — element-replacement (e.g. converting a
175
+ * rectangle to an ellipse). Returns the new element, which the
176
+ * renderer hands back to PropertyPanel via `onTargetReplaced`.
177
+ * - `effect: PropertyEffectId` — Tier C-only operation. The renderer
178
+ * looks the id up in its effect-handler table and dispatches.
179
+ * Used for arrow / draw-style / marker / redact converters that
180
+ * either need `CanvasManager` (redact) or wrap helpers that live
181
+ * in `@ingcreators/annot-editor` (the rest).
182
+ */
183
+ export interface PropertyControlDef<T = unknown> {
184
+ id: PropertyControlId;
185
+ type: PropertyControlType;
186
+ /** Human-readable label shown next to the control (e.g. "Color",
187
+ * "Width"). Variant pickers omit a separate label — their chip
188
+ * row IS the label, anchored under the section header. */
189
+ label: string;
190
+ /** Pure read of the current value off an SVG element. Always
191
+ * implemented in Tier B. */
192
+ getValue: (el: SVGElement) => T;
193
+ /** In-place mutation. Tier B-only — no `CanvasManager`, no host
194
+ * callbacks. Mutually exclusive with `replace` and `effect`. */
195
+ setValue?: (el: SVGElement, value: T) => void;
196
+ /** Element replacement. Returns the new element to swap into the
197
+ * DOM. Tier B-only. Mutually exclusive with `setValue` and `effect`. */
198
+ replace?: (el: SVGElement, value: T) => SVGElement;
199
+ /** Tier C-only side effect; the renderer must supply an
200
+ * implementation bound to this id. Mutually exclusive with
201
+ * `setValue` and `replace`. */
202
+ effect?: PropertyEffectId;
203
+ /** Optional gating predicate. Returning `false` tells the renderer
204
+ * to skip this control for the current selection's first element
205
+ * (e.g. "Fill is hidden when the element is line-like"). */
206
+ visibleWhen?: (el: SVGElement) => boolean;
207
+ /** Optional metadata for variant pickers / select dropdowns. */
208
+ options?: ReadonlyArray<PropertyControlOption<T>>;
209
+ /** Dynamically-computed options. When present AND the def's
210
+ * `type === "select"` (or future "variantPicker"), the renderer
211
+ * calls this with the current sample target instead of reading
212
+ * the static `options` field. Lets per-end arrow shape pickers
213
+ * filter by the current variant without forcing the registry
214
+ * to know about cross-control state. Functions receive the same
215
+ * `Element` instance the renderer passes to `getValue` /
216
+ * `visibleWhen`. */
217
+ getOptions?: (el: SVGElement) => ReadonlyArray<PropertyControlOption<T>>;
218
+ /** Number of columns for the select popup grid. Defaults to 1
219
+ * (vertical list). Used by per-end arrow shape (3 cols for the
220
+ * 6-shape OOXML grid) and size (3 cols for the 3×3 width × length
221
+ * grid) selects to match PowerPoint's layout exactly. */
222
+ selectColumns?: number;
223
+ /** Width hint (px) for the select popup. Used alongside
224
+ * `selectColumns` to size the per-end arrow grids. */
225
+ selectPopupWidth?: number;
226
+ /** Inclusive lower bound for a number input. */
227
+ min?: number;
228
+ /** Inclusive upper bound for a number input. */
229
+ max?: number;
230
+ /** Spinner / arrow-key step granularity for a number input. */
231
+ step?: number;
232
+ /** Trailing unit label for a number input (e.g. "pt", "%"). */
233
+ unit?: string;
234
+ /** Whether a "No fill" sentinel is offered alongside the palette
235
+ * (only applies to `type === "color"` controls — fill paints can
236
+ * be unset, strokes generally cannot). */
237
+ allowNone?: boolean;
238
+ }
239
+ /**
240
+ * The complete set of declarative control definitions PropertyPanel
241
+ * renders. Phase 1 of `docs/plans/property-panel-schema.md`: the
242
+ * data structure, with no consumer yet — the panel still uses the
243
+ * imperative `#renderXxxControls` chain. Phase 2 builds the renderer;
244
+ * Phase 3 migrates the panel to use it.
245
+ */
246
+ export declare const PROPERTY_CONTROLS: Readonly<{
247
+ [K in PropertyControlId]: PropertyControlDef;
248
+ }>;
249
+ /** Returns the categorical bucket for a single SVG element. */
250
+ export declare function classifyPropertyElement(el: Element): PropertyCategory;
251
+ /**
252
+ * Coerce a heterogeneous selection to a single category. Returns
253
+ * `null` when the selection mixes categories — at which point the
254
+ * panel typically falls back to either an empty render or the
255
+ * category of the FIRST element. The legacy `show()` always used
256
+ * the first element; this helper preserves that behaviour while
257
+ * exposing the "are they all the same?" check for any future
258
+ * "edit shared properties only" mode.
259
+ */
260
+ export declare function classifyPropertySelection(elements: readonly Element[]): {
261
+ category: PropertyCategory | null;
262
+ uniform: boolean;
263
+ };
@@ -0,0 +1,8 @@
1
+ import { TextRun } from '../utils/desktop-bridge.js';
2
+ /** Convert a contentEditable subtree into the canonical
3
+ * `TextRun[]` representation. Empty input → empty array. */
4
+ export declare function htmlToRuns(root: HTMLElement | DocumentFragment): TextRun[];
5
+ /** Inverse of `htmlToRuns`. Emits an HTML string the
6
+ * contentEditable can ingest as `innerHTML` or compose into a
7
+ * document fragment for `appendChild`. */
8
+ export declare function runsToHtml(runs: readonly TextRun[]): string;
@@ -0,0 +1,66 @@
1
+ /** Plain rectangle shape. Structurally compatible with `DOMRect`, so
2
+ * existing callers passing `DOMRect[]` continue to typecheck. */
3
+ export interface Rect {
4
+ x: number;
5
+ y: number;
6
+ width: number;
7
+ height: number;
8
+ }
9
+ /**
10
+ * Rotate the point `(px, py)` around the pivot `(cx, cy)` by `rad`
11
+ * radians (positive = counter-clockwise in screen-space terms,
12
+ * which matches Math.cos / Math.sin convention).
13
+ */
14
+ export declare function rotateAround(px: number, py: number, cx: number, cy: number, rad: number): {
15
+ x: number;
16
+ y: number;
17
+ };
18
+ /** Pick the appropriate axis-aligned cursor for the angle at which a
19
+ * resize handle sits relative to its element's centre.
20
+ *
21
+ * The angle is normalised into `[0, 2π)`, offset by π/8 so cursor
22
+ * "centres" align with E/SE/S/SW/W/NW/N/NE, then bucketed into 8
23
+ * equal 45° sectors. */
24
+ export declare function cursorForAngle(rad: number): string;
25
+ export interface SnapGuide {
26
+ x1: number;
27
+ y1: number;
28
+ x2: number;
29
+ y2: number;
30
+ }
31
+ export interface SnapResult {
32
+ /** Adjusted drag delta in world units — what the caller should
33
+ * apply instead of the raw pointer delta. */
34
+ dx: number;
35
+ dy: number;
36
+ /** World-space line segments to render as dashed guides. Each
37
+ * guide is an axis-aligned line through the snap point. */
38
+ guides: SnapGuide[];
39
+ }
40
+ export interface SnapInput {
41
+ /** Bounding boxes of the dragged (selected) elements in world
42
+ * space, computed BEFORE applying the current delta. */
43
+ draggedBoxes: readonly Rect[];
44
+ /** Raw pointer delta the caller wants to apply. */
45
+ dx: number;
46
+ dy: number;
47
+ /** Bounding boxes of NON-dragged elements to snap against. */
48
+ otherBoxes: readonly Rect[];
49
+ /** Snap activation radius in world units. Typically 4–6 px. */
50
+ threshold: number;
51
+ }
52
+ /**
53
+ * Compute a snapped drag delta and the guide lines to render.
54
+ *
55
+ * Snap candidates per axis (for each dragged-bbox edge):
56
+ * left ↔ other's left, center-x, right
57
+ * center ↔ same three
58
+ * right ↔ same three
59
+ * Same six comparisons on Y. That's 18 candidate offsets per
60
+ * (dragged-union, other) pair; the smallest absolute delta per axis
61
+ * wins, breaking ties by edge-similarity.
62
+ *
63
+ * Returns the raw `dx` / `dy` unmodified when no snap candidate is
64
+ * within `threshold`.
65
+ */
66
+ export declare function computeSnap(input: SnapInput): SnapResult;
@@ -0,0 +1,27 @@
1
+ import { ShapeType } from './tool-options.js';
2
+ /**
3
+ * Classify an existing SVG element as one of the three Shape variants,
4
+ * or null if it isn't a shape.
5
+ */
6
+ export declare function detectShapeType(el: SVGElement): ShapeType | null;
7
+ /**
8
+ * Return the element's canvas-space bounding box as (x, y, w, h).
9
+ * Works for both <rect> (x/y/w/h attrs) and <ellipse> (cx/cy/rx/ry).
10
+ */
11
+ export declare function shapeBBox(el: SVGElement): {
12
+ x: number;
13
+ y: number;
14
+ w: number;
15
+ h: number;
16
+ };
17
+ /**
18
+ * Convert a Shape element to a different variant in place. Preserves
19
+ * the bounding box (same x/y/w/h visual footprint) and common style
20
+ * attributes (stroke, fill, dash, etc.).
21
+ *
22
+ * Returns the NEW element, which has been inserted into the DOM in
23
+ * the same position as the old one. The old element is removed.
24
+ * Callers must update any references they hold (SelectionManager,
25
+ * PropertyPanel targets, etc.) to point at the returned element.
26
+ */
27
+ export declare function convertShape(oldEl: SVGElement, newType: ShapeType): SVGElement;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Annot SVG format version — the lever for evolving the annotation
3
+ * format while keeping old files readable.
4
+ *
5
+ * Every SVG Annot writes carries a `data-annot-version="N"` attribute
6
+ * on its root. Readers can inspect that attribute to know which schema
7
+ * a given document was written with and (eventually) run migrations.
8
+ *
9
+ * See `docs/svg-format.md` for the full format specification.
10
+ *
11
+ * Current version: 1 (initial versioned format — April 2026).
12
+ *
13
+ * ## Why a string, not a number
14
+ *
15
+ * Treating the version as a string leaves room for "1.1" / "2-rc1" /
16
+ * "next" style pre-release identifiers if we ever need them, without
17
+ * requiring consumers to juggle number-vs-string comparisons later.
18
+ * The value is still compared as an exact string — no implicit
19
+ * semver parsing — so there's no surprise ordering.
20
+ *
21
+ * ## Migration policy
22
+ *
23
+ * New writes ALWAYS stamp the CURRENT version. Old writes with
24
+ * missing attributes are treated as version "0" (the pre-versioning
25
+ * era) and read best-effort. When / if a breaking change lands, the
26
+ * reader gains a `migrateFromV0` / `migrateFromV1` branch; until
27
+ * then we simply stamp + read-through.
28
+ */
29
+ /** Current Annot SVG format version. Bump only with a migration. */
30
+ export declare const ANNOT_SVG_VERSION = "1";
31
+ /** The pseudo-version returned for documents written before Annot
32
+ * started stamping — i.e. any SVG without the
33
+ * `data-annot-version` attribute. Readers treat this as "legacy,
34
+ * parse leniently". */
35
+ export declare const ANNOT_SVG_VERSION_UNSTAMPED = "0";
36
+ /** DOM attribute name used to carry the version on the SVG root. */
37
+ export declare const ANNOT_SVG_VERSION_ATTR = "data-annot-version";
38
+ /**
39
+ * Stamp the current Annot format version onto an SVG root element.
40
+ * Call right before serializing so every written document carries
41
+ * the version.
42
+ *
43
+ * Idempotent — calling twice leaves the attribute with the same
44
+ * value. Safe to call on an SVG that already has a stamp (e.g. a
45
+ * clone of the live editor root, which carries the stamp from
46
+ * `CanvasManager`'s constructor).
47
+ */
48
+ export declare function stampAnnotVersion(svgRoot: Element): void;
49
+ /**
50
+ * Read the format version from a parsed SVG root. Returns the
51
+ * unstamped sentinel (`"0"`) when the attribute is absent — this is
52
+ * the signal to a reader that the document predates versioning and
53
+ * should be parsed with legacy tolerance.
54
+ */
55
+ export declare function readAnnotVersion(svgRoot: Element): string;
56
+ /**
57
+ * Cheap string-level version probe — reads the first
58
+ * `data-annot-version="…"` occurrence in the raw SVG text without
59
+ * instantiating a DOMParser. Useful at storage-layer boundaries
60
+ * where parsing is expensive and callers only want a quick
61
+ * "do I need to upgrade this?" check.
62
+ *
63
+ * Returns the unstamped sentinel when no match is found; not
64
+ * defensive against the attribute appearing mid-string in a CDATA
65
+ * or attribute value (extremely unlikely in practice, since Annot
66
+ * emits the attribute on the root well before any content).
67
+ */
68
+ export declare function getAnnotVersionFromString(svgString: string): string;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * SVG id-rewriting helpers.
3
+ *
4
+ * SVG references resolve by document-scope id (`url(#id)` for paint /
5
+ * clip-path / filter / mask, `href="#id"` / `xlink:href="#id"` for
6
+ * `<use>` / animation links). When an annotation tree is cloned and
7
+ * inserted into the same document — paste, scratchpad-stamp, future
8
+ * "duplicate as template" features — naive `cloneNode(true)` keeps
9
+ * the original ids on every clone. Two elements share an id, the
10
+ * browser picks the FIRST one in document order, and the second
11
+ * shape's ref points at the wrong target. Visible symptom for a
12
+ * sticky / text-on-shape: the pasted text gets clipped against the
13
+ * ORIGINAL shape's clipPath (located at the source position, not the
14
+ * paste point), so the text appears blank.
15
+ *
16
+ * `freshenInternalIds` walks a cloned subtree, replaces every
17
+ * descendant `id` with a fresh one, and rewrites every `url(#id)` /
18
+ * `href="#id"` reference inside the same subtree to point at the new
19
+ * id. References to ids OUTSIDE the subtree are left intact (the
20
+ * original document still has them, and the rewrite would otherwise
21
+ * silently drop the ref).
22
+ *
23
+ * Tier B — pure attribute manipulation, jsdom-friendly. No DOM-API
24
+ * dependencies beyond `getAttribute` / `setAttribute` / `attributes`.
25
+ */
26
+ /**
27
+ * Rewrite every internal id in `root` (and its descendants) to a
28
+ * fresh `${oldId}-<random>` and update every same-subtree `url(#id)`
29
+ * / `href="#id"` / `xlink:href="#id"` reference accordingly. The
30
+ * `root` element ITSELF is NOT renamed, since the caller may want
31
+ * to keep the wrapper's identity stable; descendants are.
32
+ *
33
+ * No-op when the subtree carries no ids. Idempotent in the sense
34
+ * that re-running yields different (but equally fresh) ids — the
35
+ * caller should call this exactly once per clone.
36
+ */
37
+ export declare function freshenInternalIds(root: SVGElement): void;
@@ -0,0 +1,34 @@
1
+ import { AnnotationShape } from '../utils/desktop-bridge.js';
2
+ /** Apply a group's `transform="translate(tx, ty)"` (or the
3
+ * canonical `data-tx` / `data-ty`) when pulling out coordinates,
4
+ * so the resulting shape lands at the same visual position the
5
+ * user sees in the editor. Reads `data-tx` / `data-ty` first (the
6
+ * transform-utils layer writes them when the visual transform
7
+ * attribute has been rewritten as a `matrix(...)` for rotation /
8
+ * flip support), then falls back to the literal `transform`
9
+ * attribute. */
10
+ export declare function translateOf(el: SVGElement): {
11
+ tx: number;
12
+ ty: number;
13
+ };
14
+ /** Pull rotation / flip / line-polish state into the AnnotationShape
15
+ * partial. Returned only when non-default so the JSON payload stays
16
+ * compact for unstyled shapes. */
17
+ export declare function transformOf(el: SVGElement): Partial<AnnotationShape>;
18
+ /**
19
+ * Convert one annotation `<g>` / leaf element into an
20
+ * `AnnotationShape`, or `null` for elements that have no
21
+ * Office-paste mapping (an unrecognised `<g>` wrapper, etc.).
22
+ *
23
+ * Mirrors the per-tag dispatch the toolbar's `#copyForOffice`
24
+ * historically inlined.
25
+ */
26
+ export declare function svgElementToAnnotationShape(el: SVGElement): AnnotationShape | null;
27
+ /**
28
+ * Convert all top-level annotation elements under
29
+ * `annotationsParent` into `AnnotationShape[]`, skipping any node
30
+ * that doesn't map to a recognised emitter.
31
+ */
32
+ export declare function svgAnnotationsToShapes(annotationsParent: {
33
+ childNodes: ArrayLike<Node>;
34
+ }): AnnotationShape[];