@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 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
@@ -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` written into
26
- * the embedded XMP for provenance / debugging.
25
+ * {@link AnnotatorInput} plus optional opaque kv `tags` and the
26
+ * schema-2.0 provenance fields written into the embedded XMP.
27
27
  *
28
- * Soft-convention key names (not validated by the writer; documented
29
- * in `@ingcreators/annot-core/xmp-bytes`'s `WELL_KNOWN_TAG_KEYS`):
30
- * - `source` what produced the PNG (`"docs-tour"`, `"playwright-fixture"`, `"annot-mcp"`).
31
- * - `screen` for living-product-docs, the `<Screen id>` value.
32
- * - `capturedAt` ISO timestamp.
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 {
@@ -0,0 +1,6 @@
1
+ import { BBox } from './dsl/types.js';
2
+ /**
3
+ * Walk a pixelmatch diff mask and return one bbox per contiguous
4
+ * changed region. 4-connected adjacency.
5
+ */
6
+ export declare function aggregateDiffRegions(diffRgba: Uint8Array, width: number, height: number): BBox[];
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>;
@@ -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[];
@@ -98,7 +98,47 @@ export interface RawAnnotation {
98
98
  type: "raw";
99
99
  svgFragment: string;
100
100
  }
101
- export type BboxAnnotation = BboxRectAnnotation | BboxCircleAnnotation | BboxArrowAnnotation | BboxTextAnnotation | BboxCalloutAnnotation | BboxNumberedBadgeAnnotation | RawAnnotation;
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';