@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,212 @@
|
|
|
1
|
+
import { TextRun } from '../utils/desktop-bridge.js';
|
|
2
|
+
import { TextVariant } from './tool-options.js';
|
|
3
|
+
export declare function stickyBgFor(color: string): string;
|
|
4
|
+
/** In-place text-shape color update.
|
|
5
|
+
*
|
|
6
|
+
* Writes the color to:
|
|
7
|
+
* - `data-color` on the wrapper (the cache attr that survives
|
|
8
|
+
* save / paste / Office round-trips when the inner `<text>`
|
|
9
|
+
* gets re-rendered).
|
|
10
|
+
* - `fill` on the inner `<text>` child (what the SVG renderer
|
|
11
|
+
* paints).
|
|
12
|
+
* - For sticky / callout variants: `fill` on the bg `<rect>`
|
|
13
|
+
* and the tail `<path>` derived from `stickyBgFor(color)`,
|
|
14
|
+
* so the body tint follows the text color the way it would
|
|
15
|
+
* if the wrapper were freshly built.
|
|
16
|
+
*
|
|
17
|
+
* Text-on-shape wrappers (see `isTextOnShape`) carry the user's
|
|
18
|
+
* drawn fill on the geometry primitive; the bg fill stays
|
|
19
|
+
* untouched here so a deliberate user-set color isn't
|
|
20
|
+
* overwritten by the text-color change.
|
|
21
|
+
*
|
|
22
|
+
* Tier B — pure jsdom-friendly element manipulation, no live-
|
|
23
|
+
* canvas dependency. Used by both PropertyPanel's textColor
|
|
24
|
+
* effect and TextTool's commit path so a sticky's body tint
|
|
25
|
+
* stays in lockstep with its text color across both surfaces. */
|
|
26
|
+
export declare function applyTextShapeColor(g: SVGElement, color: string): void;
|
|
27
|
+
/** Plain-text view of a run array — joins runs in order with `\n`
|
|
28
|
+
* inserted at every `line_break_after`. Useful when an editor
|
|
29
|
+
* wants the unstyled body (e.g. for the contentEditable seed). */
|
|
30
|
+
export declare function runsToPlainText(runs: readonly TextRun[]): string;
|
|
31
|
+
/** Build a uniformly-styled run array from a plain-text string,
|
|
32
|
+
* one run per line. Convenience for callers that don't yet emit
|
|
33
|
+
* styled runs (Phase 1's TextTool, the contentEditable commit
|
|
34
|
+
* before Phase 2's rich-text mapper, …). */
|
|
35
|
+
export declare function plainTextToRuns(text: string): TextRun[];
|
|
36
|
+
/** Horizontal text alignment inside the shape box. Maps onto
|
|
37
|
+
* SVG's `text-anchor` (`start` / `middle` / `end`). */
|
|
38
|
+
export type TextAnchor = "start" | "middle" | "end";
|
|
39
|
+
/** Vertical text alignment inside the shape box. SVG has no
|
|
40
|
+
* built-in vertical-anchor attribute on `<text>`, so the
|
|
41
|
+
* layout pass computes the y-origin from total run height +
|
|
42
|
+
* the vanchor value (`top` / `middle` / `bottom`). */
|
|
43
|
+
export type TextVerticalAnchor = "top" | "middle" | "bottom";
|
|
44
|
+
/** Auto-fit policy for a text-bearing shape (matches PowerPoint's
|
|
45
|
+
* three radio options under Format Shape → Text Box):
|
|
46
|
+
*
|
|
47
|
+
* "none" — text is clipped to the box; the user resizes
|
|
48
|
+
* the box to make room. Default.
|
|
49
|
+
* "shrink" — text shrinks (scales font-size down) when it
|
|
50
|
+
* overflows so it always fits. Currently records
|
|
51
|
+
* intent only — the layout pass that does the
|
|
52
|
+
* actual scaling is a follow-up.
|
|
53
|
+
* "resize" — the box grows (height) so the text always fits
|
|
54
|
+
* without clipping. Width stays fixed.
|
|
55
|
+
*
|
|
56
|
+
* Stored on the wrapper as `data-text-autofit`. */
|
|
57
|
+
export type TextAutofit = "none" | "shrink" | "resize";
|
|
58
|
+
/** Per-side text-box padding in user-space units. PowerPoint's
|
|
59
|
+
* "Text Box → Margins" surface; defaults map to the legacy
|
|
60
|
+
* hard-coded inset (10 px on plain & callout, 2 px on plain).
|
|
61
|
+
* Stored on the wrapper as `data-text-margin-{l,r,t,b}` so the
|
|
62
|
+
* per-side margins survive variant changes / re-edits. */
|
|
63
|
+
export interface TextMargins {
|
|
64
|
+
left: number;
|
|
65
|
+
right: number;
|
|
66
|
+
top: number;
|
|
67
|
+
bottom: number;
|
|
68
|
+
}
|
|
69
|
+
export interface TextShapeSpec {
|
|
70
|
+
x: number;
|
|
71
|
+
y: number;
|
|
72
|
+
w: number;
|
|
73
|
+
h: number;
|
|
74
|
+
variant: TextVariant;
|
|
75
|
+
/** Per-run text content + per-character formatting. Use
|
|
76
|
+
* `plainTextToRuns(s)` to bridge from plain text. */
|
|
77
|
+
runs: TextRun[];
|
|
78
|
+
/** Default font size (px) for runs without a per-run override. */
|
|
79
|
+
fontSize: number;
|
|
80
|
+
/** Default font family for runs without a per-run override. */
|
|
81
|
+
fontFamily: string;
|
|
82
|
+
/** Default text color for runs without a per-run override. */
|
|
83
|
+
color: string;
|
|
84
|
+
/** Horizontal alignment inside the shape box. Defaults to
|
|
85
|
+
* `start` for the auto-bg variants (plain / sticky / callout,
|
|
86
|
+
* matching the pre-Phase-3 layout) and to `middle` for
|
|
87
|
+
* text-on-shape kinds (PowerPoint default). */
|
|
88
|
+
textAnchor?: TextAnchor;
|
|
89
|
+
/** Vertical alignment inside the shape box. Defaults to
|
|
90
|
+
* `top` for the auto-bg variants, `middle` for text-on-shape. */
|
|
91
|
+
textVerticalAnchor?: TextVerticalAnchor;
|
|
92
|
+
/** Callout tail tip in canvas coordinates. If undefined and the
|
|
93
|
+
* variant is "callout", a default position (below-left of the box)
|
|
94
|
+
* is picked. */
|
|
95
|
+
tailX?: number;
|
|
96
|
+
tailY?: number;
|
|
97
|
+
}
|
|
98
|
+
/** Returns true when the wrapper is a text-on-shape variant —
|
|
99
|
+
* i.e. the wrapper was promoted from a Shape-tool primitive
|
|
100
|
+
* (`rect` / `rounded` / `ellipse`) via `wrapBareRectForText`,
|
|
101
|
+
* so the geometry primitive carries the user's drawn fill /
|
|
102
|
+
* stroke. The other text-bearing variants (`plain` / `sticky` /
|
|
103
|
+
* `callout`) carry an auto-generated bg whose tint follows the
|
|
104
|
+
* text color via `stickyBgFor`.
|
|
105
|
+
*
|
|
106
|
+
* Used by:
|
|
107
|
+
* - `applyTextShapeColor` (above) to skip the bg-fill refresh
|
|
108
|
+
* for user-drawn geometry.
|
|
109
|
+
* - `TextTool` to decide whether to hide the wrapper or just
|
|
110
|
+
* its `<text>` child during edit, and whether the editor
|
|
111
|
+
* overlay should paint its own yellow background or stay
|
|
112
|
+
* transparent.
|
|
113
|
+
*
|
|
114
|
+
* Returns false for non-text-shape elements (no `data-type`,
|
|
115
|
+
* raw `<rect>`, etc.) so callers can use it as a discriminator
|
|
116
|
+
* without an outer `isTextShapeElement` guard. */
|
|
117
|
+
export declare function isTextOnShape(el: Element): boolean;
|
|
118
|
+
/** Returns true when the element is a unified text-bearing shape
|
|
119
|
+
* (`<g data-type="shape" data-shape-kind="...">`). */
|
|
120
|
+
export declare function isTextShapeElement(el: Element): boolean;
|
|
121
|
+
export declare function detectTextVariant(g: SVGElement): TextVariant;
|
|
122
|
+
/** Read the spec off an existing text-bearing shape. Used when
|
|
123
|
+
* converting variant or re-rendering after an edit. */
|
|
124
|
+
export declare function readTextShapeSpec(g: SVGElement): TextShapeSpec;
|
|
125
|
+
/**
|
|
126
|
+
* Construct a fresh text-bearing shape group element that matches
|
|
127
|
+
* the spec. Does NOT insert it into the DOM; caller is responsible.
|
|
128
|
+
*/
|
|
129
|
+
export declare function createTextShape(spec: TextShapeSpec): SVGGElement;
|
|
130
|
+
/**
|
|
131
|
+
* Translate a text-bearing shape's children (bg `<rect>`, optional
|
|
132
|
+
* callout tail `<path>` + `data-tail-*` attrs, optional clipPath
|
|
133
|
+
* `<rect>`, every positioned `<tspan>` inside the inner `<text>`)
|
|
134
|
+
* by (dx, dy) in world space.
|
|
135
|
+
*
|
|
136
|
+
* Phase 2 of `docs/plans/move-bakes-coordinates.md`. The text-shape
|
|
137
|
+
* skeleton stores positions in child geometry attrs only; the outer
|
|
138
|
+
* `<g>` carries no x/y of its own. Shifting all those attrs in place
|
|
139
|
+
* leaves the visual unchanged when the OLD wrapper transform is
|
|
140
|
+
* removed and the children are re-read at their new world coords —
|
|
141
|
+
* which is exactly what the move-baker wants for the move-only
|
|
142
|
+
* case (no rotation / no flip).
|
|
143
|
+
*
|
|
144
|
+
* No-op for non-text-shape inputs. The caller is expected to have
|
|
145
|
+
* confirmed `g.getAttribute("data-type") === "shape"` before
|
|
146
|
+
* dispatching here.
|
|
147
|
+
*
|
|
148
|
+
* Tier B — pure Element manipulation, jsdom-friendly. The path
|
|
149
|
+
* shifter routes via `translatePathD` (Tier A), which keeps this
|
|
150
|
+
* helper independent of any browser-only path APIs.
|
|
151
|
+
*/
|
|
152
|
+
export declare function bakeTextShapeTranslate(g: SVGElement, dx: number, dy: number): void;
|
|
153
|
+
/**
|
|
154
|
+
* Rebuild the callout tail <path> off the current bg <rect> bounds and
|
|
155
|
+
* the stored data-tail-x / data-tail-y. Call after any change that
|
|
156
|
+
* affects either input — resize (bg rect changed) or tail-tip drag
|
|
157
|
+
* (data-tail-* changed) — to keep the visual consistent.
|
|
158
|
+
*
|
|
159
|
+
* No-op for non-callout shapes (or callouts missing the tail path).
|
|
160
|
+
*/
|
|
161
|
+
export declare function rebuildCalloutTail(g: SVGElement): void;
|
|
162
|
+
/**
|
|
163
|
+
* Update a callout's tail-tip position. Writes the new coords to the
|
|
164
|
+
* data-tail-* attributes and rebuilds the tail <path>. Coords are in
|
|
165
|
+
* the textbox's LOCAL space — the caller is responsible for subtracting
|
|
166
|
+
* any group transform (e.g. translate from a previous drag).
|
|
167
|
+
*/
|
|
168
|
+
export declare function setCalloutTail(g: SVGElement, localTailX: number, localTailY: number): void;
|
|
169
|
+
/** Wrap a bare `<rect>` (drawn by ShapeTool — sharp, rounded, or
|
|
170
|
+
* highlight) into the unified `<g data-type="shape">` skeleton so
|
|
171
|
+
* the user can add text to it. The original geometry element
|
|
172
|
+
* becomes the first child of the wrapper unchanged; we layer a
|
|
173
|
+
* `<clipPath>` + an initially empty `<text>` on top so the
|
|
174
|
+
* TextTool's edit flow reads / writes through the same skeleton
|
|
175
|
+
* it uses for plain / sticky / callout textboxes.
|
|
176
|
+
*
|
|
177
|
+
* Text-on-shape entry path — landed in Phase 3 of
|
|
178
|
+
* `docs/plans/_done/rich-text-and-shape-text.md`. The wrapper
|
|
179
|
+
* REPLACES the original element in its parent; the caller
|
|
180
|
+
* should reassign any selection / undo state to the returned
|
|
181
|
+
* `<g>`. See `isTextOnShape` for the runtime predicate that
|
|
182
|
+
* matches the resulting wrapper kinds.
|
|
183
|
+
*
|
|
184
|
+
* The matching {@link unwrapBareTextShape} reverses this
|
|
185
|
+
* transformation when the user cancels a freshly-opened text
|
|
186
|
+
* edit without typing. */
|
|
187
|
+
export declare function wrapBareRectForText(rect: SVGRectElement): SVGGElement;
|
|
188
|
+
/** Replace the `<text>` content of an existing text-bearing shape
|
|
189
|
+
* in-place. Used by every TextTool re-edit commit and by the
|
|
190
|
+
* PropertyPanel's text effects (color / variant / autofit /
|
|
191
|
+
* margins). The wrapper's other children (geometry, clipPath,
|
|
192
|
+
* callout tail) are left untouched.
|
|
193
|
+
*
|
|
194
|
+
* Tspan layout follows the same anchor / line-height rules as
|
|
195
|
+
* `createTextShape`. Layout reads its origin from the FIRST
|
|
196
|
+
* `<rect>` direct child (the geometry primitive — the user's
|
|
197
|
+
* drawn rect for text-on-shape, or the auto-generated bg for
|
|
198
|
+
* the plain / sticky / callout variants). */
|
|
199
|
+
export declare function replaceRunsInPlace(g: SVGElement, runs: readonly TextRun[]): void;
|
|
200
|
+
/** Reverse a {@link wrapBareRectForText} promotion when the user
|
|
201
|
+
* cancels a freshly-opened text edit without typing anything.
|
|
202
|
+
* Returns the original geometry element after replacing the
|
|
203
|
+
* wrapper in its parent. Idempotent on a non-wrapper element. */
|
|
204
|
+
export declare function unwrapBareTextShape(g: SVGElement): SVGElement;
|
|
205
|
+
/**
|
|
206
|
+
* Convert an existing text-bearing shape to a different variant.
|
|
207
|
+
* Preserves position, size, runs, and all metadata. Replaces the
|
|
208
|
+
* old element in the DOM and returns the new element (caller must
|
|
209
|
+
* update SelectionManager refs via the PropertyPanel's
|
|
210
|
+
* onTargetReplaced callback).
|
|
211
|
+
*/
|
|
212
|
+
export declare function convertTextVariant(oldG: SVGElement, newVariant: TextVariant): SVGElement;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal contract a host must satisfy for a `ToolBase` subclass to
|
|
3
|
+
* draw onto its canvas.
|
|
4
|
+
*
|
|
5
|
+
* Three operations cover every tool in the editor:
|
|
6
|
+
*
|
|
7
|
+
* - {@link attachDraft} — mount an in-flight element onto the
|
|
8
|
+
* canvas without saving history. Used while the user is still
|
|
9
|
+
* dragging; the tool keeps a ref so it can mutate attributes
|
|
10
|
+
* each pointer-move and either {@link saveHistory} (commit) or
|
|
11
|
+
* `el.remove()` (discard) at pointer-up.
|
|
12
|
+
*
|
|
13
|
+
* - {@link addAnnotation} — atomic "append + save" used by
|
|
14
|
+
* click-to-create tools (counter, single-click placements) where
|
|
15
|
+
* the element is fully formed at attach time.
|
|
16
|
+
*
|
|
17
|
+
* - {@link saveHistory} — push a history snapshot without changing
|
|
18
|
+
* children. Used when the tool committed an in-flight element
|
|
19
|
+
* via {@link attachDraft} earlier and the gesture is now
|
|
20
|
+
* finished, or when an existing element was mutated in place.
|
|
21
|
+
*/
|
|
22
|
+
export interface ToolDOMSurface {
|
|
23
|
+
attachDraft(el: SVGElement): void;
|
|
24
|
+
addAnnotation(el: SVGElement): void;
|
|
25
|
+
saveHistory(): void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Test-only surface that records every call so tests can drive a
|
|
29
|
+
* tool against an inert sink and assert the resulting sequence of
|
|
30
|
+
* DOM mutations + history saves.
|
|
31
|
+
*
|
|
32
|
+
* `attachDraft` and `addAnnotation` BOTH append the element to the
|
|
33
|
+
* supplied `host` parent so the tool's subsequent attribute
|
|
34
|
+
* mutations on the element survive across pointer-move callbacks.
|
|
35
|
+
* `addAnnotation` additionally bumps `saveCount`. The mock therefore
|
|
36
|
+
* lets a tool's full pointer-event lifecycle execute without ever
|
|
37
|
+
* touching a real `CanvasManager`.
|
|
38
|
+
*/
|
|
39
|
+
export interface MockToolSurface extends ToolDOMSurface {
|
|
40
|
+
/** All elements ever attached as drafts (not yet committed). */
|
|
41
|
+
readonly drafts: readonly SVGElement[];
|
|
42
|
+
/** All elements ever passed through `addAnnotation` — the
|
|
43
|
+
* click-to-create / atomic case. */
|
|
44
|
+
readonly committed: readonly SVGElement[];
|
|
45
|
+
/** Total count of `saveHistory` invocations, including those
|
|
46
|
+
* implicit in `addAnnotation`. Tests typically assert this hits
|
|
47
|
+
* exactly 1 after a successful single-shape gesture. */
|
|
48
|
+
saveCount: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Build a {@link MockToolSurface} that mounts attached / added
|
|
52
|
+
* elements into `host`. The host can be any `Element` that accepts
|
|
53
|
+
* `appendChild` — typically a freshly-created `<g>` SVG group from
|
|
54
|
+
* `document.createElementNS` under happy-dom.
|
|
55
|
+
*/
|
|
56
|
+
export declare function createMockToolSurface(host: Element): MockToolSurface;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure type / interface definitions for the editor's tool layer.
|
|
3
|
+
* Moved out of `tools/tool-base.ts` (now in
|
|
4
|
+
* `@ingcreators/annot-editor`) as part of Phase 2 of
|
|
5
|
+
* `docs/plans/three-package-split.md` — these types are shared
|
|
6
|
+
* between the editor (which contains the runtime `ToolBase`
|
|
7
|
+
* class + concrete tool classes) and the rest of `core/editor`
|
|
8
|
+
* (PropertyPanel was a temporary dependent; the helpers in
|
|
9
|
+
* `gradient-utils`, `redact-utils`, `shape-utils`, `text-utils`,
|
|
10
|
+
* `property-controls`, `property-panel-helpers` continue to read
|
|
11
|
+
* variant strings out of these types).
|
|
12
|
+
*
|
|
13
|
+
* Lives in core/editor (Tier B subpath) because consumers run
|
|
14
|
+
* under jsdom for tests and the types are used at the SVG
|
|
15
|
+
* element manipulation layer.
|
|
16
|
+
*/
|
|
17
|
+
export type ShapeType = "rect" | "rounded" | "ellipse" | "highlight";
|
|
18
|
+
/** Shape preset for the Counter (Marker) tool. Shared vocabulary
|
|
19
|
+
* with ShapeType so users see the same "rect / rounded / circle"
|
|
20
|
+
* mental model across tools. */
|
|
21
|
+
export type MarkerShape = "circle" | "rect" | "rounded";
|
|
22
|
+
export type ArrowHead = "none" | "end" | "both";
|
|
23
|
+
export type TextVariant = "plain" | "sticky" | "callout";
|
|
24
|
+
export type DrawStyle = "pen" | "highlighter";
|
|
25
|
+
export type RedactStyle = "mosaic" | "solid" | "blur";
|
|
26
|
+
/** Arrow head shapes (per endpoint). Matches PowerPoint's line panel
|
|
27
|
+
* exactly — the six OOXML preset types defined in the ECMA-376
|
|
28
|
+
* standard:
|
|
29
|
+
* none — no marker
|
|
30
|
+
* arrow — open / outline triangle
|
|
31
|
+
* triangle — filled triangle
|
|
32
|
+
* stealth — narrow filled angular arrow
|
|
33
|
+
* diamond — filled diamond
|
|
34
|
+
* oval — filled circle / oval
|
|
35
|
+
* Rendering is path-based (see `arrow-markers.ts`): the head is
|
|
36
|
+
* baked into the line's `<path>` data, not a separate `<marker>`
|
|
37
|
+
* element in `<defs>`. */
|
|
38
|
+
export type ArrowShape = "none" | "arrow" | "triangle" | "stealth" | "diamond" | "oval";
|
|
39
|
+
/** Per-dimension size preset — matches OOXML's `w` / `len` attribute
|
|
40
|
+
* granularity (sm / med / lg). Width is perpendicular to the stem,
|
|
41
|
+
* length is along the stem. */
|
|
42
|
+
export type ArrowDim = "sm" | "md" | "lg";
|
|
43
|
+
export type LineCap = "butt" | "round" | "square";
|
|
44
|
+
export type LineJoin = "miter" | "round" | "bevel";
|
|
45
|
+
/** Horizontal text alignment inside a text-bearing shape. Mirrors
|
|
46
|
+
* the SELECTION-side `data-text-anchor` (start / middle / end)
|
|
47
|
+
* exactly; the Text tool seeds new shapes with this value when set. */
|
|
48
|
+
export type TextAnchor = "start" | "middle" | "end";
|
|
49
|
+
/** Vertical text alignment inside a text-bearing shape. Mirrors
|
|
50
|
+
* the SELECTION-side `data-text-vanchor` (top / middle / bottom). */
|
|
51
|
+
export type TextVerticalAnchor = "top" | "middle" | "bottom";
|
|
52
|
+
/** A single gradient stop — color + 0..1 position along the gradient,
|
|
53
|
+
* optional transparency. The 2-stop case covers the common PowerPoint
|
|
54
|
+
* "fade from A to B" workflow; 3+ stops render fine too. */
|
|
55
|
+
export interface GradientStop {
|
|
56
|
+
color: string;
|
|
57
|
+
offset: number;
|
|
58
|
+
opacity?: number;
|
|
59
|
+
}
|
|
60
|
+
/** Linear-gradient stroke descriptor. When set on an element, the
|
|
61
|
+
* serializer emits a <linearGradient> into the SVG defs and references
|
|
62
|
+
* it via `stroke="url(#...)"`. */
|
|
63
|
+
export interface GradientSpec {
|
|
64
|
+
type: "linear";
|
|
65
|
+
stops: GradientStop[];
|
|
66
|
+
/** Direction in degrees (0 = left→right, 90 = top→bottom, etc). */
|
|
67
|
+
angle: number;
|
|
68
|
+
}
|
|
69
|
+
export interface ToolOptions {
|
|
70
|
+
strokeColor: string;
|
|
71
|
+
fillColor: string;
|
|
72
|
+
strokeWidth: number;
|
|
73
|
+
fontSize: number;
|
|
74
|
+
strokeDasharray: string;
|
|
75
|
+
fillOpacity: number;
|
|
76
|
+
/** Subtype for the unified Shape tool ("rect" / "rounded" / "ellipse").
|
|
77
|
+
* Ignored by tools that don't have a shape-family choice. */
|
|
78
|
+
shapeType?: ShapeType;
|
|
79
|
+
/** Head configuration for the unified Line/Arrow tool.
|
|
80
|
+
* "none" = plain line, "end" = arrow at endpoint (default),
|
|
81
|
+
* "both" = bi-directional arrow. Ignored by non-line tools. */
|
|
82
|
+
arrowHead?: ArrowHead;
|
|
83
|
+
/** Variant for the unified Text tool.
|
|
84
|
+
* "plain" = text only (no background)
|
|
85
|
+
* "sticky" = text with colored background (default, classic sticky note)
|
|
86
|
+
* "callout" = text with pointer tail (speech-bubble-like) */
|
|
87
|
+
textVariant?: TextVariant;
|
|
88
|
+
/** Font family CSS value for the Text tool. Defaults to "sans-serif"
|
|
89
|
+
* when not specified. Stored on the textbox as `data-font-family`
|
|
90
|
+
* so it survives save / reopen / Office paste. */
|
|
91
|
+
fontFamily?: string;
|
|
92
|
+
/** Horizontal text alignment for newly-drawn Text shapes. Defaults
|
|
93
|
+
* to "start" (left-aligned) when not specified. */
|
|
94
|
+
textAnchor?: TextAnchor;
|
|
95
|
+
/** Vertical text alignment for newly-drawn Text shapes. Defaults
|
|
96
|
+
* to "top" when not specified. */
|
|
97
|
+
textVerticalAnchor?: TextVerticalAnchor;
|
|
98
|
+
/** Style for the Draw tool: normal pen vs highlighter. */
|
|
99
|
+
drawStyle?: DrawStyle;
|
|
100
|
+
/** Style for the unified Redact tool. */
|
|
101
|
+
redactStyle?: RedactStyle;
|
|
102
|
+
/** Per-end arrow head shapes. When set, override the simpler
|
|
103
|
+
* `arrowHead` (which only distinguishes none / end / both). */
|
|
104
|
+
arrowHeadStart?: ArrowShape;
|
|
105
|
+
arrowHeadEnd?: ArrowShape;
|
|
106
|
+
/** Per-end arrow widths (perpendicular to stem) — sm / md / lg. */
|
|
107
|
+
arrowWidthStart?: ArrowDim;
|
|
108
|
+
arrowWidthEnd?: ArrowDim;
|
|
109
|
+
/** Per-end arrow lengths (along stem) — sm / md / lg. */
|
|
110
|
+
arrowLengthStart?: ArrowDim;
|
|
111
|
+
arrowLengthEnd?: ArrowDim;
|
|
112
|
+
/** Stroke opacity (0..1). Separate from fill-opacity. */
|
|
113
|
+
strokeOpacity?: number;
|
|
114
|
+
/** SVG stroke-linecap: butt / round / square. */
|
|
115
|
+
strokeLinecap?: LineCap;
|
|
116
|
+
/** SVG stroke-linejoin for shapes with corners (rect, path). */
|
|
117
|
+
strokeLinejoin?: LineJoin;
|
|
118
|
+
/** Optional gradient override for stroke. */
|
|
119
|
+
strokeGradient?: GradientSpec;
|
|
120
|
+
/** Optional gradient override for fill. */
|
|
121
|
+
fillGradient?: GradientSpec;
|
|
122
|
+
/** Color for the Highlight shape variant. */
|
|
123
|
+
highlightColor?: string;
|
|
124
|
+
/** Shape variant for the Counter (Marker) tool. */
|
|
125
|
+
markerShape?: MarkerShape;
|
|
126
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { PropertyControlId, PropertyControlOption } from './property-schema.js';
|
|
2
|
+
import { ToolOptions } from './tool-options.js';
|
|
3
|
+
import { ToolPanelExtraControlId } from './tool-registry.js';
|
|
4
|
+
/** Every id a Tool-side `panelControls` entry can reference, plus
|
|
5
|
+
* the Tool-only extras. Drives the type of `TOOL_PANEL_ADAPTERS`'s
|
|
6
|
+
* key. */
|
|
7
|
+
export type ToolPanelAdapterId = PropertyControlId | ToolPanelExtraControlId;
|
|
8
|
+
/**
|
|
9
|
+
* Read / write closure pair routing a single Tool-panel control's
|
|
10
|
+
* value onto the active preset.
|
|
11
|
+
*
|
|
12
|
+
* Most adapters genuinely don't need `toolId` — they target a fixed
|
|
13
|
+
* `ToolOptions` field regardless of which tool's panel is rendering.
|
|
14
|
+
* The exception is `tool.typeChips`, whose target field varies by
|
|
15
|
+
* tool (`shape.shapeType`, `arrow.arrowHead`, …) and is resolved
|
|
16
|
+
* dynamically via `TOOL_REGISTRY[toolId].variantField`. To keep the
|
|
17
|
+
* adapter shape uniform, every read / write takes `toolId` and the
|
|
18
|
+
* implementations that don't need it simply ignore the argument.
|
|
19
|
+
*
|
|
20
|
+
* Values are typed `unknown` at the registry level (a generic `T`
|
|
21
|
+
* runs into the contravariant-position assignability problem when
|
|
22
|
+
* mixed adapters share a single `Record` literal — the same reason
|
|
23
|
+
* the SELECTION-side `PropertyControlDef` keeps T at its default).
|
|
24
|
+
* Each adapter narrows internally via a single `String(value)` /
|
|
25
|
+
* `Number(value)` / `value as Shape` coercion at the write site;
|
|
26
|
+
* Phase 2's renderer is responsible for passing the right value
|
|
27
|
+
* type for the id it's dispatching.
|
|
28
|
+
*
|
|
29
|
+
* `selectionDef` (Phase 4 wiring): names the matching SELECTION-side
|
|
30
|
+
* `PropertyControlId` whose `PROPERTY_CONTROLS[…]` entry supplies the
|
|
31
|
+
* label / options / min / max / step / unit / allowNone metadata.
|
|
32
|
+
* `null` for Tool-only ids whose metadata has no SELECTION-side
|
|
33
|
+
* analogue. Phase 2's renderer doesn't read this yet — Phase 4 is
|
|
34
|
+
* where the metadata reuse lands.
|
|
35
|
+
*/
|
|
36
|
+
export interface ToolPanelAdapter {
|
|
37
|
+
/** Read the current value off the preset. */
|
|
38
|
+
read: (preset: ToolOptions, toolId: string) => unknown;
|
|
39
|
+
/** Mutate the preset in place. Returning the value or anything
|
|
40
|
+
* else is ignored — the caller is responsible for persistence
|
|
41
|
+
* (re-saving the preset, re-syncing `ctx.options`, etc.). */
|
|
42
|
+
write: (preset: ToolOptions, value: unknown, toolId: string) => void;
|
|
43
|
+
/** SELECTION-side def whose metadata (label / options / ranges)
|
|
44
|
+
* this adapter borrows. `null` for Tool-only ids. */
|
|
45
|
+
selectionDef?: PropertyControlId | null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Maps every `panelControls` id to its read / write contract.
|
|
49
|
+
*
|
|
50
|
+
* Adapter coverage rules (validated by `tool-panel-adapter.test.ts`):
|
|
51
|
+
* 1. Every `ToolPanelExtraControlId` has an entry.
|
|
52
|
+
* 2. Every id used by any tool's `panelControls` array has an entry.
|
|
53
|
+
* 3. Round-trip per adapter is a no-op: with a fully-populated preset,
|
|
54
|
+
* `write(preset, read(preset, toolId), toolId)` leaves the preset
|
|
55
|
+
* semantically unchanged (every field the adapter touches reads
|
|
56
|
+
* back the same value).
|
|
57
|
+
*
|
|
58
|
+
* SELECTION-side ids that aren't (yet) used on the Tool side don't
|
|
59
|
+
* need an adapter; only the ones the registry actually references.
|
|
60
|
+
*/
|
|
61
|
+
export declare const TOOL_PANEL_ADAPTERS: Readonly<Partial<Record<ToolPanelAdapterId, ToolPanelAdapter>>>;
|
|
62
|
+
/** Static metadata an adapter borrows from its SELECTION-side def.
|
|
63
|
+
* Phase 4 makes this the single source of truth for option arrays
|
|
64
|
+
* (dash / cap / font), labels, number-input ranges, and the
|
|
65
|
+
* `allowNone` color-picker flag — so a UX edit to (say)
|
|
66
|
+
* `PROPERTY_CONTROLS.strokeStyle.options` flows through to BOTH
|
|
67
|
+
* the SELECTION-side panel AND the Tool-side panel without a
|
|
68
|
+
* parallel edit.
|
|
69
|
+
*
|
|
70
|
+
* Per-tool divergences (marker's narrower strokeWidth + fontSize
|
|
71
|
+
* ranges, redact's `allowNone: false`, shape's "Fill" label
|
|
72
|
+
* override) stay in the renderer as documented exceptions on top
|
|
73
|
+
* of this metadata — there's no SELECTION-side equivalent to
|
|
74
|
+
* override against. */
|
|
75
|
+
export interface ToolPanelAdapterMetadata {
|
|
76
|
+
/** SELECTION-side label (e.g. "Color", "Width", "Dash type"). */
|
|
77
|
+
label: string;
|
|
78
|
+
/** Option list for `select` / `variantPicker` controls. Tool-side
|
|
79
|
+
* consumers decorate each option with a per-id preview SVG
|
|
80
|
+
* (dash sample, cap sample, etc.) at render time. */
|
|
81
|
+
options?: ReadonlyArray<PropertyControlOption>;
|
|
82
|
+
/** Inclusive lower bound for a number input. */
|
|
83
|
+
min?: number;
|
|
84
|
+
/** Inclusive upper bound for a number input. */
|
|
85
|
+
max?: number;
|
|
86
|
+
/** Spinner / arrow-key step granularity. */
|
|
87
|
+
step?: number;
|
|
88
|
+
/** Trailing unit label (e.g. "pt", "%"). */
|
|
89
|
+
unit?: string;
|
|
90
|
+
/** Whether a "No fill" sentinel is offered for color pickers. */
|
|
91
|
+
allowNone?: boolean;
|
|
92
|
+
}
|
|
93
|
+
/** Look up the SELECTION-side metadata an adapter borrows. Returns
|
|
94
|
+
* `null` when the adapter has no `selectionDef` (Tool-only ids
|
|
95
|
+
* like `tool.typeChips` / `tool.freehandDone`) or when the named
|
|
96
|
+
* def doesn't exist (which would be a registry typo — guarded
|
|
97
|
+
* defensively rather than throwing).
|
|
98
|
+
*
|
|
99
|
+
* Tier B — pure: no DOM access, no Element-taking; the returned
|
|
100
|
+
* metadata is just the static fields off `PROPERTY_CONTROLS`. */
|
|
101
|
+
export declare function selectionDefMetadata(id: ToolPanelAdapterId): ToolPanelAdapterMetadata | null;
|
|
102
|
+
/** Convenience: list of every adapter id present in the registry.
|
|
103
|
+
* Used by the test file to enumerate the adapters without depending
|
|
104
|
+
* on `Object.keys` order. */
|
|
105
|
+
export declare const TOOL_PANEL_ADAPTER_IDS: ReadonlyArray<ToolPanelAdapterId>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ToolOptions } from './tool-options.js';
|
|
2
|
+
/** Wire format selector. `snake` rewrites camelCase keys to
|
|
3
|
+
* snake_case via the table below (matches the Tauri YAML schema in
|
|
4
|
+
* `packages/desktop/src-tauri/src/commands/settings.rs` and the
|
|
5
|
+
* TypeScript-side `ToolPreset` interface). `camel` passes the keys
|
|
6
|
+
* through unchanged (matches the localStorage / chrome.storage
|
|
7
|
+
* blobs, which historically just `JSON.stringify`'d the live
|
|
8
|
+
* `ToolOptions`). */
|
|
9
|
+
export type PresetWireFormat = "snake" | "camel";
|
|
10
|
+
/**
|
|
11
|
+
* Convert a preset object to its wire form. Walks `presetFields` and
|
|
12
|
+
* copies each defined value to the output, optionally rewriting the
|
|
13
|
+
* key to snake_case. `undefined` values are dropped (matches
|
|
14
|
+
* `JSON.stringify`'s behaviour, plus keeps the on-disk YAML diff-
|
|
15
|
+
* friendly).
|
|
16
|
+
*
|
|
17
|
+
* Caller decides which fields are part of the wire schema by passing
|
|
18
|
+
* the tool's `TOOL_REGISTRY[id].presetFields`. Adding a new field to
|
|
19
|
+
* a tool means one entry in that array — no edit to this file
|
|
20
|
+
* required (unless the field is brand-new and missing from the
|
|
21
|
+
* `FIELD_TO_SNAKE` table, in which case `format: "snake"` will fall
|
|
22
|
+
* through to camelCase for that field).
|
|
23
|
+
*/
|
|
24
|
+
export declare function presetToWire(opts: Partial<ToolOptions>, presetFields: ReadonlyArray<keyof ToolOptions>, format: PresetWireFormat): Record<string, unknown>;
|
|
25
|
+
/**
|
|
26
|
+
* Inverse of `presetToWire`. Reads each `presetFields` entry off
|
|
27
|
+
* `record` (looking under the appropriate wire-format key) and
|
|
28
|
+
* returns a partial `ToolOptions` containing the present values.
|
|
29
|
+
*
|
|
30
|
+
* Empty strings on optional fields are treated as "absent" — the old
|
|
31
|
+
* imperative loader applied `|| undefined` to most variant fields
|
|
32
|
+
* (`shape_type`, `arrow_head`, …) so a corrupted-empty value didn't
|
|
33
|
+
* pollute the in-memory preset. We preserve that here uniformly so
|
|
34
|
+
* the helper is safe to use on every field, not just the ones that
|
|
35
|
+
* had the explicit `|| undefined` in the old code.
|
|
36
|
+
*/
|
|
37
|
+
export declare function presetFromWire(record: Record<string, unknown>, presetFields: ReadonlyArray<keyof ToolOptions>, format: PresetWireFormat): Partial<ToolOptions>;
|
|
38
|
+
/** Translate a snake_case wire key back to its camelCase
|
|
39
|
+
* `ToolOptions` field name, or `undefined` if the key isn't in the
|
|
40
|
+
* shared SerDe table. Exposed as a low-level helper for callers
|
|
41
|
+
* (e.g. preset-key migration paths) that operate on raw wire
|
|
42
|
+
* records without the tool-fields filter. */
|
|
43
|
+
export declare function fieldForSnakeKey(snakeKey: string): keyof ToolOptions | undefined;
|