@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.
- package/CHANGELOG.md +7 -0
- package/LICENSE +201 -0
- package/README.md +56 -0
- package/dist/auto-capture-options.d.ts +78 -0
- package/dist/editor/arrow-markers.d.ts +142 -0
- package/dist/editor/bake-translate.d.ts +192 -0
- package/dist/editor/font-registry.d.ts +58 -0
- package/dist/editor/gradient-utils.d.ts +23 -0
- package/dist/editor/history-core.d.ts +48 -0
- package/dist/editor/icons/brand-icons.d.ts +53 -0
- package/dist/editor/icons/material-symbols.d.ts +105 -0
- package/dist/editor/icons/registry.d.ts +179 -0
- package/dist/editor/icons/render.d.ts +22 -0
- package/dist/editor/icons/sanitize.d.ts +60 -0
- package/dist/editor/index.d.ts +13 -0
- package/dist/editor/path-utils.d.ts +32 -0
- package/dist/editor/property-schema.d.ts +263 -0
- package/dist/editor/rich-text-mapper.d.ts +8 -0
- package/dist/editor/selection-geometry.d.ts +66 -0
- package/dist/editor/shape-utils.d.ts +27 -0
- package/dist/editor/svg-format.d.ts +68 -0
- package/dist/editor/svg-id-utils.d.ts +37 -0
- package/dist/editor/svg-to-annotation-shapes.d.ts +34 -0
- package/dist/editor/text-utils.d.ts +212 -0
- package/dist/editor/tool-lifecycle.d.ts +56 -0
- package/dist/editor/tool-options.d.ts +126 -0
- package/dist/editor/tool-panel-adapter.d.ts +105 -0
- package/dist/editor/tool-preset-serde.d.ts +43 -0
- package/dist/editor/tool-registry.d.ts +320 -0
- package/dist/editor/tool-style-reader.d.ts +36 -0
- package/dist/editor/tool-style-writer.d.ts +33 -0
- package/dist/editor/toolbar-icons.d.ts +84 -0
- package/dist/editor/transform-utils.d.ts +127 -0
- package/dist/editor/viewport-math.d.ts +69 -0
- package/dist/encode/index.d.ts +4 -0
- package/dist/encode/options.d.ts +79 -0
- package/dist/encode/png8.d.ts +10 -0
- package/dist/headless.d.ts +29 -0
- package/dist/icons/index.d.ts +15 -0
- package/dist/icons/types.d.ts +108 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3164 -0
- package/dist/storage/errors.d.ts +59 -0
- package/dist/storage/index.d.ts +6 -0
- package/dist/storage/metadata-cache.d.ts +231 -0
- package/dist/storage/path.d.ts +49 -0
- package/dist/storage/thumbnail-cache.d.ts +110 -0
- package/dist/storage/thumbnail.d.ts +31 -0
- package/dist/storage/types.d.ts +677 -0
- package/dist/utils/assert.d.ts +31 -0
- package/dist/utils/constants.d.ts +10 -0
- package/dist/utils/dash-utils.d.ts +6 -0
- package/dist/utils/desktop-bridge.d.ts +349 -0
- package/dist/utils/filename.d.ts +48 -0
- package/dist/utils/id.d.ts +21 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/types.d.ts +20 -0
- package/dist/xmp/xmp-browser.d.ts +39 -0
- package/dist/zip/zip-builder.d.ts +8 -0
- package/dist/zip/zip-bytes.d.ts +22 -0
- package/package.json +58 -0
- package/styles/editor.css +1912 -0
- package/styles/fonts.css +46 -0
- package/styles/property-panel.css +779 -0
- 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[];
|