@ingcreators/annot-annotator 0.1.0 → 0.3.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 +45 -0
- package/README.md +11 -8
- package/dist/annotator.d.ts +31 -7
- package/dist/dsl/schema.d.ts +239 -0
- package/dist/dsl/svg-primitives.d.ts +32 -0
- package/dist/dsl/to-svg.d.ts +11 -0
- package/dist/dsl/types.d.ts +66 -0
- package/dist/encode/encode.d.ts +37 -0
- package/dist/encode/options.d.ts +19 -0
- package/dist/encode/quantize.d.ts +23 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +530 -80
- package/package.json +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# @ingcreators/annot-annotator
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 86d0853: `Annotator.toEncoded(input, encodeOptions?)` — new async method that combines rasterise + smart-encode in one step. Achieves feature parity with Chrome extension's "Save size" + "Format smart" capture options for the manual-creation / agent use cases.
|
|
8
|
+
|
|
9
|
+
**EncodeOptions** (re-exported from `@ingcreators/annot-core/encode/options`):
|
|
10
|
+
- `format: "smart" | "png" | "jpeg"` — Smart picks PNG-8 for UI-heavy content (via libimagequant) and JPEG / PNG-32 for photo-heavy content per `smartFallback`. PNG / JPEG paths skip the smart heuristic.
|
|
11
|
+
- `saveSizePreset: "light" | "standard" | "highQuality" | "original"` — Max-width caps at 1280 / 1920 / 2560 / no-resize. Aspect-preserving; never upscales.
|
|
12
|
+
- `smartFallback: "png" | "jpeg"` — Photo-heavy fallback format.
|
|
13
|
+
- `smartColorThreshold: number` — Unique-colour count threshold (default 15000). Above this the image is treated as photo-heavy.
|
|
14
|
+
- `jpegPercent: number` — JPEG quality 60–100 (default 92).
|
|
15
|
+
|
|
16
|
+
Returns `{ bytes, chosen, reason?, width, height }` so callers can log which format was actually chosen (`"png-8"` / `"photo-fallback-jpeg"` / `"imagequant-missing"` etc.).
|
|
17
|
+
|
|
18
|
+
Standalone `encodeRgba(rgba, width, height, options)` also exported for callers who already have raw RGBA bytes (e.g. from a Playwright screenshot fed through their own canvas).
|
|
19
|
+
|
|
20
|
+
**New runtime dependencies:**
|
|
21
|
+
- `@napi-rs/canvas` (regular `dependencies`) — native canvas binding for PNG / JPEG encoding + resize. ~20 MB platform-matched binary at install time.
|
|
22
|
+
- `@ingcreators/annot-imagequant` (regular `dependencies`) — GPL-3.0 WASM wrapper around libimagequant for PNG-8 quantization. Loaded via dynamic import; consumers who explicitly uninstall the package to avoid the GPL inclusion get a graceful fallback to PNG-32 (`reason: "imagequant-missing"`).
|
|
23
|
+
- `pako` (regular `dependencies`) — pulled in transitively via the PNG-8 encoder; declared explicitly so consumer installs are deterministic.
|
|
24
|
+
|
|
25
|
+
The existing `toPng()` and `toSvg()` methods are unchanged — `toEncoded()` is purely additive.
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- Updated dependencies [adae49d]
|
|
30
|
+
- @ingcreators/annot-imagequant@0.1.0
|
|
31
|
+
|
|
32
|
+
## 0.2.0
|
|
33
|
+
|
|
34
|
+
### Minor Changes
|
|
35
|
+
|
|
36
|
+
- 92378f9: Public DSL surface (since 0.2.0). The annotation DSL that was previously private to `@ingcreators/annot-mcp` now lives on the annotator package so any Annot consumer (test runtimes, AI agents, plugin authors) can use the same vocabulary.
|
|
37
|
+
|
|
38
|
+
New exports:
|
|
39
|
+
- Types: `BBox`, `Point`, `Intent`, `AnnotationStyle`, `BboxAnnotation` (`rect` / `circle` / `arrow` / `text` / `callout` / `raw`), `RawAnnotation`, `BboxRedactRegion`, `RedactStyle`.
|
|
40
|
+
- Converter: `bboxAnnotationsToSvg(annotations)` returns the SVG fragment string `createAnnotator(...).toPng({ annotationsSvg })` accepts.
|
|
41
|
+
- SVG primitives: `rectForBoundingBox`, `arrowBetween`, `textAt`, plus `BoundingBox` / `RectOptions` / `ArrowOptions` / `TextOptions`.
|
|
42
|
+
- JSON Schemas: `SHARED_DEFS`, `BBOX_ANNOTATION_SCHEMA`, `BBOX_REDACT_REGION_SCHEMA` (drop into MCP tool `inputSchema` `$defs` blocks).
|
|
43
|
+
|
|
44
|
+
The `intent` shorthand (`"info"` / `"warning"` / `"error"` / `"success"` / `"neutral"`) resolves to the Annot design system's semantic colour tokens automatically — no more thinking in raw hex values.
|
|
45
|
+
|
|
46
|
+
Marker id prefix in `arrowBetween` changed from `annot-pw-arrow-N` (previous helper in `@ingcreators/annot-playwright`) / `annot-mcp-arrow-N` (previous helper in `@ingcreators/annot-mcp`) to the package-neutral `annot-arrow-N`. Snapshot-on-SVG tests should expect this minor cosmetic delta.
|
|
47
|
+
|
|
3
48
|
## 0.1.0
|
|
4
49
|
|
|
5
50
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
# `@ingcreators/annot-annotator`
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@ingcreators/annot-annotator)
|
|
4
|
+
[](https://github.com/ingcreators/annot/blob/main/LICENSE)
|
|
5
|
+
|
|
3
6
|
Headless annotator — produce annotated screenshots from Node without
|
|
4
7
|
a browser. Reads an `ImageRecord`-shaped input (base image + saved
|
|
5
8
|
annotations SVG) and emits a PNG or the merged SVG.
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
>
|
|
11
|
-
> Companion package: `@ingcreators/annot-playwright` (Phase 2 of the
|
|
12
|
-
> same track) — a Playwright fixture composing this annotator into
|
|
13
|
-
> idiomatic `test.extend({ annotator })` form.
|
|
10
|
+
Companion package: [`@ingcreators/annot-playwright`](https://www.npmjs.com/package/@ingcreators/annot-playwright) —
|
|
11
|
+
a Playwright fixture composing this annotator into idiomatic
|
|
12
|
+
`test.extend({ annotator })` form.
|
|
14
13
|
|
|
15
|
-
## Install
|
|
14
|
+
## Install
|
|
16
15
|
|
|
17
16
|
```sh
|
|
18
17
|
npm install @ingcreators/annot-annotator
|
|
@@ -20,6 +19,10 @@ npm install @ingcreators/annot-annotator
|
|
|
20
19
|
pnpm add @ingcreators/annot-annotator
|
|
21
20
|
```
|
|
22
21
|
|
|
22
|
+
Peer runtime requirements: Node 20+. The package depends on
|
|
23
|
+
`@resvg/resvg-js` (native binding via `@napi-rs`) — npm install
|
|
24
|
+
fetches the platform-matched prebuild automatically.
|
|
25
|
+
|
|
23
26
|
## Usage
|
|
24
27
|
|
|
25
28
|
```ts
|
package/dist/annotator.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { EncodeOptions } from '@ingcreators/annot-core/encode/options';
|
|
2
|
+
import { EncodeResult } from './encode/options.js';
|
|
1
3
|
/**
|
|
2
4
|
* Structural shape the annotator accepts. A real `ImageRecord`
|
|
3
5
|
* from `@ingcreators/annot-core/storage` satisfies this — the
|
|
@@ -47,16 +49,23 @@ export interface AnnotatorOptions {
|
|
|
47
49
|
defaultFontFamily?: string;
|
|
48
50
|
}
|
|
49
51
|
/**
|
|
50
|
-
* Annotator instance returned by {@link createAnnotator}.
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
52
|
+
* Annotator instance returned by {@link createAnnotator}.
|
|
53
|
+
*
|
|
54
|
+
* - {@link toPng} synchronous rasterise → PNG-32 bytes.
|
|
55
|
+
* - {@link toSvg} build the rasterise-ready SVG string (no
|
|
56
|
+
* rasterisation).
|
|
57
|
+
* - {@link toEncoded} (since 0.3.0) async rasterise + smart
|
|
58
|
+
* encode pipeline — `saveSizePreset` resize,
|
|
59
|
+
* `format: "smart" | "png" | "jpeg"` decision
|
|
60
|
+
* tree, PNG-8 quantization via the optional
|
|
61
|
+
* `@ingcreators/annot-imagequant`.
|
|
54
62
|
*/
|
|
55
63
|
export interface Annotator {
|
|
56
64
|
/**
|
|
57
|
-
* Rasterise the input to a PNG byte array.
|
|
58
|
-
*
|
|
59
|
-
*
|
|
65
|
+
* Rasterise the input to a PNG-32 byte array. Synchronous, no
|
|
66
|
+
* resize, no smart format selection — the most direct path.
|
|
67
|
+
* Use {@link toEncoded} when you need `saveSize` / format
|
|
68
|
+
* decisions.
|
|
60
69
|
*/
|
|
61
70
|
toPng(input: AnnotatorInput): Uint8Array;
|
|
62
71
|
/**
|
|
@@ -65,6 +74,21 @@ export interface Annotator {
|
|
|
65
74
|
* the caller can feed to any other SVG tool.
|
|
66
75
|
*/
|
|
67
76
|
toSvg(input: AnnotatorInput): string;
|
|
77
|
+
/**
|
|
78
|
+
* Rasterise + encode in one step. Honours `saveSizePreset`
|
|
79
|
+
* (max-width resize), `format` (`"smart"` / `"png"` /
|
|
80
|
+
* `"jpeg"`), and `jpegPercent`. Returns the encoded bytes plus
|
|
81
|
+
* a metadata record so the caller can log which format was
|
|
82
|
+
* actually picked.
|
|
83
|
+
*
|
|
84
|
+
* Smart mode requires `@ingcreators/annot-imagequant` to be
|
|
85
|
+
* available at runtime for PNG-8 output; the WASM ships as a
|
|
86
|
+
* regular `dependencies` entry but is dynamic-imported, so a
|
|
87
|
+
* consumer who explicitly uninstalls the package (to avoid
|
|
88
|
+
* its GPL-3.0 license) gets a graceful fallback to PNG-32
|
|
89
|
+
* with `reason: "imagequant-missing"`.
|
|
90
|
+
*/
|
|
91
|
+
toEncoded(input: AnnotatorInput, encodeOptions?: EncodeOptions): Promise<EncodeResult>;
|
|
68
92
|
}
|
|
69
93
|
/**
|
|
70
94
|
* Construct a headless annotator. Options are forwarded to the
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/** Common subschemas to spread into a tool's `$defs` block. */
|
|
2
|
+
export declare const SHARED_DEFS: {
|
|
3
|
+
BBox: {
|
|
4
|
+
type: string;
|
|
5
|
+
required: string[];
|
|
6
|
+
additionalProperties: boolean;
|
|
7
|
+
properties: {
|
|
8
|
+
x: {
|
|
9
|
+
type: string;
|
|
10
|
+
};
|
|
11
|
+
y: {
|
|
12
|
+
type: string;
|
|
13
|
+
};
|
|
14
|
+
width: {
|
|
15
|
+
type: string;
|
|
16
|
+
minimum: number;
|
|
17
|
+
};
|
|
18
|
+
height: {
|
|
19
|
+
type: string;
|
|
20
|
+
minimum: number;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
Point: {
|
|
25
|
+
type: string;
|
|
26
|
+
required: string[];
|
|
27
|
+
additionalProperties: boolean;
|
|
28
|
+
properties: {
|
|
29
|
+
x: {
|
|
30
|
+
type: string;
|
|
31
|
+
};
|
|
32
|
+
y: {
|
|
33
|
+
type: string;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
Intent: {
|
|
38
|
+
type: string;
|
|
39
|
+
enum: string[];
|
|
40
|
+
};
|
|
41
|
+
AnnotationStyle: {
|
|
42
|
+
type: string;
|
|
43
|
+
additionalProperties: boolean;
|
|
44
|
+
properties: {
|
|
45
|
+
intent: {
|
|
46
|
+
$ref: string;
|
|
47
|
+
};
|
|
48
|
+
stroke: {
|
|
49
|
+
type: string;
|
|
50
|
+
};
|
|
51
|
+
strokeWidth: {
|
|
52
|
+
type: string;
|
|
53
|
+
minimum: number;
|
|
54
|
+
};
|
|
55
|
+
fill: {
|
|
56
|
+
type: string;
|
|
57
|
+
};
|
|
58
|
+
color: {
|
|
59
|
+
type: string;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
export declare const BBOX_ANNOTATION_SCHEMA: {
|
|
65
|
+
oneOf: ({
|
|
66
|
+
type: string;
|
|
67
|
+
required: string[];
|
|
68
|
+
additionalProperties: boolean;
|
|
69
|
+
properties: {
|
|
70
|
+
type: {
|
|
71
|
+
const: string;
|
|
72
|
+
};
|
|
73
|
+
bbox: {
|
|
74
|
+
$ref: string;
|
|
75
|
+
};
|
|
76
|
+
intent: {
|
|
77
|
+
$ref: string;
|
|
78
|
+
};
|
|
79
|
+
stroke: {
|
|
80
|
+
type: string;
|
|
81
|
+
};
|
|
82
|
+
strokeWidth: {
|
|
83
|
+
type: string;
|
|
84
|
+
minimum: number;
|
|
85
|
+
};
|
|
86
|
+
fill: {
|
|
87
|
+
type: string;
|
|
88
|
+
};
|
|
89
|
+
color: {
|
|
90
|
+
type: string;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
} | {
|
|
94
|
+
type: string;
|
|
95
|
+
required: string[];
|
|
96
|
+
additionalProperties: boolean;
|
|
97
|
+
properties: {
|
|
98
|
+
type: {
|
|
99
|
+
const: string;
|
|
100
|
+
};
|
|
101
|
+
center: {
|
|
102
|
+
$ref: string;
|
|
103
|
+
};
|
|
104
|
+
radius: {
|
|
105
|
+
type: string;
|
|
106
|
+
minimum: number;
|
|
107
|
+
};
|
|
108
|
+
intent: {
|
|
109
|
+
$ref: string;
|
|
110
|
+
};
|
|
111
|
+
stroke: {
|
|
112
|
+
type: string;
|
|
113
|
+
};
|
|
114
|
+
strokeWidth: {
|
|
115
|
+
type: string;
|
|
116
|
+
minimum: number;
|
|
117
|
+
};
|
|
118
|
+
fill: {
|
|
119
|
+
type: string;
|
|
120
|
+
};
|
|
121
|
+
color: {
|
|
122
|
+
type: string;
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
} | {
|
|
126
|
+
type: string;
|
|
127
|
+
required: string[];
|
|
128
|
+
additionalProperties: boolean;
|
|
129
|
+
properties: {
|
|
130
|
+
type: {
|
|
131
|
+
const: string;
|
|
132
|
+
};
|
|
133
|
+
from: {
|
|
134
|
+
$ref: string;
|
|
135
|
+
};
|
|
136
|
+
to: {
|
|
137
|
+
$ref: string;
|
|
138
|
+
};
|
|
139
|
+
intent: {
|
|
140
|
+
$ref: string;
|
|
141
|
+
};
|
|
142
|
+
stroke: {
|
|
143
|
+
type: string;
|
|
144
|
+
};
|
|
145
|
+
strokeWidth: {
|
|
146
|
+
type: string;
|
|
147
|
+
minimum: number;
|
|
148
|
+
};
|
|
149
|
+
color: {
|
|
150
|
+
type: string;
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
} | {
|
|
154
|
+
type: string;
|
|
155
|
+
required: string[];
|
|
156
|
+
additionalProperties: boolean;
|
|
157
|
+
properties: {
|
|
158
|
+
type: {
|
|
159
|
+
const: string;
|
|
160
|
+
};
|
|
161
|
+
at: {
|
|
162
|
+
$ref: string;
|
|
163
|
+
};
|
|
164
|
+
content: {
|
|
165
|
+
type: string;
|
|
166
|
+
};
|
|
167
|
+
fontSize: {
|
|
168
|
+
type: string;
|
|
169
|
+
minimum: number;
|
|
170
|
+
};
|
|
171
|
+
anchor: {
|
|
172
|
+
type: string;
|
|
173
|
+
enum: string[];
|
|
174
|
+
};
|
|
175
|
+
intent: {
|
|
176
|
+
$ref: string;
|
|
177
|
+
};
|
|
178
|
+
color: {
|
|
179
|
+
type: string;
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
} | {
|
|
183
|
+
type: string;
|
|
184
|
+
required: string[];
|
|
185
|
+
additionalProperties: boolean;
|
|
186
|
+
properties: {
|
|
187
|
+
type: {
|
|
188
|
+
const: string;
|
|
189
|
+
};
|
|
190
|
+
at: {
|
|
191
|
+
$ref: string;
|
|
192
|
+
};
|
|
193
|
+
targetBbox: {
|
|
194
|
+
$ref: string;
|
|
195
|
+
};
|
|
196
|
+
content: {
|
|
197
|
+
type: string;
|
|
198
|
+
};
|
|
199
|
+
intent: {
|
|
200
|
+
$ref: string;
|
|
201
|
+
};
|
|
202
|
+
stroke: {
|
|
203
|
+
type: string;
|
|
204
|
+
};
|
|
205
|
+
color: {
|
|
206
|
+
type: string;
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
} | {
|
|
210
|
+
type: string;
|
|
211
|
+
required: string[];
|
|
212
|
+
additionalProperties: boolean;
|
|
213
|
+
properties: {
|
|
214
|
+
type: {
|
|
215
|
+
const: string;
|
|
216
|
+
};
|
|
217
|
+
svgFragment: {
|
|
218
|
+
type: string;
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
})[];
|
|
222
|
+
};
|
|
223
|
+
export declare const BBOX_REDACT_REGION_SCHEMA: {
|
|
224
|
+
type: string;
|
|
225
|
+
required: string[];
|
|
226
|
+
additionalProperties: boolean;
|
|
227
|
+
properties: {
|
|
228
|
+
bbox: {
|
|
229
|
+
$ref: string;
|
|
230
|
+
};
|
|
231
|
+
style: {
|
|
232
|
+
type: string;
|
|
233
|
+
enum: string[];
|
|
234
|
+
};
|
|
235
|
+
color: {
|
|
236
|
+
type: string;
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { BBox, Point } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Back-compat alias retained because the original
|
|
4
|
+
* `@ingcreators/annot-playwright` helpers exposed this name.
|
|
5
|
+
* Prefer importing `BBox` from `@ingcreators/annot-annotator`
|
|
6
|
+
* directly.
|
|
7
|
+
*/
|
|
8
|
+
export type BoundingBox = BBox;
|
|
9
|
+
export interface RectOptions {
|
|
10
|
+
stroke?: string;
|
|
11
|
+
strokeWidth?: number;
|
|
12
|
+
fill?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ArrowOptions {
|
|
15
|
+
color?: string;
|
|
16
|
+
strokeWidth?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface TextOptions {
|
|
19
|
+
color?: string;
|
|
20
|
+
fontSize?: number;
|
|
21
|
+
anchor?: "start" | "middle" | "end";
|
|
22
|
+
}
|
|
23
|
+
/** Outline a bounding box with an SVG `<rect>`. */
|
|
24
|
+
export declare function rectForBoundingBox(bbox: BoundingBox, opts?: RectOptions): string;
|
|
25
|
+
/**
|
|
26
|
+
* Draw an arrow from `from` to `to`. Inlines a unique-id `<marker>`
|
|
27
|
+
* definition so the helper is self-contained — call it twice and
|
|
28
|
+
* each call produces a distinct arrowhead marker.
|
|
29
|
+
*/
|
|
30
|
+
export declare function arrowBetween(from: Point, to: Point, opts?: ArrowOptions): string;
|
|
31
|
+
/** Drop a text label at a position. */
|
|
32
|
+
export declare function textAt(at: Point, content: string, opts?: TextOptions): string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BboxAnnotation } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Convert a list of bbox-flavour annotations into a single SVG
|
|
4
|
+
* fragment string. The fragment is suitable as the
|
|
5
|
+
* `annotationsSvg` payload for `createAnnotator(...).toPng()` /
|
|
6
|
+
* `.toSvg()`.
|
|
7
|
+
*
|
|
8
|
+
* Emits bare fragments (no outer `<svg>` wrapper); the annotator's
|
|
9
|
+
* sanitiser wraps them at rasterise time.
|
|
10
|
+
*/
|
|
11
|
+
export declare function bboxAnnotationsToSvg(annotations: readonly BboxAnnotation[]): string;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** Axis-aligned bounding box. Coordinates are page pixels. */
|
|
2
|
+
export interface BBox {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
}
|
|
8
|
+
/** Point in page pixels. */
|
|
9
|
+
export interface Point {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Semantic colour shorthand. Resolves to the Annot design system's
|
|
15
|
+
* matching token (e.g. `intent: "error"` → `--annot-color-error`).
|
|
16
|
+
* Explicit `stroke` / `color` / `fill` on an annotation override
|
|
17
|
+
* the intent-derived defaults.
|
|
18
|
+
*/
|
|
19
|
+
export type Intent = "info" | "warning" | "error" | "success" | "neutral";
|
|
20
|
+
/** Style overrides shared across most annotation shapes. */
|
|
21
|
+
export interface AnnotationStyle {
|
|
22
|
+
intent?: Intent;
|
|
23
|
+
stroke?: string;
|
|
24
|
+
strokeWidth?: number;
|
|
25
|
+
fill?: string;
|
|
26
|
+
color?: string;
|
|
27
|
+
}
|
|
28
|
+
export type BboxRectAnnotation = AnnotationStyle & {
|
|
29
|
+
type: "rect";
|
|
30
|
+
bbox: BBox;
|
|
31
|
+
};
|
|
32
|
+
export type BboxCircleAnnotation = AnnotationStyle & {
|
|
33
|
+
type: "circle";
|
|
34
|
+
center: Point;
|
|
35
|
+
radius: number;
|
|
36
|
+
};
|
|
37
|
+
export type BboxArrowAnnotation = AnnotationStyle & {
|
|
38
|
+
type: "arrow";
|
|
39
|
+
from: Point;
|
|
40
|
+
to: Point;
|
|
41
|
+
};
|
|
42
|
+
export type BboxTextAnnotation = AnnotationStyle & {
|
|
43
|
+
type: "text";
|
|
44
|
+
at: Point;
|
|
45
|
+
content: string;
|
|
46
|
+
fontSize?: number;
|
|
47
|
+
anchor?: "start" | "middle" | "end";
|
|
48
|
+
};
|
|
49
|
+
export type BboxCalloutAnnotation = AnnotationStyle & {
|
|
50
|
+
type: "callout";
|
|
51
|
+
at: Point;
|
|
52
|
+
targetBbox: BBox;
|
|
53
|
+
content: string;
|
|
54
|
+
};
|
|
55
|
+
export interface RawAnnotation {
|
|
56
|
+
type: "raw";
|
|
57
|
+
svgFragment: string;
|
|
58
|
+
}
|
|
59
|
+
export type BboxAnnotation = BboxRectAnnotation | BboxCircleAnnotation | BboxArrowAnnotation | BboxTextAnnotation | BboxCalloutAnnotation | RawAnnotation;
|
|
60
|
+
export type RedactStyle = "solid" | "mosaic" | "blur";
|
|
61
|
+
export interface BboxRedactRegion {
|
|
62
|
+
bbox: BBox;
|
|
63
|
+
style?: RedactStyle;
|
|
64
|
+
/** CSS colour, applied only when `style` is `"solid"`. */
|
|
65
|
+
color?: string;
|
|
66
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { EncodeOptions } from '@ingcreators/annot-core/encode/options';
|
|
2
|
+
import { EncodeResult } from './options.js';
|
|
3
|
+
/**
|
|
4
|
+
* Encode raw RGBA pixels per the requested {@link EncodeOptions}.
|
|
5
|
+
*
|
|
6
|
+
* Algorithm (mirrors the browser-side pipeline):
|
|
7
|
+
*
|
|
8
|
+
* 1. Resize the source via `saveSizePreset` (aspect-preserving;
|
|
9
|
+
* never upscale).
|
|
10
|
+
* 2. Branch on `format`:
|
|
11
|
+
* - `"png"` → PNG-32 encode (lossless).
|
|
12
|
+
* - `"jpeg"` → JPEG encode at `jpegPercent`.
|
|
13
|
+
* - `"smart"`:
|
|
14
|
+
* - If pixel count > `MAX_SMART_PIXELS`, return PNG-32
|
|
15
|
+
* with `reason: "too-large-for-png8"`.
|
|
16
|
+
* - Sample unique-colour count via {@link isPhotoHeavy}.
|
|
17
|
+
* If photo-heavy, emit `smartFallback` (PNG-32 or
|
|
18
|
+
* JPEG) with reason `"photo-fallback-*"`.
|
|
19
|
+
* - Otherwise quantize to ≤256 colours via libimagequant
|
|
20
|
+
* (if installed) and emit PNG-8 with reason `"png-8"`.
|
|
21
|
+
* When the optional imagequant WASM isn't installed,
|
|
22
|
+
* fall back to PNG-32 with `reason: "imagequant-missing"`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function encodeRgba(rgba: Uint8Array, width: number, height: number, options?: EncodeOptions): Promise<EncodeResult>;
|
|
25
|
+
/**
|
|
26
|
+
* Decode a PNG / JPEG byte stream into RGBA and re-encode it
|
|
27
|
+
* through {@link encodeRgba}. Useful for downstream pipelines
|
|
28
|
+
* that emit a PNG first (e.g. the redact-burn output or a
|
|
29
|
+
* Playwright `page.screenshot()` capture) and want to apply
|
|
30
|
+
* `saveSize` / `format` after the fact.
|
|
31
|
+
*
|
|
32
|
+
* The decode goes through `@napi-rs/canvas`'s `loadImage`, so
|
|
33
|
+
* any format the underlying Skia decoder understands (PNG /
|
|
34
|
+
* JPEG / WebP / AVIF on the canvas's supported list) flows
|
|
35
|
+
* through transparently.
|
|
36
|
+
*/
|
|
37
|
+
export declare function decodeAndEncodeImage(imageBytes: Uint8Array, options?: EncodeOptions): Promise<EncodeResult>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type { EncodeFormat, EncodeOptions, EncodeResult as BrowserEncodeResult, SaveSizePreset, } from '@ingcreators/annot-core/encode/options';
|
|
2
|
+
export { computeResizeTarget, DEFAULT_ENCODE_OPTIONS, SAVE_SIZE_LABEL, SAVE_SIZE_MAX_WIDTH, } from '@ingcreators/annot-core/encode/options';
|
|
3
|
+
/**
|
|
4
|
+
* Node-side encode result. Returns raw bytes (vs the browser-side
|
|
5
|
+
* counterpart returning a `data:` URL string) — agents and test
|
|
6
|
+
* runtimes typically want bytes to attach to GitHub issues / test
|
|
7
|
+
* reports / write to disk.
|
|
8
|
+
*/
|
|
9
|
+
export interface EncodeResult {
|
|
10
|
+
/** Encoded image bytes (PNG-8 / PNG-32 / JPEG depending on `chosen`). */
|
|
11
|
+
bytes: Uint8Array;
|
|
12
|
+
/** Actual format chosen (may differ from requested in smart mode). */
|
|
13
|
+
chosen: "png" | "jpeg";
|
|
14
|
+
/** Human-readable note (`"png-8"` / `"photo-fallback-jpeg"` / …). */
|
|
15
|
+
reason?: string;
|
|
16
|
+
/** Final pixel dimensions after `saveSizePreset` resize. */
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quantize RGBA pixels to ≤256 colours via libimagequant and emit
|
|
3
|
+
* a PNG-8 file. Returns the encoded bytes, OR `null` if the
|
|
4
|
+
* optional imagequant module isn't installed.
|
|
5
|
+
*
|
|
6
|
+
* The caller is responsible for falling back to a non-PNG-8 path
|
|
7
|
+
* (PNG-32 or JPEG) when this returns `null`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function quantizeRgbaToPng8(rgba: Uint8Array, width: number, height: number): Promise<Uint8Array | null>;
|
|
10
|
+
/**
|
|
11
|
+
* Heuristic: does this image look photo-heavy?
|
|
12
|
+
*
|
|
13
|
+
* Samples ~50,000 pixels from the RGBA buffer (stride-walks rather
|
|
14
|
+
* than reading every pixel — sufficient for the bimodal "UI vs
|
|
15
|
+
* photo" decision). Returns `true` if the unique-color count
|
|
16
|
+
* exceeds `threshold`. Typical UI screenshots return <5,000 unique
|
|
17
|
+
* colours; pages with rich photography return >20,000.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isPhotoHeavy(rgba: Uint8Array, threshold: number): boolean;
|
|
20
|
+
/** Whether the optional imagequant WASM is available at runtime.
|
|
21
|
+
* Useful for callers that want to decide ahead of time whether to
|
|
22
|
+
* request `format: "smart"`. */
|
|
23
|
+
export declare function isImagequantAvailable(): Promise<boolean>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
1
|
export { type Annotator, type AnnotatorInput, type AnnotatorOptions, createAnnotator, } from './annotator.js';
|
|
2
|
+
export { decodeAndEncodeImage, encodeRgba } from './encode/encode.js';
|
|
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
|
+
export { isImagequantAvailable, isPhotoHeavy } from './encode/quantize.js';
|
|
5
|
+
export { BBOX_ANNOTATION_SCHEMA, BBOX_REDACT_REGION_SCHEMA, SHARED_DEFS, } from './dsl/schema.js';
|
|
6
|
+
export { type ArrowOptions, arrowBetween, type BoundingBox, type RectOptions, rectForBoundingBox, type TextOptions, textAt, } from './dsl/svg-primitives.js';
|
|
7
|
+
export { bboxAnnotationsToSvg } from './dsl/to-svg.js';
|
|
8
|
+
export type { AnnotationStyle, BBox, BboxAnnotation, BboxArrowAnnotation, BboxCalloutAnnotation, BboxCircleAnnotation, BboxRectAnnotation, BboxRedactRegion, BboxTextAnnotation, Intent, Point, RawAnnotation, RedactStyle, } from './dsl/types.js';
|
package/dist/index.js
CHANGED
|
@@ -1,80 +1,530 @@
|
|
|
1
|
-
import { Resvg as
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
1
|
+
import { Resvg as P } from "@resvg/resvg-js";
|
|
2
|
+
import { loadImage as R, createCanvas as m } from "@napi-rs/canvas";
|
|
3
|
+
import { deflate as L } from "pako";
|
|
4
|
+
import { DOMParser as j, XMLSerializer as q } from "@xmldom/xmldom";
|
|
5
|
+
const W = "1", M = "data-annot-version", X = {
|
|
6
|
+
light: 1280,
|
|
7
|
+
standard: 1920,
|
|
8
|
+
highQuality: 2560,
|
|
9
|
+
original: null
|
|
10
|
+
}, Se = {
|
|
11
|
+
light: "Light (1280px)",
|
|
12
|
+
standard: "Standard (1920px)",
|
|
13
|
+
highQuality: "High Quality (2560px)",
|
|
14
|
+
original: "Original"
|
|
15
|
+
}, C = {
|
|
16
|
+
format: "smart",
|
|
17
|
+
smartFallback: "png",
|
|
18
|
+
smartColorThreshold: 15e3,
|
|
19
|
+
jpegPercent: 92,
|
|
20
|
+
saveSizePreset: "standard"
|
|
21
|
+
};
|
|
22
|
+
function V(e, t, r) {
|
|
23
|
+
const n = X[r];
|
|
24
|
+
if (n === null || e <= n)
|
|
25
|
+
return { width: e, height: t, scaled: !1 };
|
|
26
|
+
const o = n / e;
|
|
27
|
+
return {
|
|
28
|
+
width: n,
|
|
29
|
+
height: Math.max(1, Math.round(t * o)),
|
|
30
|
+
scaled: !0
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const G = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]), Q = (() => {
|
|
34
|
+
const e = new Uint32Array(256);
|
|
35
|
+
for (let t = 0; t < 256; t++) {
|
|
36
|
+
let r = t;
|
|
37
|
+
for (let n = 0; n < 8; n++)
|
|
38
|
+
r = r & 1 ? 3988292384 ^ r >>> 1 : r >>> 1;
|
|
39
|
+
e[t] = r >>> 0;
|
|
40
|
+
}
|
|
41
|
+
return e;
|
|
42
|
+
})();
|
|
43
|
+
function H(e) {
|
|
44
|
+
let t = 4294967295;
|
|
45
|
+
for (let r = 0; r < e.length; r++)
|
|
46
|
+
t = Q[(t ^ e[r]) & 255] ^ t >>> 8;
|
|
47
|
+
return (t ^ 4294967295) >>> 0;
|
|
48
|
+
}
|
|
49
|
+
function h(e, t) {
|
|
50
|
+
const r = new Uint8Array(12 + t.length), n = new DataView(r.buffer);
|
|
51
|
+
n.setUint32(0, t.length, !1);
|
|
52
|
+
for (let i = 0; i < 4; i++) r[4 + i] = e.charCodeAt(i);
|
|
53
|
+
r.set(t, 8);
|
|
54
|
+
const o = r.subarray(4, 8 + t.length);
|
|
55
|
+
return n.setUint32(8 + t.length, H(o), !1), r;
|
|
56
|
+
}
|
|
57
|
+
function Z(e, t, r, n, o = 9) {
|
|
58
|
+
if (r <= 0 || n <= 0) throw new Error("encodePng8: invalid dimensions");
|
|
59
|
+
if (e.length % 4 !== 0)
|
|
60
|
+
throw new Error("encodePng8: palette must be RGBA bytes (multiple of 4)");
|
|
61
|
+
const i = e.length >>> 2;
|
|
62
|
+
if (i < 1 || i > 256)
|
|
63
|
+
throw new Error(`encodePng8: palette must have 1–256 colors, got ${i}`);
|
|
64
|
+
if (t.length !== r * n)
|
|
65
|
+
throw new Error(
|
|
66
|
+
`encodePng8: indices length ${t.length} != width*height ${r * n}`
|
|
67
|
+
);
|
|
68
|
+
const s = new Uint8Array(13), a = new DataView(s.buffer);
|
|
69
|
+
a.setUint32(0, r, !1), a.setUint32(4, n, !1), s[8] = 8, s[9] = 3, s[10] = 0, s[11] = 0, s[12] = 0;
|
|
70
|
+
const c = new Uint8Array(i * 3);
|
|
71
|
+
let u = !1, f = -1;
|
|
72
|
+
for (let l = 0; l < i; l++)
|
|
73
|
+
c[l * 3] = e[l * 4], c[l * 3 + 1] = e[l * 4 + 1], c[l * 3 + 2] = e[l * 4 + 2], e[l * 4 + 3] < 255 && (u = !0, f = l);
|
|
74
|
+
let g = null;
|
|
75
|
+
if (u) {
|
|
76
|
+
g = new Uint8Array(f + 1);
|
|
77
|
+
for (let l = 0; l <= f; l++) g[l] = e[l * 4 + 3];
|
|
78
|
+
}
|
|
79
|
+
const A = r + 1, x = new Uint8Array(A * n);
|
|
80
|
+
for (let l = 0; l < n; l++) {
|
|
81
|
+
const T = l * A;
|
|
82
|
+
x[T] = 0, x.set(t.subarray(l * r, l * r + r), T + 1);
|
|
83
|
+
}
|
|
84
|
+
const U = L(x, { level: o, memLevel: 9, windowBits: 15 }), d = [G, h("IHDR", s), h("PLTE", c)];
|
|
85
|
+
g && d.push(h("tRNS", g)), d.push(h("IDAT", U)), d.push(h("IEND", new Uint8Array(0)));
|
|
86
|
+
let I = 0;
|
|
87
|
+
for (const l of d) I += l.length;
|
|
88
|
+
const S = new Uint8Array(I);
|
|
89
|
+
let v = 0;
|
|
90
|
+
for (const l of d)
|
|
91
|
+
S.set(l, v), v += l.length;
|
|
92
|
+
return S;
|
|
93
|
+
}
|
|
94
|
+
let w = null;
|
|
95
|
+
async function N() {
|
|
96
|
+
return w || (w = (async () => {
|
|
97
|
+
try {
|
|
98
|
+
const e = await import("@ingcreators/annot-imagequant");
|
|
99
|
+
return await e.default(), { quantize_image: e.quantize_image };
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
})(), w);
|
|
104
|
+
}
|
|
105
|
+
async function J(e, t, r) {
|
|
106
|
+
const n = await N();
|
|
107
|
+
if (!n) return null;
|
|
108
|
+
const o = n.quantize_image(e, t, r, 256);
|
|
109
|
+
if (!(o?.palette instanceof Uint8Array) || !(o?.indices instanceof Uint8Array))
|
|
110
|
+
throw new Error("quantize_image returned an unexpected shape");
|
|
111
|
+
return Z(o.palette, o.indices, t, r, 9);
|
|
112
|
+
}
|
|
113
|
+
function K(e, t) {
|
|
114
|
+
const r = new Uint32Array(e.buffer, e.byteOffset, e.byteLength >>> 2), n = r.length;
|
|
115
|
+
if (n === 0) return !1;
|
|
116
|
+
const i = Math.max(1, Math.floor(n / 5e4)), s = /* @__PURE__ */ new Set();
|
|
117
|
+
for (let a = 0; a < n; a += i)
|
|
118
|
+
if (s.add(r[a]), s.size > t) return !0;
|
|
119
|
+
return !1;
|
|
120
|
+
}
|
|
121
|
+
async function ve() {
|
|
122
|
+
return await N() !== null;
|
|
123
|
+
}
|
|
124
|
+
const Y = 1e7;
|
|
125
|
+
async function O(e, t, r, n = C) {
|
|
126
|
+
const o = n.saveSizePreset ?? "original", i = V(t, r, o), s = i.scaled ? ee(e, t, r, i.width, i.height) : e, a = i.width, c = i.height;
|
|
127
|
+
if (n.format === "png")
|
|
128
|
+
return { bytes: b(s, a, c), chosen: "png", width: a, height: c };
|
|
129
|
+
if (n.format === "jpeg")
|
|
130
|
+
return {
|
|
131
|
+
bytes: B(s, a, c, n.jpegPercent / 100),
|
|
132
|
+
chosen: "jpeg",
|
|
133
|
+
width: a,
|
|
134
|
+
height: c
|
|
135
|
+
};
|
|
136
|
+
if (a * c > Y)
|
|
137
|
+
return {
|
|
138
|
+
bytes: b(s, a, c),
|
|
139
|
+
chosen: "png",
|
|
140
|
+
reason: "too-large-for-png8",
|
|
141
|
+
width: a,
|
|
142
|
+
height: c
|
|
143
|
+
};
|
|
144
|
+
if (K(s, n.smartColorThreshold))
|
|
145
|
+
return n.smartFallback === "jpeg" ? {
|
|
146
|
+
bytes: B(s, a, c, n.jpegPercent / 100),
|
|
147
|
+
chosen: "jpeg",
|
|
148
|
+
reason: "photo-fallback-jpeg",
|
|
149
|
+
width: a,
|
|
150
|
+
height: c
|
|
151
|
+
} : {
|
|
152
|
+
bytes: b(s, a, c),
|
|
153
|
+
chosen: "png",
|
|
154
|
+
reason: "photo-fallback-png",
|
|
155
|
+
width: a,
|
|
156
|
+
height: c
|
|
157
|
+
};
|
|
158
|
+
const f = await J(s, a, c);
|
|
159
|
+
return f ? { bytes: f, chosen: "png", reason: "png-8", width: a, height: c } : {
|
|
160
|
+
bytes: b(s, a, c),
|
|
161
|
+
chosen: "png",
|
|
162
|
+
reason: "imagequant-missing",
|
|
163
|
+
width: a,
|
|
164
|
+
height: c
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
async function Te(e, t = C) {
|
|
168
|
+
const r = await R(Buffer.from(e)), o = m(r.width, r.height).getContext("2d");
|
|
169
|
+
o.drawImage(r, 0, 0);
|
|
170
|
+
const i = o.getImageData(0, 0, r.width, r.height).data, s = new Uint8Array(i.buffer, i.byteOffset, i.byteLength);
|
|
171
|
+
return O(s, r.width, r.height, t);
|
|
172
|
+
}
|
|
173
|
+
function b(e, t, r) {
|
|
174
|
+
const n = m(t, r), o = n.getContext("2d"), i = o.createImageData(t, r);
|
|
175
|
+
i.data.set(e), o.putImageData(i, 0, 0);
|
|
176
|
+
const s = n.toBuffer("image/png");
|
|
177
|
+
return new Uint8Array(s.buffer, s.byteOffset, s.byteLength);
|
|
178
|
+
}
|
|
179
|
+
function B(e, t, r, n) {
|
|
180
|
+
const o = m(t, r), i = o.getContext("2d"), s = i.createImageData(t, r);
|
|
181
|
+
s.data.set(e), i.putImageData(s, 0, 0);
|
|
182
|
+
const a = o.toBuffer("image/jpeg", n);
|
|
183
|
+
return new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
|
|
184
|
+
}
|
|
185
|
+
function ee(e, t, r, n, o) {
|
|
186
|
+
const i = m(t, r), s = i.getContext("2d"), a = s.createImageData(t, r);
|
|
187
|
+
a.data.set(e), s.putImageData(a, 0, 0);
|
|
188
|
+
const u = m(n, o).getContext("2d");
|
|
189
|
+
u.imageSmoothingEnabled = !0, u.imageSmoothingQuality = "high", u.drawImage(i, 0, 0, t, r, 0, 0, n, o);
|
|
190
|
+
const g = u.getImageData(0, 0, n, o).data;
|
|
191
|
+
return new Uint8Array(g.buffer, g.byteOffset, g.byteLength);
|
|
192
|
+
}
|
|
193
|
+
function te(e) {
|
|
194
|
+
if (!e || e.trim().length === 0)
|
|
195
|
+
return "";
|
|
196
|
+
const t = new j();
|
|
197
|
+
let r;
|
|
198
|
+
try {
|
|
199
|
+
r = t.parseFromString(e, "image/svg+xml");
|
|
200
|
+
} catch {
|
|
201
|
+
return "";
|
|
202
|
+
}
|
|
203
|
+
const n = r.documentElement;
|
|
204
|
+
if (!n) return "";
|
|
205
|
+
const o = new q();
|
|
206
|
+
let i = "";
|
|
207
|
+
for (let s = 0; s < n.childNodes.length; s++) {
|
|
208
|
+
const a = n.childNodes.item(s);
|
|
209
|
+
if (!a || a.nodeType !== 1) continue;
|
|
210
|
+
const c = a, u = c.localName ?? c.tagName ?? "";
|
|
211
|
+
if (u === "defs") {
|
|
212
|
+
const f = re(c);
|
|
213
|
+
f !== null && (i += o.serializeToString(f));
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (!(u === "image" && !c.getAttribute("data-redact-style")) && c.getAttribute("id") !== "ui-overlay") {
|
|
217
|
+
if (c.getAttribute("id") === "annotations") {
|
|
218
|
+
for (let f = 0; f < c.childNodes.length; f++) {
|
|
219
|
+
const g = c.childNodes.item(f);
|
|
220
|
+
g && g.nodeType === 1 && (i += o.serializeToString(g));
|
|
221
|
+
}
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
i += o.serializeToString(c);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return i;
|
|
228
|
+
}
|
|
229
|
+
function re(e) {
|
|
230
|
+
const t = e.cloneNode(!0);
|
|
231
|
+
for (let r = t.childNodes.length - 1; r >= 0; r--) {
|
|
232
|
+
const n = t.childNodes.item(r);
|
|
233
|
+
if (!n || n.nodeType !== 1) continue;
|
|
234
|
+
const o = n;
|
|
235
|
+
(o.localName ?? o.tagName ?? "") === "style" && o.hasAttribute("data-annot-fonts") && t.removeChild(n);
|
|
236
|
+
}
|
|
237
|
+
return t.childNodes.length === 0 ? null : t;
|
|
238
|
+
}
|
|
239
|
+
function Pe(e = {}) {
|
|
240
|
+
const t = {
|
|
241
|
+
loadSystemFonts: e.loadSystemFonts ?? !1,
|
|
242
|
+
...e.fontFiles ? { fontFiles: e.fontFiles } : {},
|
|
243
|
+
...e.fontDirs ? { fontDirs: e.fontDirs } : {},
|
|
244
|
+
...e.defaultFontFamily ? { defaultFontFamily: e.defaultFontFamily } : {}
|
|
245
|
+
};
|
|
246
|
+
function r(n) {
|
|
247
|
+
const o = $(n), s = new P(o, {
|
|
248
|
+
fitTo: { mode: "width", value: n.width },
|
|
249
|
+
background: "rgba(0, 0, 0, 0)",
|
|
250
|
+
font: t
|
|
251
|
+
}).render();
|
|
252
|
+
return {
|
|
253
|
+
pixels: s.pixels,
|
|
254
|
+
width: s.width,
|
|
255
|
+
height: s.height
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
toSvg(n) {
|
|
260
|
+
return $(n);
|
|
261
|
+
},
|
|
262
|
+
toPng(n) {
|
|
263
|
+
const o = $(n);
|
|
264
|
+
return new P(o, {
|
|
265
|
+
fitTo: { mode: "width", value: n.width },
|
|
266
|
+
background: "rgba(0, 0, 0, 0)",
|
|
267
|
+
font: t
|
|
268
|
+
}).render().asPng();
|
|
269
|
+
},
|
|
270
|
+
async toEncoded(n, o) {
|
|
271
|
+
const { pixels: i, width: s, height: a } = r(n);
|
|
272
|
+
return O(i, s, a, o);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function $(e) {
|
|
277
|
+
const t = "http://www.w3.org/2000/svg", r = "http://www.w3.org/1999/xlink", n = te(e.annotationsSvg);
|
|
278
|
+
return `<svg xmlns="${t}" xmlns:xlink="${r}" ${M}="${W}" width="${e.width}" height="${e.height}" viewBox="0 0 ${e.width} ${e.height}"><image href="${e.originalDataUrl}" width="${e.width}" height="${e.height}"/>` + n + "</svg>";
|
|
279
|
+
}
|
|
280
|
+
const Be = {
|
|
281
|
+
BBox: {
|
|
282
|
+
type: "object",
|
|
283
|
+
required: ["x", "y", "width", "height"],
|
|
284
|
+
additionalProperties: !1,
|
|
285
|
+
properties: {
|
|
286
|
+
x: { type: "number" },
|
|
287
|
+
y: { type: "number" },
|
|
288
|
+
width: { type: "number", minimum: 0 },
|
|
289
|
+
height: { type: "number", minimum: 0 }
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
Point: {
|
|
293
|
+
type: "object",
|
|
294
|
+
required: ["x", "y"],
|
|
295
|
+
additionalProperties: !1,
|
|
296
|
+
properties: {
|
|
297
|
+
x: { type: "number" },
|
|
298
|
+
y: { type: "number" }
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
Intent: {
|
|
302
|
+
type: "string",
|
|
303
|
+
enum: ["info", "warning", "error", "success", "neutral"]
|
|
304
|
+
},
|
|
305
|
+
AnnotationStyle: {
|
|
306
|
+
type: "object",
|
|
307
|
+
additionalProperties: !1,
|
|
308
|
+
properties: {
|
|
309
|
+
intent: { $ref: "#/$defs/Intent" },
|
|
310
|
+
stroke: { type: "string" },
|
|
311
|
+
strokeWidth: { type: "number", minimum: 0 },
|
|
312
|
+
fill: { type: "string" },
|
|
313
|
+
color: { type: "string" }
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}, ne = {
|
|
317
|
+
type: "object",
|
|
318
|
+
required: ["type", "bbox"],
|
|
319
|
+
additionalProperties: !1,
|
|
320
|
+
properties: {
|
|
321
|
+
type: { const: "rect" },
|
|
322
|
+
bbox: { $ref: "#/$defs/BBox" },
|
|
323
|
+
intent: { $ref: "#/$defs/Intent" },
|
|
324
|
+
stroke: { type: "string" },
|
|
325
|
+
strokeWidth: { type: "number", minimum: 0 },
|
|
326
|
+
fill: { type: "string" },
|
|
327
|
+
color: { type: "string" }
|
|
328
|
+
}
|
|
329
|
+
}, oe = {
|
|
330
|
+
type: "object",
|
|
331
|
+
required: ["type", "center", "radius"],
|
|
332
|
+
additionalProperties: !1,
|
|
333
|
+
properties: {
|
|
334
|
+
type: { const: "circle" },
|
|
335
|
+
center: { $ref: "#/$defs/Point" },
|
|
336
|
+
radius: { type: "number", minimum: 0 },
|
|
337
|
+
intent: { $ref: "#/$defs/Intent" },
|
|
338
|
+
stroke: { type: "string" },
|
|
339
|
+
strokeWidth: { type: "number", minimum: 0 },
|
|
340
|
+
fill: { type: "string" },
|
|
341
|
+
color: { type: "string" }
|
|
342
|
+
}
|
|
343
|
+
}, ie = {
|
|
344
|
+
type: "object",
|
|
345
|
+
required: ["type", "from", "to"],
|
|
346
|
+
additionalProperties: !1,
|
|
347
|
+
properties: {
|
|
348
|
+
type: { const: "arrow" },
|
|
349
|
+
from: { $ref: "#/$defs/Point" },
|
|
350
|
+
to: { $ref: "#/$defs/Point" },
|
|
351
|
+
intent: { $ref: "#/$defs/Intent" },
|
|
352
|
+
stroke: { type: "string" },
|
|
353
|
+
strokeWidth: { type: "number", minimum: 0 },
|
|
354
|
+
color: { type: "string" }
|
|
355
|
+
}
|
|
356
|
+
}, se = {
|
|
357
|
+
type: "object",
|
|
358
|
+
required: ["type", "at", "content"],
|
|
359
|
+
additionalProperties: !1,
|
|
360
|
+
properties: {
|
|
361
|
+
type: { const: "text" },
|
|
362
|
+
at: { $ref: "#/$defs/Point" },
|
|
363
|
+
content: { type: "string" },
|
|
364
|
+
fontSize: { type: "number", minimum: 0 },
|
|
365
|
+
anchor: { type: "string", enum: ["start", "middle", "end"] },
|
|
366
|
+
intent: { $ref: "#/$defs/Intent" },
|
|
367
|
+
color: { type: "string" }
|
|
368
|
+
}
|
|
369
|
+
}, ae = {
|
|
370
|
+
type: "object",
|
|
371
|
+
required: ["type", "at", "targetBbox", "content"],
|
|
372
|
+
additionalProperties: !1,
|
|
373
|
+
properties: {
|
|
374
|
+
type: { const: "callout" },
|
|
375
|
+
at: { $ref: "#/$defs/Point" },
|
|
376
|
+
targetBbox: { $ref: "#/$defs/BBox" },
|
|
377
|
+
content: { type: "string" },
|
|
378
|
+
intent: { $ref: "#/$defs/Intent" },
|
|
379
|
+
stroke: { type: "string" },
|
|
380
|
+
color: { type: "string" }
|
|
381
|
+
}
|
|
382
|
+
}, ce = {
|
|
383
|
+
type: "object",
|
|
384
|
+
required: ["type", "svgFragment"],
|
|
385
|
+
additionalProperties: !1,
|
|
386
|
+
properties: {
|
|
387
|
+
type: { const: "raw" },
|
|
388
|
+
svgFragment: { type: "string" }
|
|
389
|
+
}
|
|
390
|
+
}, Ee = {
|
|
391
|
+
oneOf: [ne, oe, ie, se, ae, ce]
|
|
392
|
+
}, _e = {
|
|
393
|
+
type: "object",
|
|
394
|
+
required: ["bbox"],
|
|
395
|
+
additionalProperties: !1,
|
|
396
|
+
properties: {
|
|
397
|
+
bbox: { $ref: "#/$defs/BBox" },
|
|
398
|
+
style: { type: "string", enum: ["solid", "mosaic", "blur"] },
|
|
399
|
+
color: { type: "string" }
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
function F(e, t = {}) {
|
|
403
|
+
const r = t.stroke ?? "red", n = t.strokeWidth ?? 2, o = t.fill ?? "none";
|
|
404
|
+
return `<rect x="${e.x}" y="${e.y}" width="${e.width}" height="${e.height}" fill="${p(o)}" stroke="${p(r)}" stroke-width="${n}"/>`;
|
|
405
|
+
}
|
|
406
|
+
function z(e, t, r = {}) {
|
|
407
|
+
const n = r.color ?? "red", o = r.strokeWidth ?? 2, i = `annot-arrow-${le()}`;
|
|
408
|
+
return `<defs><marker id="${i}" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto" markerUnits="strokeWidth"><path d="M 0 0 L 10 5 L 0 10 z" fill="${p(n)}"/></marker></defs><line x1="${e.x}" y1="${e.y}" x2="${t.x}" y2="${t.y}" stroke="${p(n)}" stroke-width="${o}" marker-end="url(#${i})"/>`;
|
|
409
|
+
}
|
|
410
|
+
function D(e, t, r = {}) {
|
|
411
|
+
const n = r.color ?? "red", o = r.fontSize ?? 14, i = r.anchor ?? "start";
|
|
412
|
+
return `<text x="${e.x}" y="${e.y}" fill="${p(n)}" font-size="${o}" text-anchor="${i}">` + fe(t) + "</text>";
|
|
413
|
+
}
|
|
414
|
+
let k = 0;
|
|
415
|
+
function le() {
|
|
416
|
+
return k = k + 1 | 0, k;
|
|
417
|
+
}
|
|
418
|
+
function p(e) {
|
|
419
|
+
return e.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
420
|
+
}
|
|
421
|
+
function fe(e) {
|
|
422
|
+
return e.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
423
|
+
}
|
|
424
|
+
const ge = {
|
|
425
|
+
info: { stroke: "#3b82f6", fill: "rgba(59, 130, 246, 0.12)", text: "#1e40af" },
|
|
426
|
+
warning: { stroke: "#f59e0b", fill: "rgba(245, 158, 11, 0.12)", text: "#92400e" },
|
|
427
|
+
error: { stroke: "#ef4444", fill: "rgba(239, 68, 68, 0.12)", text: "#991b1b" },
|
|
428
|
+
success: { stroke: "#10b981", fill: "rgba(16, 185, 129, 0.12)", text: "#065f46" },
|
|
429
|
+
neutral: { stroke: "#6b7280", fill: "rgba(107, 114, 128, 0.12)", text: "#374151" }
|
|
430
|
+
}, ue = "error";
|
|
431
|
+
function y(e) {
|
|
432
|
+
const t = e.intent ?? ue, r = ge[t];
|
|
433
|
+
return {
|
|
434
|
+
stroke: e.stroke ?? r.stroke,
|
|
435
|
+
fill: e.fill ?? "none",
|
|
436
|
+
text: e.color ?? r.text
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
function Ce(e) {
|
|
440
|
+
return e.map(de).join("");
|
|
441
|
+
}
|
|
442
|
+
function de(e) {
|
|
443
|
+
switch (e.type) {
|
|
444
|
+
case "rect":
|
|
445
|
+
return he(e);
|
|
446
|
+
case "circle":
|
|
447
|
+
return me(e);
|
|
448
|
+
case "arrow":
|
|
449
|
+
return pe(e);
|
|
450
|
+
case "text":
|
|
451
|
+
return ye(e);
|
|
452
|
+
case "callout":
|
|
453
|
+
return we(e);
|
|
454
|
+
case "raw":
|
|
455
|
+
return be(e);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
function he(e) {
|
|
459
|
+
const t = y(e);
|
|
460
|
+
return F(e.bbox, {
|
|
461
|
+
stroke: t.stroke,
|
|
462
|
+
strokeWidth: e.strokeWidth ?? 2,
|
|
463
|
+
fill: t.fill
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
function me(e) {
|
|
467
|
+
const t = y(e), r = e.strokeWidth ?? 2;
|
|
468
|
+
return `<circle cx="${e.center.x}" cy="${e.center.y}" r="${e.radius}" fill="${_(t.fill)}" stroke="${_(t.stroke)}" stroke-width="${r}"/>`;
|
|
469
|
+
}
|
|
470
|
+
function pe(e) {
|
|
471
|
+
const t = y(e);
|
|
472
|
+
return z(e.from, e.to, {
|
|
473
|
+
color: t.stroke,
|
|
474
|
+
strokeWidth: e.strokeWidth ?? 2
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
function ye(e) {
|
|
478
|
+
const t = y(e);
|
|
479
|
+
return D(e.at, e.content, {
|
|
480
|
+
color: t.text,
|
|
481
|
+
fontSize: e.fontSize ?? 14,
|
|
482
|
+
anchor: e.anchor ?? "start"
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
function we(e) {
|
|
486
|
+
const t = y(e), r = e.targetBbox, n = xe(r, e.at);
|
|
487
|
+
return F(r, {
|
|
488
|
+
stroke: t.stroke,
|
|
489
|
+
strokeWidth: e.strokeWidth ?? 2,
|
|
490
|
+
fill: t.fill
|
|
491
|
+
}) + z(e.at, n, {
|
|
492
|
+
color: t.stroke,
|
|
493
|
+
strokeWidth: 2
|
|
494
|
+
}) + D(e.at, e.content, {
|
|
495
|
+
color: t.text,
|
|
496
|
+
fontSize: 14,
|
|
497
|
+
anchor: "start"
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
function be(e) {
|
|
501
|
+
return e.svgFragment;
|
|
502
|
+
}
|
|
503
|
+
function xe(e, t) {
|
|
504
|
+
const r = E(t.x, e.x, e.x + e.width), n = E(t.y, e.y, e.y + e.height);
|
|
505
|
+
return { x: r, y: n };
|
|
506
|
+
}
|
|
507
|
+
function E(e, t, r) {
|
|
508
|
+
return Math.min(Math.max(e, t), r);
|
|
509
|
+
}
|
|
510
|
+
function _(e) {
|
|
511
|
+
return e.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
512
|
+
}
|
|
513
|
+
export {
|
|
514
|
+
Ee as BBOX_ANNOTATION_SCHEMA,
|
|
515
|
+
_e as BBOX_REDACT_REGION_SCHEMA,
|
|
516
|
+
C as DEFAULT_ENCODE_OPTIONS,
|
|
517
|
+
Se as SAVE_SIZE_LABEL,
|
|
518
|
+
X as SAVE_SIZE_MAX_WIDTH,
|
|
519
|
+
Be as SHARED_DEFS,
|
|
520
|
+
z as arrowBetween,
|
|
521
|
+
Ce as bboxAnnotationsToSvg,
|
|
522
|
+
V as computeResizeTarget,
|
|
523
|
+
Pe as createAnnotator,
|
|
524
|
+
Te as decodeAndEncodeImage,
|
|
525
|
+
O as encodeRgba,
|
|
526
|
+
ve as isImagequantAvailable,
|
|
527
|
+
K as isPhotoHeavy,
|
|
528
|
+
F as rectForBoundingBox,
|
|
529
|
+
D as textAt
|
|
530
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ingcreators/annot-annotator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Headless annotator — render annotated screenshots from Node without a browser. Built on @resvg/resvg-js. Pairs with @ingcreators/annot-playwright for fail-on-test annotated reports.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "ingcreators",
|
|
@@ -44,13 +44,16 @@
|
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"typescript": "^6.0.3",
|
|
47
|
-
"vite": "^
|
|
47
|
+
"vite": "^6.4.2",
|
|
48
48
|
"vite-plugin-dts": "^5.0.0",
|
|
49
49
|
"@ingcreators/annot-core": "0.1.0"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
+
"@napi-rs/canvas": "^0.1.0",
|
|
52
53
|
"@resvg/resvg-js": "^2.6.2",
|
|
53
|
-
"@xmldom/xmldom": "^0.9.10"
|
|
54
|
+
"@xmldom/xmldom": "^0.9.10",
|
|
55
|
+
"pako": "^2.1.0",
|
|
56
|
+
"@ingcreators/annot-imagequant": "0.1.0"
|
|
54
57
|
},
|
|
55
58
|
"scripts": {
|
|
56
59
|
"build": "vite build",
|