@ingcreators/annot-core 0.2.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 +147 -0
- package/README.md +1 -1
- package/dist/editor/index.d.ts +1 -0
- package/dist/editor/overlay-pick-types.d.ts +35 -0
- package/dist/editor/tool-registry.d.ts +1 -1
- package/dist/element-tree/index.d.ts +4 -0
- package/dist/element-tree/json.d.ts +21 -0
- package/dist/element-tree/types.d.ts +96 -0
- package/dist/element-tree/walk.d.ts +49 -0
- package/dist/element-tree/yaml.d.ts +22 -0
- package/dist/headless.d.ts +3 -1
- package/dist/index.js +1884 -621
- package/dist/storage/path.d.ts +17 -0
- package/dist/storage/types.d.ts +71 -87
- package/dist/xmp/element-tree-payload.d.ts +32 -0
- package/dist/xmp/xmp-browser.d.ts +1 -1
- package/dist/xmp/xmp-bytes.d.ts +29 -0
- package/dist/xmp-bytes.js +265 -0
- package/package.json +9 -3
- package/styles/editor.css +6 -3
package/dist/storage/path.d.ts
CHANGED
|
@@ -20,6 +20,23 @@ export declare function joinPath(parent: string, name: string): string;
|
|
|
20
20
|
export declare function getParentPath(path: string): string;
|
|
21
21
|
/** Return the last segment (folder name or filename) from a path. */
|
|
22
22
|
export declare function getFilename(path: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Derive the annotations YAML sidecar path paired with the image
|
|
25
|
+
* at `pngPath`. Convention introduced in Phase 4a of the
|
|
26
|
+
* [living-spec authoring roadmap](../../../../docs/plans/living-spec-authoring-roadmap.md):
|
|
27
|
+
* the sidecar sits in the same folder as the PNG with filename
|
|
28
|
+
* `<pngFilename>.annotations.yaml`.
|
|
29
|
+
*
|
|
30
|
+
* Examples:
|
|
31
|
+
* - `shots/login.png` → `shots/login.png.annotations.yaml`
|
|
32
|
+
* - `login.jpg` → `login.jpg.annotations.yaml`
|
|
33
|
+
*
|
|
34
|
+
* The leaf-as-suffix form (keep the full PNG filename including
|
|
35
|
+
* its extension, then append `.annotations.yaml`) keeps the
|
|
36
|
+
* sidecar discoverable next to its image regardless of the image
|
|
37
|
+
* extension.
|
|
38
|
+
*/
|
|
39
|
+
export declare function annotationsYamlPathFor(pngPath: string): string;
|
|
23
40
|
/** Split a path into its segments. Returns [] for the root path. */
|
|
24
41
|
export declare function splitPath(path: string): string[];
|
|
25
42
|
/**
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Shared storage types used by Extension, Web app, and Desktop.
|
|
3
|
-
*
|
|
4
|
-
* Images and folders are identified by their filesystem-style path.
|
|
5
|
-
* Root folder is represented by the empty string "".
|
|
6
|
-
*/
|
|
1
|
+
import { ElementTree } from '../element-tree/types.js';
|
|
7
2
|
export interface ImageRecord {
|
|
8
3
|
/** Primary key: full path, e.g. "Screenshots/Mobile/image-123.png". */
|
|
9
4
|
path: string;
|
|
@@ -18,12 +13,14 @@ export interface ImageRecord {
|
|
|
18
13
|
tags: Record<string, string>;
|
|
19
14
|
createdAt: string;
|
|
20
15
|
updatedAt: string;
|
|
21
|
-
/**
|
|
22
|
-
* by the browser extension
|
|
23
|
-
*
|
|
24
|
-
* capture
|
|
25
|
-
*
|
|
26
|
-
|
|
16
|
+
/** Canonical screen-capture tree captured alongside the
|
|
17
|
+
* screenshot. Source-agnostic: produced by the browser extension's
|
|
18
|
+
* MAIN-world walker, by Playwright's adapter, or by any future
|
|
19
|
+
* capture source (Figma adapter, OCR-derived, etc.). Undefined
|
|
20
|
+
* when the capture came from a non-DOM source (desktop screenshot,
|
|
21
|
+
* paste). Enables "click the Submit button → auto-annotate"
|
|
22
|
+
* features in the editor. See `@ingcreators/annot-core/element-tree`. */
|
|
23
|
+
elementTree?: ElementTree;
|
|
27
24
|
}
|
|
28
25
|
/**
|
|
29
26
|
* In-place updates allowed via `updateImage`. Note that `folderPath`
|
|
@@ -62,81 +59,6 @@ export interface ImageRecord {
|
|
|
62
59
|
* dimensions actually changed.
|
|
63
60
|
*/
|
|
64
61
|
export type ImageRecordUpdate = Partial<Pick<ImageRecord, "annotationsSvg" | "tags" | "updatedAt" | "originalDataUrl" | "width" | "height">>;
|
|
65
|
-
export interface PageMetadata {
|
|
66
|
-
/** Schema version — bump on breaking changes so consumers can
|
|
67
|
-
* gracefully handle old records. Current: 1. */
|
|
68
|
-
version: 1;
|
|
69
|
-
/** Source URL at capture time (also stored on ImageRecord.sourceUrl
|
|
70
|
-
* but duplicated here so metadata is self-contained). */
|
|
71
|
-
url: string;
|
|
72
|
-
/** Viewport size at capture time, in CSS pixels (not device pixels).
|
|
73
|
-
* Used to compute the scale factor when mapping bboxes onto the
|
|
74
|
-
* screenshot (which is in device pixels). */
|
|
75
|
-
viewport: {
|
|
76
|
-
width: number;
|
|
77
|
-
height: number;
|
|
78
|
-
};
|
|
79
|
-
/** `window.devicePixelRatio` at capture time. */
|
|
80
|
-
devicePixelRatio: number;
|
|
81
|
-
/** For scroll / per-page captures: the scroll offset at capture
|
|
82
|
-
* time. Single-viewport captures use `{ x: 0, y: 0 }`. Bboxes in
|
|
83
|
-
* `elements` are in document coordinates; subtract this offset +
|
|
84
|
-
* add again per-segment for stitched captures. */
|
|
85
|
-
scrollOffset: {
|
|
86
|
-
x: number;
|
|
87
|
-
y: number;
|
|
88
|
-
};
|
|
89
|
-
/** The rectangle of the document that the SCREENSHOT actually
|
|
90
|
-
* covers, in CSS pixels, document coordinates. Used by the editor
|
|
91
|
-
* to (a) FILTER `elements` to those visible in the screenshot
|
|
92
|
-
* and (b) MAP element bboxes from doc coords → screenshot coords
|
|
93
|
-
* via `(elBbox - captureRect.origin) * devicePixelRatio`.
|
|
94
|
-
* - visible capture: equals viewport at capture time (scrollX/Y +
|
|
95
|
-
* viewport size)
|
|
96
|
-
* - area capture: the user-selected sub-region, offset by scroll
|
|
97
|
-
* - scroll / per-page: full document (or per-segment) */
|
|
98
|
-
captureRect: {
|
|
99
|
-
x: number;
|
|
100
|
-
y: number;
|
|
101
|
-
width: number;
|
|
102
|
-
height: number;
|
|
103
|
-
};
|
|
104
|
-
/** ISO timestamp of when the metadata snapshot was taken (usually
|
|
105
|
-
* within a few ms of the screenshot). */
|
|
106
|
-
capturedAt: string;
|
|
107
|
-
/** Detected elements. Filtered to interactive / labeled items (see
|
|
108
|
-
* `interactiveRole` predicate in the content script). */
|
|
109
|
-
elements: PageElement[];
|
|
110
|
-
}
|
|
111
|
-
export interface PageElement {
|
|
112
|
-
/** Stable id within this capture — references across annotations. */
|
|
113
|
-
id: string;
|
|
114
|
-
/** Tag name in lowercase (e.g. "button", "a", "input"). */
|
|
115
|
-
tag: string;
|
|
116
|
-
/** ARIA role or implicit role ("button", "link", "textbox", ...). */
|
|
117
|
-
role?: string;
|
|
118
|
-
/** Visible text — textContent for button/link, label text for input. */
|
|
119
|
-
text?: string;
|
|
120
|
-
/** `aria-label` — explicit accessibility label, prefer over `text`
|
|
121
|
-
* when present since it's the a11y ground truth. */
|
|
122
|
-
ariaLabel?: string;
|
|
123
|
-
/** For form inputs: the input type (text / email / submit / …). */
|
|
124
|
-
inputType?: string;
|
|
125
|
-
/** For inputs: placeholder text. */
|
|
126
|
-
placeholder?: string;
|
|
127
|
-
/** For links: the href destination. */
|
|
128
|
-
href?: string;
|
|
129
|
-
/** Element id attribute (if any) — useful for stable selection. */
|
|
130
|
-
domId?: string;
|
|
131
|
-
/** Bounding box in DOCUMENT (not viewport) coordinates at capture
|
|
132
|
-
* time, in CSS pixels. To draw on the screenshot: multiply by
|
|
133
|
-
* devicePixelRatio. [x, y, width, height]. */
|
|
134
|
-
bbox: [number, number, number, number];
|
|
135
|
-
/** CSS selector (best effort) for re-locating the element later. */
|
|
136
|
-
selector?: string;
|
|
137
|
-
/** True if element was (partially) visible at capture time. */
|
|
138
|
-
visible: boolean;
|
|
139
|
-
}
|
|
140
62
|
export interface FolderRecord {
|
|
141
63
|
/** Primary key: full path, e.g. "Screenshots/Mobile". */
|
|
142
64
|
path: string;
|
|
@@ -667,6 +589,53 @@ export interface StorageWithDocuments {
|
|
|
667
589
|
* @throws `Error` for unstructured backend / IO failures.
|
|
668
590
|
*/
|
|
669
591
|
updateDocument(path: string, updates: DocumentRecordUpdate): Promise<void>;
|
|
592
|
+
/**
|
|
593
|
+
* Read the annotations YAML sidecar paired with the image at
|
|
594
|
+
* `pngPath`. The store derives the sidecar location internally
|
|
595
|
+
* from the convention `<pngPath>.annotations.yaml` (so
|
|
596
|
+
* `shots/login.png` →`shots/login.png.annotations.yaml`); callers
|
|
597
|
+
* pass the PNG path only.
|
|
598
|
+
*
|
|
599
|
+
* Optional — stores that don't support sidecar yaml files may
|
|
600
|
+
* omit this method. Callers gate on
|
|
601
|
+
* {@link supportsAnnotationsYaml} before invoking.
|
|
602
|
+
*
|
|
603
|
+
* Introduced in Phase 4a of the
|
|
604
|
+
* [living-spec authoring roadmap](../../../../docs/plans/living-spec-authoring-roadmap.md)
|
|
605
|
+
* for the Annot editor's Overlay tool. Each entry in the yaml
|
|
606
|
+
* follows the `AnnotationsFile` schema defined by
|
|
607
|
+
* `@ingcreators/annot-product-docs` (Phase 2a).
|
|
608
|
+
*
|
|
609
|
+
* @returns the YAML source string, or `undefined` when no
|
|
610
|
+
* sidecar exists for that PNG.
|
|
611
|
+
* @throws `Error` for backend / IO / parse failures.
|
|
612
|
+
*/
|
|
613
|
+
getAnnotationsYaml?(pngPath: string): Promise<string | undefined>;
|
|
614
|
+
/**
|
|
615
|
+
* Atomically create or replace the annotations YAML sidecar
|
|
616
|
+
* paired with the image at `pngPath`. The store derives the
|
|
617
|
+
* sidecar location internally from the convention
|
|
618
|
+
* `<pngPath>.annotations.yaml`.
|
|
619
|
+
*
|
|
620
|
+
* Optional — stores that don't support sidecar yaml files may
|
|
621
|
+
* omit this method. Callers gate on
|
|
622
|
+
* {@link supportsAnnotationsYaml} before invoking.
|
|
623
|
+
*
|
|
624
|
+
* The write does NOT touch the PNG bytes or any sibling MDX
|
|
625
|
+
* file — only the sidecar yaml is mutated. Idempotent on
|
|
626
|
+
* unchanged input (same `content` twice produces the same
|
|
627
|
+
* on-disk bytes).
|
|
628
|
+
*
|
|
629
|
+
* Introduced in Phase 4a of the
|
|
630
|
+
* [living-spec authoring roadmap](../../../../docs/plans/living-spec-authoring-roadmap.md).
|
|
631
|
+
*
|
|
632
|
+
* @throws `StoragePermissionError` for backend auth / ACL
|
|
633
|
+
* rejection.
|
|
634
|
+
* @throws `StorageQuotaError` when the backend reports
|
|
635
|
+
* out-of-space.
|
|
636
|
+
* @throws `Error` for unstructured backend / IO failures.
|
|
637
|
+
*/
|
|
638
|
+
setAnnotationsYaml?(pngPath: string, content: string): Promise<void>;
|
|
670
639
|
}
|
|
671
640
|
export declare function supportsResync(store: StorageProvider): store is StorageProvider & StorageWithResync;
|
|
672
641
|
export declare function supportsForceRefresh(store: StorageProvider): store is StorageProvider & StorageWithForceRefresh;
|
|
@@ -675,3 +644,18 @@ export declare function supportsInit(store: StorageProvider): store is StoragePr
|
|
|
675
644
|
export declare function supportsRateLimit(store: StorageProvider): store is StorageProvider & StorageWithRateLimit;
|
|
676
645
|
export declare function supportsThumbnailCache(store: StorageProvider): store is StorageProvider & StorageWithThumbnailCache;
|
|
677
646
|
export declare function supportsDocuments(store: StorageProvider): store is StorageProvider & StorageWithDocuments;
|
|
647
|
+
/**
|
|
648
|
+
* Narrows a store to one that exposes the Phase 4a annotations
|
|
649
|
+
* YAML sidecar surface (`getAnnotationsYaml` + `setAnnotationsYaml`).
|
|
650
|
+
*
|
|
651
|
+
* Use instead of `if (store.getAnnotationsYaml)` so the narrow is
|
|
652
|
+
* type-safe and the optional behaviour is documented at the call
|
|
653
|
+
* site. The methods sit on
|
|
654
|
+
* {@link StorageWithDocuments} as optional members; this predicate
|
|
655
|
+
* is the dedicated capability check for callers that need both
|
|
656
|
+
* sides of the read / write pair.
|
|
657
|
+
*/
|
|
658
|
+
export declare function supportsAnnotationsYaml(store: StorageProvider): store is StorageProvider & StorageWithDocuments & {
|
|
659
|
+
getAnnotationsYaml: NonNullable<StorageWithDocuments["getAnnotationsYaml"]>;
|
|
660
|
+
setAnnotationsYaml: NonNullable<StorageWithDocuments["setAnnotationsYaml"]>;
|
|
661
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ElementTree } from '../element-tree/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* iTXt chunk keyword identifying the ElementTree payload.
|
|
4
|
+
* Differs from the editor's XMP keyword (`XML:com.adobe.xmp`) so
|
|
5
|
+
* the two chunks can coexist on the same PNG.
|
|
6
|
+
*/
|
|
7
|
+
export declare const ELEMENT_TREE_ITXT_KEYWORD = "annot:elementTree";
|
|
8
|
+
/**
|
|
9
|
+
* Write `tree` into a PNG's `annot:elementTree` iTXt chunk. The
|
|
10
|
+
* chunk is deflate-compressed (compressionFlag=1) — ElementTree YAML
|
|
11
|
+
* is verbose with high token repetition and compresses well.
|
|
12
|
+
*
|
|
13
|
+
* Any existing `annot:elementTree` chunk is replaced. Other chunks
|
|
14
|
+
* (image data, editor XMP, embedded original via `svGo`) are
|
|
15
|
+
* preserved verbatim.
|
|
16
|
+
*/
|
|
17
|
+
export declare function writeElementTreePng(pngData: Uint8Array, tree: ElementTree): Uint8Array;
|
|
18
|
+
/**
|
|
19
|
+
* Read the `annot:elementTree` payload from a PNG. Returns the
|
|
20
|
+
* parsed `ElementTree`, or `null` when the input is not a PNG or
|
|
21
|
+
* has no `annot:elementTree` chunk.
|
|
22
|
+
*
|
|
23
|
+
* Throws on schema-version mismatch (unknown major version) or
|
|
24
|
+
* parse failure — silent ignore would mask data corruption. Callers
|
|
25
|
+
* that need a tolerant read should wrap in try / catch.
|
|
26
|
+
*/
|
|
27
|
+
export declare function readElementTreePng(pngData: Uint8Array): ElementTree | null;
|
|
28
|
+
/**
|
|
29
|
+
* Light-weight predicate — does this PNG carry an
|
|
30
|
+
* `annot:elementTree` chunk at all? Doesn't deflate or parse.
|
|
31
|
+
*/
|
|
32
|
+
export declare function hasElementTreePng(pngData: Uint8Array): boolean;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* Re-exports of Tier-A symbols below keep existing
|
|
15
15
|
* `@ingcreators/annot-core/xmp` consumers working unchanged.
|
|
16
16
|
*/
|
|
17
|
-
export { type AnnotMetadata, type CreateEditablePngBytesOptions, createEditablePngBytes, readEditableImage, readEditablePngBytes, WELL_KNOWN_TAG_KEYS, writePngWithTagsOnly, } from './xmp-bytes.js';
|
|
17
|
+
export { type AnnotMetadata, type CreateEditablePngBytesOptions, createEditablePngBytes, dataUrlToUint8Array, readEditableImage, readEditablePngBytes, WELL_KNOWN_TAG_KEYS, writePngWithTagsOnly, } from './xmp-bytes.js';
|
|
18
18
|
export interface EditableImageOptions {
|
|
19
19
|
/** Rendered image (screenshot + annotations) as Blob */
|
|
20
20
|
renderedBlob: Blob;
|
package/dist/xmp/xmp-bytes.d.ts
CHANGED
|
@@ -16,8 +16,37 @@
|
|
|
16
16
|
* segments (each prefixed with `annot:OriginalImage\0`).
|
|
17
17
|
*/
|
|
18
18
|
export declare function buildXmp(annotationsSvg: string, width: number, height: number, tags?: Record<string, string>): string;
|
|
19
|
+
export declare function u32be(n: number): Uint8Array;
|
|
20
|
+
export declare function readU32be(data: Uint8Array, offset: number): number;
|
|
21
|
+
export declare function concat(...arrays: Uint8Array[]): Uint8Array;
|
|
22
|
+
export declare function startsWith(data: Uint8Array, offset: number, prefix: Uint8Array): boolean;
|
|
19
23
|
export declare function dataUrlToUint8Array(dataUrl: string): Uint8Array;
|
|
20
24
|
export declare function writeJpegWithMetadata(jpegData: Uint8Array, xmpBytes: Uint8Array, originalData: Uint8Array): Uint8Array;
|
|
25
|
+
export declare function crc32(data: Uint8Array): number;
|
|
26
|
+
export declare function buildPngChunk(chunkType: Uint8Array, data: Uint8Array): Uint8Array;
|
|
27
|
+
/**
|
|
28
|
+
* Strip the Annot editor's editable-layer metadata from a PNG.
|
|
29
|
+
* Walks the chunk stream, drops the Adobe XMP iTXt chunk (the
|
|
30
|
+
* `<annot:annotations>` / `<annot:tags>` carrier) AND the custom
|
|
31
|
+
* `svGo` chunk (the original un-annotated bitmap), keeps every
|
|
32
|
+
* other chunk verbatim (including all critical chunks IHDR /
|
|
33
|
+
* IDAT / IEND so the result stays a valid PNG with the same
|
|
34
|
+
* visible pixels).
|
|
35
|
+
*
|
|
36
|
+
* Returns the input bytes unchanged when no editable-layer
|
|
37
|
+
* chunks are present.
|
|
38
|
+
*
|
|
39
|
+
* Used internally by `writePngWithMetadata` / `writePngWithTagsOnly`
|
|
40
|
+
* to clear stale metadata before re-injecting new chunks. Exposed
|
|
41
|
+
* since Phase 3j of `docs/plans/living-spec-authoring-roadmap.md`
|
|
42
|
+
* (Phase 3 follow-up #2) so `@ingcreators/annot-annotator` can
|
|
43
|
+
* publish a top-level `flattenEditablePng` primitive against the
|
|
44
|
+
* same logic without duplicating chunk-walking code.
|
|
45
|
+
*
|
|
46
|
+
* No re-rasterization — the visible bytes were already the
|
|
47
|
+
* annotated bitmap; this is metadata removal only.
|
|
48
|
+
*/
|
|
49
|
+
export declare function stripPngEditableLayer(data: Uint8Array): Uint8Array;
|
|
21
50
|
export declare function writePngWithMetadata(pngData: Uint8Array, xmpBytes: Uint8Array, originalData: Uint8Array): Uint8Array;
|
|
22
51
|
/**
|
|
23
52
|
* Build a tags-only XMP packet — `<annot:tags>` element present,
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
//#region src/xmp/xmp-bytes.ts
|
|
2
|
+
var e = "annot", t = "https://ingcreators.com/annot/ns/1.0/", n = new TextEncoder().encode("http://ns.adobe.com/xap/1.0/\0"), r = new TextEncoder().encode("annot:OriginalImage\0"), i = new TextEncoder().encode("XML:com.adobe.xmp");
|
|
3
|
+
function a(n, r, i, a) {
|
|
4
|
+
let o = a && Object.keys(a).length > 0 ? JSON.stringify(a) : "";
|
|
5
|
+
return `<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
|
|
6
|
+
<x:xmpmeta xmlns:x="adobe:ns:meta/">
|
|
7
|
+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
|
8
|
+
<rdf:Description rdf:about=""
|
|
9
|
+
xmlns:${e}="${t}">
|
|
10
|
+
<${e}:annotations><![CDATA[${n}]]></${e}:annotations>
|
|
11
|
+
<${e}:width>${r}</${e}:width>
|
|
12
|
+
<${e}:height>${i}</${e}:height>
|
|
13
|
+
<${e}:version>1.0</${e}:version>${o ? `\n <${e}:tags>${o}</${e}:tags>` : ""}
|
|
14
|
+
</rdf:Description>
|
|
15
|
+
</rdf:RDF>
|
|
16
|
+
</x:xmpmeta>
|
|
17
|
+
<?xpacket end="w"?>`;
|
|
18
|
+
}
|
|
19
|
+
function o(e) {
|
|
20
|
+
return new Uint8Array([e >> 8 & 255, e & 255]);
|
|
21
|
+
}
|
|
22
|
+
function s(e) {
|
|
23
|
+
return new Uint8Array([
|
|
24
|
+
e >> 24 & 255,
|
|
25
|
+
e >> 16 & 255,
|
|
26
|
+
e >> 8 & 255,
|
|
27
|
+
e & 255
|
|
28
|
+
]);
|
|
29
|
+
}
|
|
30
|
+
function c(e, t) {
|
|
31
|
+
return e[t] << 8 | e[t + 1];
|
|
32
|
+
}
|
|
33
|
+
function l(e, t) {
|
|
34
|
+
return (e[t] << 24 | e[t + 1] << 16 | e[t + 2] << 8 | e[t + 3]) >>> 0;
|
|
35
|
+
}
|
|
36
|
+
function u(...e) {
|
|
37
|
+
let t = 0;
|
|
38
|
+
for (let n of e) t += n.length;
|
|
39
|
+
let n = new Uint8Array(t), r = 0;
|
|
40
|
+
for (let t of e) n.set(t, r), r += t.length;
|
|
41
|
+
return n;
|
|
42
|
+
}
|
|
43
|
+
function d(e, t, n) {
|
|
44
|
+
if (t + n.length > e.length) return !1;
|
|
45
|
+
for (let r = 0; r < n.length; r++) if (e[t + r] !== n[r]) return !1;
|
|
46
|
+
return !0;
|
|
47
|
+
}
|
|
48
|
+
function f(e) {
|
|
49
|
+
let t = e.split(",")[1] || "", n = atob(t), r = new Uint8Array(n.length);
|
|
50
|
+
for (let e = 0; e < n.length; e++) r[e] = n.charCodeAt(e);
|
|
51
|
+
return r;
|
|
52
|
+
}
|
|
53
|
+
function p(e, t) {
|
|
54
|
+
let n = t.length + 2;
|
|
55
|
+
return u(new Uint8Array([255, e]), o(n), t);
|
|
56
|
+
}
|
|
57
|
+
function m(e) {
|
|
58
|
+
let t = 65533 - r.length - 4, n = Math.ceil(e.length / t), i = [];
|
|
59
|
+
for (let a = 0; a < n; a++) {
|
|
60
|
+
let s = a * t, c = Math.min(s + t, e.length), l = e.slice(s, c), d = u(r, o(a), o(n), l);
|
|
61
|
+
i.push(p(226, d));
|
|
62
|
+
}
|
|
63
|
+
return u(...i);
|
|
64
|
+
}
|
|
65
|
+
function h(e) {
|
|
66
|
+
let t = [e.slice(0, 2)], i = 2;
|
|
67
|
+
for (; i + 4 <= e.length && e[i] === 255;) {
|
|
68
|
+
let a = e[i + 1];
|
|
69
|
+
if (a === 217 || a === 218) return t.push(e.slice(i)), u(...t);
|
|
70
|
+
let o = c(e, i + 2), s = i + 2 + o;
|
|
71
|
+
if (s > e.length) break;
|
|
72
|
+
let l = a === 225 && d(e, i + 4, n), f = a === 226 && d(e, i + 4, r);
|
|
73
|
+
!l && !f && t.push(e.slice(i, s)), i = s;
|
|
74
|
+
}
|
|
75
|
+
return i < e.length && t.push(e.slice(i)), u(...t);
|
|
76
|
+
}
|
|
77
|
+
function g(e, t, r) {
|
|
78
|
+
let i = p(225, u(n, t)), a = m(r), o = h(e);
|
|
79
|
+
return u(o.slice(0, 2), i, a, o.slice(2));
|
|
80
|
+
}
|
|
81
|
+
var _ = (() => {
|
|
82
|
+
let e = /* @__PURE__ */ new Uint32Array(256);
|
|
83
|
+
for (let t = 0; t < 256; t++) {
|
|
84
|
+
let n = t;
|
|
85
|
+
for (let e = 0; e < 8; e++) n & 1 ? n = 3988292384 ^ n >>> 1 : n >>>= 1;
|
|
86
|
+
e[t] = n;
|
|
87
|
+
}
|
|
88
|
+
return e;
|
|
89
|
+
})();
|
|
90
|
+
function v(e) {
|
|
91
|
+
let t = 4294967295;
|
|
92
|
+
for (let n = 0; n < e.length; n++) t = _[(t ^ e[n]) & 255] ^ t >>> 8;
|
|
93
|
+
return (t ^ 4294967295) >>> 0;
|
|
94
|
+
}
|
|
95
|
+
function y(e, t) {
|
|
96
|
+
let n = u(e, t), r = v(n);
|
|
97
|
+
return u(s(t.length), n, s(r));
|
|
98
|
+
}
|
|
99
|
+
function b(e) {
|
|
100
|
+
let t = u(i, new Uint8Array([
|
|
101
|
+
0,
|
|
102
|
+
0,
|
|
103
|
+
0,
|
|
104
|
+
0,
|
|
105
|
+
0
|
|
106
|
+
]), e);
|
|
107
|
+
return y(new TextEncoder().encode("iTXt"), t);
|
|
108
|
+
}
|
|
109
|
+
function x(e) {
|
|
110
|
+
let t = [e.slice(0, 8)], n = 8;
|
|
111
|
+
for (; n + 12 <= e.length;) {
|
|
112
|
+
let r = l(e, n), a = e.slice(n + 4, n + 8), o = n + 8, s = o + r + 4;
|
|
113
|
+
if (s > e.length) break;
|
|
114
|
+
let c = String.fromCharCode(...a);
|
|
115
|
+
!(c === "iTXt" && d(e, o, i)) && c !== "svGo" && t.push(e.slice(n, s)), n = s;
|
|
116
|
+
}
|
|
117
|
+
return u(...t);
|
|
118
|
+
}
|
|
119
|
+
function S(e, t, n) {
|
|
120
|
+
let r = b(t), i = y(new TextEncoder().encode("svGo"), n), a = x(e), o = a.length - 12;
|
|
121
|
+
return u(a.slice(0, o), r, i, a.slice(o));
|
|
122
|
+
}
|
|
123
|
+
function C(n) {
|
|
124
|
+
return `<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
|
|
125
|
+
<x:xmpmeta xmlns:x="adobe:ns:meta/">
|
|
126
|
+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
|
127
|
+
<rdf:Description rdf:about=""
|
|
128
|
+
xmlns:${e}="${t}">
|
|
129
|
+
<${e}:tags>${JSON.stringify(n)}</${e}:tags>
|
|
130
|
+
</rdf:Description>
|
|
131
|
+
</rdf:RDF>
|
|
132
|
+
</x:xmpmeta>
|
|
133
|
+
<?xpacket end="w"?>`;
|
|
134
|
+
}
|
|
135
|
+
function w(e, t) {
|
|
136
|
+
if (!t || Object.keys(t).length === 0) return e;
|
|
137
|
+
let n = b(new TextEncoder().encode(C(t))), r = x(e), i = r.length - 12;
|
|
138
|
+
return u(r.slice(0, i), n, r.slice(i));
|
|
139
|
+
}
|
|
140
|
+
var T = [
|
|
141
|
+
"source",
|
|
142
|
+
"screen",
|
|
143
|
+
"capturedAt",
|
|
144
|
+
"commit"
|
|
145
|
+
];
|
|
146
|
+
function E(e) {
|
|
147
|
+
let t = a(e.annotationsSvg, e.width, e.height, e.tags), n = new TextEncoder().encode(t), r = typeof e.originalImage == "string" ? f(e.originalImage) : e.originalImage;
|
|
148
|
+
return S(e.renderedPng, n, r);
|
|
149
|
+
}
|
|
150
|
+
function D(e) {
|
|
151
|
+
return A(e) ? M(e) : null;
|
|
152
|
+
}
|
|
153
|
+
function O(e) {
|
|
154
|
+
return k(e) ? j(e) : A(e) ? M(e) : null;
|
|
155
|
+
}
|
|
156
|
+
function k(e) {
|
|
157
|
+
return e[0] === 255 && e[1] === 216;
|
|
158
|
+
}
|
|
159
|
+
function A(e) {
|
|
160
|
+
return e[0] === 137 && e[1] === 80 && e[2] === 78 && e[3] === 71;
|
|
161
|
+
}
|
|
162
|
+
function j(e) {
|
|
163
|
+
let t = F(e), n = I(e);
|
|
164
|
+
return t ? N(t, n) : null;
|
|
165
|
+
}
|
|
166
|
+
function M(e) {
|
|
167
|
+
let t = L(e), n = R(e);
|
|
168
|
+
return t ? N(t, n) : null;
|
|
169
|
+
}
|
|
170
|
+
function N(e, t) {
|
|
171
|
+
let n = P(e, "annotations");
|
|
172
|
+
if (!n) return null;
|
|
173
|
+
let r = n.replace(/^<!\[CDATA\[/, "").replace(/\]\]>$/, ""), i = Number.parseInt(P(e, "width") || "0", 10), a = Number.parseInt(P(e, "height") || "0", 10), o = "";
|
|
174
|
+
t && t.length > 0 && (o = `data:${t[0] === 137 && t[1] === 80 ? "image/png" : "image/jpeg"};base64,${z(t)}`);
|
|
175
|
+
let s = {}, c = P(e, "tags");
|
|
176
|
+
if (c) try {
|
|
177
|
+
s = JSON.parse(c);
|
|
178
|
+
} catch {}
|
|
179
|
+
return {
|
|
180
|
+
originalImageDataUrl: o,
|
|
181
|
+
annotationsSvg: r,
|
|
182
|
+
width: i,
|
|
183
|
+
height: a,
|
|
184
|
+
tags: s
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function P(t, n) {
|
|
188
|
+
let r = `<${e}:${n}>`, i = `</${e}:${n}>`, a = t.indexOf(r);
|
|
189
|
+
if (a < 0) return null;
|
|
190
|
+
let o = t.indexOf(i, a);
|
|
191
|
+
return o < 0 ? null : t.substring(a + r.length, o);
|
|
192
|
+
}
|
|
193
|
+
function F(e) {
|
|
194
|
+
let t = 2;
|
|
195
|
+
for (; t + 4 <= e.length && e[t] === 255;) {
|
|
196
|
+
let r = e[t + 1];
|
|
197
|
+
if (r === 217 || r === 218) break;
|
|
198
|
+
let i = c(e, t + 2), a = t + 2 + i;
|
|
199
|
+
if (a > e.length) break;
|
|
200
|
+
if (r === 225 && d(e, t + 4, n)) {
|
|
201
|
+
let r = t + 4 + n.length;
|
|
202
|
+
return new TextDecoder().decode(e.slice(r, a));
|
|
203
|
+
}
|
|
204
|
+
t = a;
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
function I(e) {
|
|
209
|
+
let t = r.length, n = [], i = 2;
|
|
210
|
+
for (; i + 4 <= e.length && e[i] === 255;) {
|
|
211
|
+
let a = e[i + 1];
|
|
212
|
+
if (a === 217 || a === 218) break;
|
|
213
|
+
let o = c(e, i + 2), s = i + 2 + o;
|
|
214
|
+
if (s > e.length) break;
|
|
215
|
+
if (a === 226 && d(e, i + 4, r)) {
|
|
216
|
+
let r = i + 4 + t;
|
|
217
|
+
if (r + 4 <= s) {
|
|
218
|
+
let t = c(e, r), i = e.slice(r + 4, s);
|
|
219
|
+
n.push({
|
|
220
|
+
seq: t,
|
|
221
|
+
data: i
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
i = s;
|
|
226
|
+
}
|
|
227
|
+
return n.length === 0 ? null : (n.sort((e, t) => e.seq - t.seq), u(...n.map((e) => e.data)));
|
|
228
|
+
}
|
|
229
|
+
function L(e) {
|
|
230
|
+
let t = 8;
|
|
231
|
+
for (; t + 12 <= e.length;) {
|
|
232
|
+
let n = l(e, t), r = String.fromCharCode(e[t + 4], e[t + 5], e[t + 6], e[t + 7]), a = t + 8, o = a + n + 4;
|
|
233
|
+
if (o > e.length) break;
|
|
234
|
+
if (r === "iTXt" && d(e, a, i)) {
|
|
235
|
+
let t = a + i.length, r = 0, o = t;
|
|
236
|
+
for (let i = t; i < a + n; i++) if (e[i] === 0 && r++, r >= 4) {
|
|
237
|
+
o = i + 1;
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
return new TextDecoder().decode(e.slice(o, a + n));
|
|
241
|
+
}
|
|
242
|
+
t = o;
|
|
243
|
+
}
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
function R(e) {
|
|
247
|
+
let t = 8;
|
|
248
|
+
for (; t + 12 <= e.length;) {
|
|
249
|
+
let n = l(e, t), r = String.fromCharCode(e[t + 4], e[t + 5], e[t + 6], e[t + 7]), i = t + 8, a = i + n + 4;
|
|
250
|
+
if (a > e.length) break;
|
|
251
|
+
if (r === "svGo") return e.slice(i, i + n);
|
|
252
|
+
t = a;
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
function z(e) {
|
|
257
|
+
let t = 32768, n = "";
|
|
258
|
+
for (let r = 0; r < e.length; r += t) {
|
|
259
|
+
let i = e.subarray(r, Math.min(r + t, e.length));
|
|
260
|
+
n += String.fromCharCode.apply(null, i);
|
|
261
|
+
}
|
|
262
|
+
return btoa(n);
|
|
263
|
+
}
|
|
264
|
+
//#endregion
|
|
265
|
+
export { T as WELL_KNOWN_TAG_KEYS, y as buildPngChunk, a as buildXmp, C as buildXmpTagsOnly, u as concat, v as crc32, E as createEditablePngBytes, f as dataUrlToUint8Array, O as readEditableImage, D as readEditablePngBytes, l as readU32be, d as startsWith, x as stripPngEditableLayer, s as u32be, g as writeJpegWithMetadata, S as writePngWithMetadata, w as writePngWithTagsOnly };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ingcreators/annot-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Annot SDK — SVG-first screenshot annotation format, storage abstractions, and editor primitives. The foundation shared by the PWA, Chrome extension, Electron desktop, VSCode extension, and the headless annotator.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "ingcreators",
|
|
@@ -30,6 +30,10 @@
|
|
|
30
30
|
"types": "./dist/index.d.ts",
|
|
31
31
|
"default": "./dist/index.js"
|
|
32
32
|
},
|
|
33
|
+
"./xmp-bytes": {
|
|
34
|
+
"types": "./dist/xmp/xmp-bytes.d.ts",
|
|
35
|
+
"default": "./dist/xmp-bytes.js"
|
|
36
|
+
},
|
|
33
37
|
"./styles/*": "./styles/*"
|
|
34
38
|
},
|
|
35
39
|
"files": [
|
|
@@ -42,12 +46,14 @@
|
|
|
42
46
|
"access": "public"
|
|
43
47
|
},
|
|
44
48
|
"devDependencies": {
|
|
49
|
+
"@types/js-yaml": "^4.0.9",
|
|
45
50
|
"@types/pako": "^2.0.4",
|
|
46
51
|
"typescript": "^6.0.3",
|
|
47
|
-
"vite": "^8.0.
|
|
48
|
-
"vite-plugin-dts": "^5.0.
|
|
52
|
+
"vite": "^8.0.16",
|
|
53
|
+
"vite-plugin-dts": "^5.0.3"
|
|
49
54
|
},
|
|
50
55
|
"dependencies": {
|
|
56
|
+
"js-yaml": "^4.1.1",
|
|
51
57
|
"pako": "^2.1.0"
|
|
52
58
|
},
|
|
53
59
|
"scripts": {
|
package/styles/editor.css
CHANGED
|
@@ -179,9 +179,12 @@ body {
|
|
|
179
179
|
|
|
180
180
|
/* The contentEditable overlay lives inside a `<foreignObject>` —
|
|
181
181
|
bring text selection back so the user can mark / replace runs
|
|
182
|
-
while editing.
|
|
183
|
-
|
|
184
|
-
[data-annot-shell-root]
|
|
182
|
+
while editing. Selectors ordered ascending-by-specificity so
|
|
183
|
+
Biome's `noDescendingSpecificity` rule is satisfied:
|
|
184
|
+
`[data-annot-shell-root]` is (0,2,1); `#svg-root` is (1,1,1).
|
|
185
|
+
Same applied rules either way. */
|
|
186
|
+
[data-annot-shell-root] foreignObject [contenteditable="true"],
|
|
187
|
+
#svg-root foreignObject [contenteditable="true"] {
|
|
185
188
|
user-select: text;
|
|
186
189
|
-webkit-user-select: text;
|
|
187
190
|
}
|