@ingcreators/annot-annotator 0.5.0 → 0.7.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 +305 -0
- package/dist/annotator.d.ts +16 -8
- package/dist/diff-aggregate.d.ts +6 -0
- package/dist/diff.d.ts +30 -0
- package/dist/dsl/schema.d.ts +68 -0
- package/dist/dsl/types.d.ts +41 -1
- package/dist/flatten-editable-png.d.ts +37 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +412 -157
- package/dist/redact-burn.d.ts +63 -0
- package/package.json +11 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,310 @@
|
|
|
1
1
|
# @ingcreators/annot-annotator
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- `toEditablePng`'s `EditableInput` gains the schema-2.0 provenance
|
|
8
|
+
fields (`sourceUrl` / `createdAt` / `producer` / `dpr`), written
|
|
9
|
+
into the XMP packet as first-class elements. `producer` defaults
|
|
10
|
+
to `"annotator"`. Prefer these over the old `WELL_KNOWN_TAG_KEYS`
|
|
11
|
+
smuggling convention (`source` / `capturedAt` tags).
|
|
12
|
+
|
|
13
|
+
## 0.6.0
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- 6124d59: **`BboxAnnotation` palette extensions: `freehand` + `focusMask`** —
|
|
18
|
+
Phase 3b of `docs/plans/living-spec-authoring-roadmap.md`.
|
|
19
|
+
|
|
20
|
+
Two new variants on the `BboxAnnotation` union expose the rest of
|
|
21
|
+
the Annot visual palette to `bboxAnnotationsToSvg()` and through
|
|
22
|
+
it to `Annotator.toPng()` / `.toSvg()` / `.toEditablePng()`:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
// Free-form path stroke — `path` is the SVG <path> `d` attribute.
|
|
26
|
+
{
|
|
27
|
+
type: "freehand",
|
|
28
|
+
path: "M100,200 L150,250 L200,210",
|
|
29
|
+
intent: "info", // optional — defaults to "error"
|
|
30
|
+
strokeWidth: 4, // optional — defaults to 2
|
|
31
|
+
fill: "#ffeecc", // optional — defaults to "none"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Dim everything except the cutout region. One <path> with
|
|
35
|
+
// fill-rule="evenodd" combines a full-image rect with the
|
|
36
|
+
// cutout — the even-odd rule cancels overlap.
|
|
37
|
+
{
|
|
38
|
+
type: "focusMask",
|
|
39
|
+
cutout: { x: 200, y: 100, width: 80, height: 40 },
|
|
40
|
+
imageWidth: 1280,
|
|
41
|
+
imageHeight: 800,
|
|
42
|
+
dimColor: "rgba(0,0,0,0.5)", // optional — default same value
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`@ingcreators/annot-product-docs`'s `<Screen annotations>`
|
|
47
|
+
Image Service composition (Phase 3c) will map yaml
|
|
48
|
+
`AnnotationSpec` entries to these two primitives plus the
|
|
49
|
+
existing `rect` / `circle` / `arrow` / `text` / `callout` /
|
|
50
|
+
`numberedBadge` shapes. Useful standalone for any annotator
|
|
51
|
+
caller (Playwright fixtures, MCP server, custom test reporters)
|
|
52
|
+
without involving yaml or MDX.
|
|
53
|
+
|
|
54
|
+
**JSON schemas** — `BBOX_ANNOTATION_SCHEMA.oneOf` gains
|
|
55
|
+
`BBOX_FREEHAND` + `BBOX_FOCUS_MASK` entries so MCP callers can
|
|
56
|
+
validate either kind at the boundary.
|
|
57
|
+
|
|
58
|
+
**Out of scope** — redact (mosaic / blur) needs raster pixel
|
|
59
|
+
access and is not implementable as an SVG fragment; it stays
|
|
60
|
+
on the existing destructive `burnRedactions` path in
|
|
61
|
+
`@ingcreators/annot-mcp`. Phase 3a's annotation yaml rejects
|
|
62
|
+
`style: "mosaic" | "blur"` accordingly.
|
|
63
|
+
|
|
64
|
+
**Compatibility** — additive. Existing `BboxAnnotation` callers
|
|
65
|
+
keep working; the new variants are opt-in by setting
|
|
66
|
+
`type: "freehand" | "focusMask"`.
|
|
67
|
+
|
|
68
|
+
- 0c7ac26: **Relocate `burnRedactions` from `@ingcreators/annot-mcp` to
|
|
69
|
+
`@ingcreators/annot-annotator`** — Phase 3e of
|
|
70
|
+
`docs/plans/living-spec-authoring-roadmap.md` (Phase 3 follow-up).
|
|
71
|
+
|
|
72
|
+
The destructive raster burn primitive (solid / mosaic / blur over
|
|
73
|
+
a PNG buffer, built on `@napi-rs/canvas`) historically lived in
|
|
74
|
+
`@ingcreators/annot-mcp` because the MCP server's
|
|
75
|
+
`annot_redact_screenshot` tool was the first caller. The function
|
|
76
|
+
itself has no MCP-specific surface — it's pure
|
|
77
|
+
(`pngBytes + regions → pngBytes`). To let non-MCP callers consume
|
|
78
|
+
it without dragging the MCP server's dep footprint (Playwright,
|
|
79
|
+
`@modelcontextprotocol/sdk`, etc.), the primitive moves to
|
|
80
|
+
`@ingcreators/annot-annotator` — the canonical Node-side raster
|
|
81
|
+
home, which already depends on `@napi-rs/canvas` for its encode
|
|
82
|
+
pipeline (so the move adds **zero** transitive deps).
|
|
83
|
+
|
|
84
|
+
### `@ingcreators/annot-annotator` — new public surface
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import {
|
|
88
|
+
burnRedactions,
|
|
89
|
+
type RedactRegion,
|
|
90
|
+
} from "@ingcreators/annot-annotator";
|
|
91
|
+
|
|
92
|
+
const out = await burnRedactions(pngBytes, [
|
|
93
|
+
{
|
|
94
|
+
bbox: { x: 10, y: 20, width: 100, height: 30 },
|
|
95
|
+
style: "solid",
|
|
96
|
+
color: "#000000",
|
|
97
|
+
},
|
|
98
|
+
{ bbox: { x: 200, y: 100, width: 80, height: 40 }, style: "mosaic" },
|
|
99
|
+
{ bbox: { x: 0, y: 0, width: 64, height: 64 }, style: "blur" },
|
|
100
|
+
]);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`RedactRegion` is exposed as an alias of `BboxRedactRegion`
|
|
104
|
+
(structurally identical, already declared in the DSL types) so
|
|
105
|
+
existing MCP-side consumers see no shape change.
|
|
106
|
+
|
|
107
|
+
### `@ingcreators/annot-mcp` — no public API change
|
|
108
|
+
|
|
109
|
+
The existing `burnRedactions` + `RedactRegion` re-exports from
|
|
110
|
+
the package root keep working byte-identical, sourced from the
|
|
111
|
+
annotator instead of the old MCP-local file. MCP's
|
|
112
|
+
`annot_redact_screenshot` / `annot_redact_url` tools continue
|
|
113
|
+
to import from `../redact/burn.js`, which is now a one-line
|
|
114
|
+
re-export from annotator.
|
|
115
|
+
|
|
116
|
+
### Compatibility
|
|
117
|
+
|
|
118
|
+
Additive on annotator's side; zero behaviour change on MCP's
|
|
119
|
+
side. Tests move with the code (annotator 64 → 71 passed; MCP
|
|
120
|
+
91 → 84 passed — same scenarios at the new home).
|
|
121
|
+
|
|
122
|
+
### Out of scope
|
|
123
|
+
|
|
124
|
+
`@napi-rs/canvas` stays as an MCP direct dep — `compare/diff.ts`
|
|
125
|
+
and several other MCP tool tests still use it directly, so
|
|
126
|
+
collapsing it onto a transitive-via-annotator import is a
|
|
127
|
+
separate cleanup.
|
|
128
|
+
|
|
129
|
+
- 64dc6e8: **Relocate `diffScreenshots` from `@ingcreators/annot-mcp` to
|
|
130
|
+
`@ingcreators/annot-annotator`** — Phase 3i of
|
|
131
|
+
`docs/plans/living-spec-authoring-roadmap.md` (Phase 3
|
|
132
|
+
follow-up #2). Same pattern as 3e's `burnRedactions` relocate.
|
|
133
|
+
|
|
134
|
+
The pixelmatch-driven PNG comparison + contiguous-region bbox
|
|
135
|
+
aggregation lived in `@ingcreators/annot-mcp/compare/` for
|
|
136
|
+
historical reasons (the MCP server's
|
|
137
|
+
`annot_compare_screenshots` tool was the first caller). The
|
|
138
|
+
function itself has no MCP-specific surface — it's pure
|
|
139
|
+
(`pngBytes + pngBytes → DiffResult`). Relocating it to
|
|
140
|
+
`@ingcreators/annot-annotator` lets non-MCP callers
|
|
141
|
+
(Playwright visual regression fixtures, Astro pixel drift CI,
|
|
142
|
+
custom test reporters, editor before/after preview) consume
|
|
143
|
+
it without dragging the MCP server's dep footprint.
|
|
144
|
+
|
|
145
|
+
### `@ingcreators/annot-annotator` — new public surface
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import {
|
|
149
|
+
diffScreenshots,
|
|
150
|
+
aggregateDiffRegions,
|
|
151
|
+
DimensionMismatchError,
|
|
152
|
+
type DiffResult,
|
|
153
|
+
type DiffOptions,
|
|
154
|
+
} from "@ingcreators/annot-annotator";
|
|
155
|
+
|
|
156
|
+
const result = await diffScreenshots(beforePng, afterPng, { threshold: 0.1 });
|
|
157
|
+
// → { mismatchedPixels: number, regions: BBox[], width, height }
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
annotator gains `pixelmatch` (~4 KB, no transitive deps) as a
|
|
161
|
+
runtime dep.
|
|
162
|
+
|
|
163
|
+
### `@ingcreators/annot-mcp` — no public API change
|
|
164
|
+
|
|
165
|
+
The existing `compare/diff.ts` + `compare/aggregate.ts` modules
|
|
166
|
+
become one-line re-export shims forwarding from annotator. MCP's
|
|
167
|
+
internal callers (`tools/compare-screenshots.ts`) and any
|
|
168
|
+
external consumer importing from `@ingcreators/annot-mcp` keep
|
|
169
|
+
working byte-identical.
|
|
170
|
+
|
|
171
|
+
### Compatibility
|
|
172
|
+
|
|
173
|
+
Additive on annotator's side; zero behaviour change on MCP's
|
|
174
|
+
side. Tests move with the code (annotator 71 → 81 passed; MCP
|
|
175
|
+
84 → 78 passed — same scenarios at the new home, plus a new
|
|
176
|
+
`diffScreenshots` smoke test that the MCP-side aggregate-only
|
|
177
|
+
test didn't cover).
|
|
178
|
+
|
|
179
|
+
### Out of scope
|
|
180
|
+
|
|
181
|
+
`pixelmatch` stays as a direct MCP dep — even though MCP no
|
|
182
|
+
longer imports it from the moved code, it's a tiny package
|
|
183
|
+
and removing the explicit dep would force consumers to rely
|
|
184
|
+
on a transitive resolution through annotator, which is more
|
|
185
|
+
fragile than declaring the intent directly.
|
|
186
|
+
|
|
187
|
+
- 691bec5: **Add `flattenEditablePng(pngBytes) → pngBytes`** — Phase 3j of
|
|
188
|
+
`docs/plans/living-spec-authoring-roadmap.md` (Phase 3
|
|
189
|
+
follow-up #2). The editor's editable-PNG format embeds the
|
|
190
|
+
original un-annotated capture + the annotations SVG in PNG
|
|
191
|
+
ancillary chunks for re-edit; "flatten" drops those chunks and
|
|
192
|
+
keeps just the visible (already-annotated) bytes.
|
|
193
|
+
|
|
194
|
+
### `@ingcreators/annot-annotator` — new public surface
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import { flattenEditablePng } from "@ingcreators/annot-annotator";
|
|
198
|
+
|
|
199
|
+
const flat = flattenEditablePng(editablePngBytes);
|
|
200
|
+
// → flat PNG: same visible pixels, no Adobe XMP iTXt chunk,
|
|
201
|
+
// no custom svGo chunk. `readEditablePngBytes(flat)` returns
|
|
202
|
+
// null. File size drops significantly (the editable layer
|
|
203
|
+
// roughly doubled the bytes).
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### `@ingcreators/annot-core/xmp-bytes` — new public surface
|
|
207
|
+
|
|
208
|
+
The implementation lives in `@ingcreators/annot-core` as
|
|
209
|
+
`stripPngEditableLayer` — the same chunk-walking helper that
|
|
210
|
+
`writePngWithMetadata` / `writePngWithTagsOnly` already used
|
|
211
|
+
internally to clean stale metadata before re-injecting. Now
|
|
212
|
+
exported so other Tier A consumers (not just annotator) can
|
|
213
|
+
use it directly.
|
|
214
|
+
|
|
215
|
+
annotator's `flattenEditablePng` is a one-line wrapper that
|
|
216
|
+
calls `stripPngEditableLayer` under a more user-facing name.
|
|
217
|
+
|
|
218
|
+
### Why this is metadata removal, not re-rasterization
|
|
219
|
+
|
|
220
|
+
`toEditablePng` rasterizes the SVG fragment onto the base image
|
|
221
|
+
FIRST and embeds the editable layer as ancillary PNG chunks
|
|
222
|
+
(`iTXt` carrying Adobe XMP + custom `svGo` chunk). The visible
|
|
223
|
+
bytes are already the annotated bitmap. Flattening strips the
|
|
224
|
+
ancillary chunks; the IDAT pixel data stays byte-identical.
|
|
225
|
+
No decode, no re-encode, no `@napi-rs/canvas` round-trip.
|
|
226
|
+
|
|
227
|
+
### Use cases
|
|
228
|
+
- **Publish-flat** — editor session → distribution-ready PNG;
|
|
229
|
+
the editable layer is dead weight for downstream consumers
|
|
230
|
+
(Slack drop, third-party viewers).
|
|
231
|
+
- **File size** — editable PNG roughly doubles in bytes
|
|
232
|
+
(original + SVG embedded); flattening drops the overhead.
|
|
233
|
+
- **Privacy hardening** — `burnRedactions` is the strong
|
|
234
|
+
version for _redact_ regions; flattening drops the
|
|
235
|
+
recoverable original entirely for _all_ annotations,
|
|
236
|
+
including non-redact ones whose annotated visual the
|
|
237
|
+
publisher wants to keep but whose original capture they
|
|
238
|
+
don't want shippable.
|
|
239
|
+
|
|
240
|
+
### Internal rename in annot-core
|
|
241
|
+
|
|
242
|
+
The private `removePngMetadata` helper in
|
|
243
|
+
`@ingcreators/annot-core/xmp-bytes` is renamed to
|
|
244
|
+
`stripPngEditableLayer` (clearer name describing what it does
|
|
245
|
+
rather than how it's used). Internal callers in the same
|
|
246
|
+
module updated. No external API change for the rename itself;
|
|
247
|
+
`writePngWithMetadata` + `writePngWithTagsOnly` keep their
|
|
248
|
+
existing signatures + behaviour.
|
|
249
|
+
|
|
250
|
+
### Compatibility
|
|
251
|
+
|
|
252
|
+
Additive on annotator + core. No behaviour change for existing
|
|
253
|
+
callers (the only internal rename is a private helper).
|
|
254
|
+
|
|
255
|
+
- 9697f27: **Export `burnRegions` as an operation-aligned alias for
|
|
256
|
+
`burnRedactions`** — Phase 3k of
|
|
257
|
+
`docs/plans/living-spec-authoring-roadmap.md`
|
|
258
|
+
(Phase 3 follow-up #2). Closes the follow-up.
|
|
259
|
+
|
|
260
|
+
`burnRedactions` is named for its first caller's intent (MCP's
|
|
261
|
+
`annot_redact_screenshot`), but the underlying primitive is a
|
|
262
|
+
`pngBytes + region[] → pngBytes` raster transform — generic
|
|
263
|
+
over the caller's purpose. The new export surfaces the
|
|
264
|
+
operation-aligned name alongside the intent-named original.
|
|
265
|
+
|
|
266
|
+
### `@ingcreators/annot-annotator` — new public export
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
import { burnRegions } from "@ingcreators/annot-annotator";
|
|
270
|
+
|
|
271
|
+
// Identical signature + behaviour to burnRedactions.
|
|
272
|
+
const out = await burnRegions(pngBytes, [
|
|
273
|
+
{ bbox: { x: 10, y: 20, width: 100, height: 30 }, style: "mosaic" },
|
|
274
|
+
]);
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Identity-equal to `burnRedactions` (`burnRegions === burnRedactions`
|
|
278
|
+
at the export level) — picking one name over the other is purely
|
|
279
|
+
a docs-readability choice.
|
|
280
|
+
|
|
281
|
+
### Use cases that motivated the alias
|
|
282
|
+
|
|
283
|
+
The function isn't redact-specific — the JSDoc on `burnRedactions`
|
|
284
|
+
now enumerates:
|
|
285
|
+
- Editor-side "highlight this region with a translucent colour
|
|
286
|
+
and ship it baked" workflow.
|
|
287
|
+
- Visual-regression pre-processing — burn dynamic content
|
|
288
|
+
(timestamps, login state badges) into the screenshot so pixel
|
|
289
|
+
diffs stay deterministic.
|
|
290
|
+
- Watermark / overlay burn for downstream distribution.
|
|
291
|
+
- Privacy hardening at non-redact regions (e.g. blur a logo in
|
|
292
|
+
a publicly-shared screenshot).
|
|
293
|
+
|
|
294
|
+
For any of these, `burnRegions` reads as the natural name.
|
|
295
|
+
Redact callers stay on `burnRedactions` (still the recommended
|
|
296
|
+
name when the intent IS redaction); no migration forced.
|
|
297
|
+
|
|
298
|
+
### `@ingcreators/annot-mcp` — no public API change
|
|
299
|
+
|
|
300
|
+
MCP's `compare/burn.ts` re-export shim + `index.ts` forward both
|
|
301
|
+
names. Existing `burnRedactions` callers see no change.
|
|
302
|
+
|
|
303
|
+
### Compatibility
|
|
304
|
+
|
|
305
|
+
Additive. `burnRedactions` keeps its public API + JSDoc; the
|
|
306
|
+
alias is purely additive.
|
|
307
|
+
|
|
3
308
|
## 0.5.0
|
|
4
309
|
|
|
5
310
|
### Minor Changes
|
package/dist/annotator.d.ts
CHANGED
|
@@ -22,19 +22,27 @@ export interface AnnotatorInput {
|
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* Input for {@link Annotator.toEditablePng}. Same shape as
|
|
25
|
-
* {@link AnnotatorInput} plus optional opaque kv `tags`
|
|
26
|
-
*
|
|
25
|
+
* {@link AnnotatorInput} plus optional opaque kv `tags` and the
|
|
26
|
+
* schema-2.0 provenance fields written into the embedded XMP.
|
|
27
27
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* - `commit` — git SHA when applicable.
|
|
28
|
+
* Since XMP schema 2.0 (`docs/metadata-format.md`), provenance has
|
|
29
|
+
* first-class fields — prefer `sourceUrl` / `createdAt` / `producer`
|
|
30
|
+
* / `dpr` over smuggling the equivalent through `tags` (`source` /
|
|
31
|
+
* `capturedAt` from the old `WELL_KNOWN_TAG_KEYS` convention).
|
|
32
|
+
* `screen` / `commit` remain tags.
|
|
34
33
|
*/
|
|
35
34
|
export interface EditableInput extends AnnotatorInput {
|
|
36
35
|
/** Optional opaque kv tags. */
|
|
37
36
|
tags?: Record<string, string>;
|
|
37
|
+
/** URL of the captured page, when applicable. */
|
|
38
|
+
sourceUrl?: string;
|
|
39
|
+
/** ISO timestamp of the capture moment. */
|
|
40
|
+
createdAt?: string;
|
|
41
|
+
/** What produced the PNG (`"annotator"` / `"mcp"` / `"playwright"`
|
|
42
|
+
* / a caller-supplied identifier). Defaults to `"annotator"`. */
|
|
43
|
+
producer?: string;
|
|
44
|
+
/** devicePixelRatio at capture time, when known. */
|
|
45
|
+
dpr?: number;
|
|
38
46
|
}
|
|
39
47
|
/** Annotator construction options. All optional. */
|
|
40
48
|
export interface AnnotatorOptions {
|
package/dist/diff.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BBox } from './dsl/types.js';
|
|
2
|
+
export interface DiffResult {
|
|
3
|
+
/** Number of mismatched pixels. */
|
|
4
|
+
mismatchedPixels: number;
|
|
5
|
+
/** Bounding boxes of contiguous changed regions. */
|
|
6
|
+
regions: BBox[];
|
|
7
|
+
/** Dimensions of the input pair. */
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
}
|
|
11
|
+
export interface DiffOptions {
|
|
12
|
+
/** Matching threshold (0 to 1); smaller is more sensitive. */
|
|
13
|
+
threshold?: number;
|
|
14
|
+
}
|
|
15
|
+
export declare class DimensionMismatchError extends Error {
|
|
16
|
+
constructor(a: {
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
}, b: {
|
|
20
|
+
width: number;
|
|
21
|
+
height: number;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Compare two PNGs pixel-by-pixel and return the changed-region
|
|
26
|
+
* bboxes. Throws `DimensionMismatchError` when the inputs have
|
|
27
|
+
* different sizes — the caller has to capture both at the same
|
|
28
|
+
* viewport for the comparison to make sense.
|
|
29
|
+
*/
|
|
30
|
+
export declare function diffScreenshots(before: Uint8Array, after: Uint8Array, options?: DiffOptions): Promise<DiffResult>;
|
package/dist/dsl/schema.d.ts
CHANGED
|
@@ -206,6 +206,74 @@ export declare const BBOX_ANNOTATION_SCHEMA: {
|
|
|
206
206
|
type: string;
|
|
207
207
|
};
|
|
208
208
|
};
|
|
209
|
+
} | {
|
|
210
|
+
type: string;
|
|
211
|
+
required: string[];
|
|
212
|
+
additionalProperties: boolean;
|
|
213
|
+
properties: {
|
|
214
|
+
type: {
|
|
215
|
+
const: string;
|
|
216
|
+
};
|
|
217
|
+
path: {
|
|
218
|
+
type: string;
|
|
219
|
+
minLength: number;
|
|
220
|
+
};
|
|
221
|
+
intent: {
|
|
222
|
+
$ref: string;
|
|
223
|
+
};
|
|
224
|
+
stroke: {
|
|
225
|
+
type: string;
|
|
226
|
+
};
|
|
227
|
+
strokeWidth: {
|
|
228
|
+
type: string;
|
|
229
|
+
minimum: number;
|
|
230
|
+
};
|
|
231
|
+
fill: {
|
|
232
|
+
type: string;
|
|
233
|
+
};
|
|
234
|
+
color: {
|
|
235
|
+
type: string;
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
} | {
|
|
239
|
+
type: string;
|
|
240
|
+
required: string[];
|
|
241
|
+
additionalProperties: boolean;
|
|
242
|
+
properties: {
|
|
243
|
+
type: {
|
|
244
|
+
const: string;
|
|
245
|
+
};
|
|
246
|
+
cutout: {
|
|
247
|
+
$ref: string;
|
|
248
|
+
};
|
|
249
|
+
imageWidth: {
|
|
250
|
+
type: string;
|
|
251
|
+
minimum: number;
|
|
252
|
+
};
|
|
253
|
+
imageHeight: {
|
|
254
|
+
type: string;
|
|
255
|
+
minimum: number;
|
|
256
|
+
};
|
|
257
|
+
dimColor: {
|
|
258
|
+
type: string;
|
|
259
|
+
};
|
|
260
|
+
intent: {
|
|
261
|
+
$ref: string;
|
|
262
|
+
};
|
|
263
|
+
stroke: {
|
|
264
|
+
type: string;
|
|
265
|
+
};
|
|
266
|
+
strokeWidth: {
|
|
267
|
+
type: string;
|
|
268
|
+
minimum: number;
|
|
269
|
+
};
|
|
270
|
+
fill: {
|
|
271
|
+
type: string;
|
|
272
|
+
};
|
|
273
|
+
color: {
|
|
274
|
+
type: string;
|
|
275
|
+
};
|
|
276
|
+
};
|
|
209
277
|
} | {
|
|
210
278
|
type: string;
|
|
211
279
|
required: string[];
|
package/dist/dsl/types.d.ts
CHANGED
|
@@ -98,7 +98,47 @@ export interface RawAnnotation {
|
|
|
98
98
|
type: "raw";
|
|
99
99
|
svgFragment: string;
|
|
100
100
|
}
|
|
101
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Free-form path. The `path` field carries the SVG `<path>` d
|
|
103
|
+
* attribute verbatim — `M` / `L` / `C` / `Q` / `Z` commands.
|
|
104
|
+
*
|
|
105
|
+
* Style fields are interpreted as stroke-only by default
|
|
106
|
+
* (`fill: "none"` unless overridden) so a freehand stroke
|
|
107
|
+
* doesn't accidentally fill its own enclosed area.
|
|
108
|
+
*
|
|
109
|
+
* Phase 3b of
|
|
110
|
+
* [`docs/plans/living-spec-authoring-roadmap.md`](../../../../docs/plans/living-spec-authoring-roadmap.md).
|
|
111
|
+
*/
|
|
112
|
+
export type BboxFreehandAnnotation = AnnotationStyle & {
|
|
113
|
+
type: "freehand";
|
|
114
|
+
path: string;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Focus mask — dim the entire image area EXCEPT the cutout
|
|
118
|
+
* region. The cutout draws crisp; everything outside is filled
|
|
119
|
+
* with `dimColor` (default `rgba(0,0,0,0.5)`).
|
|
120
|
+
*
|
|
121
|
+
* Implemented as a single `<path>` element with `fill-rule="evenodd"`
|
|
122
|
+
* combining a full-image rect with the cutout rect: the
|
|
123
|
+
* even-odd rule cancels overlap, leaving the cutout area
|
|
124
|
+
* transparent on top of the dimmed background.
|
|
125
|
+
*
|
|
126
|
+
* `imageWidth` / `imageHeight` are required so the outer rect
|
|
127
|
+
* covers the full screenshot; the renderer rejects a partial
|
|
128
|
+
* focus mask. Phase 3b of
|
|
129
|
+
* [`docs/plans/living-spec-authoring-roadmap.md`](../../../../docs/plans/living-spec-authoring-roadmap.md).
|
|
130
|
+
*/
|
|
131
|
+
export type BboxFocusMaskAnnotation = AnnotationStyle & {
|
|
132
|
+
type: "focusMask";
|
|
133
|
+
/** The crisp cutout region. */
|
|
134
|
+
cutout: BBox;
|
|
135
|
+
/** Full-image dimensions — required so the outer rect covers everything. */
|
|
136
|
+
imageWidth: number;
|
|
137
|
+
imageHeight: number;
|
|
138
|
+
/** CSS colour for the dim layer. Default `rgba(0,0,0,0.5)`. */
|
|
139
|
+
dimColor?: string;
|
|
140
|
+
};
|
|
141
|
+
export type BboxAnnotation = BboxRectAnnotation | BboxCircleAnnotation | BboxArrowAnnotation | BboxTextAnnotation | BboxCalloutAnnotation | BboxNumberedBadgeAnnotation | BboxFreehandAnnotation | BboxFocusMaskAnnotation | RawAnnotation;
|
|
102
142
|
export type RedactStyle = "solid" | "mosaic" | "blur";
|
|
103
143
|
export interface BboxRedactRegion {
|
|
104
144
|
bbox: BBox;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "Burn" an editable PNG into a flat one — drop the editor's
|
|
3
|
+
* editable layer (Adobe XMP iTXt + custom `svGo` chunk) while
|
|
4
|
+
* keeping the visible bitmap byte-identical.
|
|
5
|
+
*
|
|
6
|
+
* The editable PNG format embeds:
|
|
7
|
+
*
|
|
8
|
+
* - **visible bytes** — the annotated raster the user sees.
|
|
9
|
+
* - **Adobe XMP iTXt chunk** — `<annot:annotations>` (the SVG
|
|
10
|
+
* layer) + `<annot:tags>` (provenance) for re-edit.
|
|
11
|
+
* - **custom `svGo` chunk** — the original un-annotated capture
|
|
12
|
+
* for re-edit.
|
|
13
|
+
*
|
|
14
|
+
* Flattening strips the iTXt + svGo chunks. The result is a
|
|
15
|
+
* regular PNG with the same visible pixels but no recoverable
|
|
16
|
+
* original / no SVG layer. Re-opening in the editor shows the
|
|
17
|
+
* annotated capture as a non-editable bitmap;
|
|
18
|
+
* `readEditablePngBytes` returns `null`.
|
|
19
|
+
*
|
|
20
|
+
* Use cases:
|
|
21
|
+
*
|
|
22
|
+
* - **Publish-flat** — editor session → distribution-ready
|
|
23
|
+
* PNG; the editable layer is dead weight for downstream
|
|
24
|
+
* consumers (Slack drop, third-party viewers).
|
|
25
|
+
* - **File size** — editable PNG roughly doubles in bytes
|
|
26
|
+
* (original + SVG); flattening drops the overhead.
|
|
27
|
+
* - **Privacy hardening** — `burnRedactions` is the strong
|
|
28
|
+
* version for *redact* regions; flattening drops the
|
|
29
|
+
* recoverable original entirely for *all* annotations,
|
|
30
|
+
* including non-redact ones whose annotated visual the
|
|
31
|
+
* publisher wants to keep but whose original capture
|
|
32
|
+
* they don't want shippable.
|
|
33
|
+
*
|
|
34
|
+
* Pure data — no DOM, no canvas. Returns input bytes unchanged
|
|
35
|
+
* when no editable-layer chunks are present (idempotent).
|
|
36
|
+
*/
|
|
37
|
+
export declare function flattenEditablePng(pngBytes: Uint8Array): Uint8Array;
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,11 @@ export { type Annotator, type AnnotatorInput, type AnnotatorOptions, createAnnot
|
|
|
2
2
|
export { decodeAndEncodeImage, encodeRgba } from './encode/encode.js';
|
|
3
3
|
export { type BrowserEncodeResult, computeResizeTarget, DEFAULT_ENCODE_OPTIONS, type EncodeFormat, type EncodeOptions, type EncodeResult, SAVE_SIZE_LABEL, SAVE_SIZE_MAX_WIDTH, type SaveSizePreset, } from './encode/options.js';
|
|
4
4
|
export { isPhotoHeavy } from './encode/quantize.js';
|
|
5
|
+
export { burnRedactions, burnRegions, type RedactRegion } from './redact-burn.js';
|
|
6
|
+
export { type DiffOptions, type DiffResult, DimensionMismatchError, diffScreenshots, } from './diff.js';
|
|
7
|
+
export { aggregateDiffRegions } from './diff-aggregate.js';
|
|
8
|
+
export { flattenEditablePng } from './flatten-editable-png.js';
|
|
5
9
|
export { BBOX_ANNOTATION_SCHEMA, BBOX_REDACT_REGION_SCHEMA, SHARED_DEFS, } from './dsl/schema.js';
|
|
6
10
|
export { type ArrowOptions, arrowBetween, type BoundingBox, type RectOptions, rectForBoundingBox, type TextOptions, textAt, } from './dsl/svg-primitives.js';
|
|
7
11
|
export { bboxAnnotationsToSvg } from './dsl/to-svg.js';
|
|
8
|
-
export type { AnnotationStyle, BadgePlacement, BBox, BboxAnnotation, BboxArrowAnnotation, BboxCalloutAnnotation, BboxCircleAnnotation, BboxNumberedBadgeAnnotation, BboxRectAnnotation, BboxRedactRegion, BboxTextAnnotation, Intent, Point, RawAnnotation, RedactStyle, } from './dsl/types.js';
|
|
12
|
+
export type { AnnotationStyle, BadgePlacement, BBox, BboxAnnotation, BboxArrowAnnotation, BboxCalloutAnnotation, BboxCircleAnnotation, BboxFocusMaskAnnotation, BboxFreehandAnnotation, BboxNumberedBadgeAnnotation, BboxRectAnnotation, BboxRedactRegion, BboxTextAnnotation, Intent, Point, RawAnnotation, RedactStyle, } from './dsl/types.js';
|