@image-marker/web 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +100 -0
  3. package/api-contract.json +224 -0
  4. package/lib/commonjs/core/batch.d.ts +32 -0
  5. package/lib/commonjs/core/batch.js +85 -0
  6. package/lib/commonjs/core/content-credentials.d.ts +53 -0
  7. package/lib/commonjs/core/content-credentials.js +96 -0
  8. package/lib/commonjs/core/image-info.d.ts +30 -0
  9. package/lib/commonjs/core/image-info.js +348 -0
  10. package/lib/commonjs/core/index.d.ts +1014 -0
  11. package/lib/commonjs/core/index.js +92 -0
  12. package/lib/commonjs/core/invisible-watermark.d.ts +102 -0
  13. package/lib/commonjs/core/invisible-watermark.js +913 -0
  14. package/lib/commonjs/core/job.d.ts +57 -0
  15. package/lib/commonjs/core/job.js +151 -0
  16. package/lib/commonjs/core/layout.d.ts +19 -0
  17. package/lib/commonjs/core/layout.js +75 -0
  18. package/lib/commonjs/core/marker.d.ts +1 -0
  19. package/lib/commonjs/core/marker.js +11 -0
  20. package/lib/commonjs/core/recipe.d.ts +44 -0
  21. package/lib/commonjs/core/recipe.js +175 -0
  22. package/lib/commonjs/core/result.d.ts +34 -0
  23. package/lib/commonjs/core/result.js +41 -0
  24. package/lib/commonjs/core/trace-runtime.d.ts +43 -0
  25. package/lib/commonjs/core/trace-runtime.js +92 -0
  26. package/lib/commonjs/core/validate.d.ts +4 -0
  27. package/lib/commonjs/core/validate.js +192 -0
  28. package/lib/commonjs/core/web/browser.d.ts +105 -0
  29. package/lib/commonjs/core/web/browser.js +434 -0
  30. package/lib/commonjs/core/web/helpers.d.ts +41 -0
  31. package/lib/commonjs/core/web/helpers.js +264 -0
  32. package/lib/commonjs/core/web/index.d.ts +59 -0
  33. package/lib/commonjs/core/web/index.js +724 -0
  34. package/lib/commonjs/core/web/invisible-worker-client.d.ts +13 -0
  35. package/lib/commonjs/core/web/invisible-worker-client.js +140 -0
  36. package/lib/commonjs/core/web/renderer.d.ts +20 -0
  37. package/lib/commonjs/core/web/renderer.js +665 -0
  38. package/lib/commonjs/core/web/runtime.d.ts +106 -0
  39. package/lib/commonjs/core/web/runtime.js +2 -0
  40. package/lib/commonjs/editor/adapter.d.ts +26 -0
  41. package/lib/commonjs/editor/adapter.js +102 -0
  42. package/lib/commonjs/editor/controller.d.ts +106 -0
  43. package/lib/commonjs/editor/controller.js +919 -0
  44. package/lib/commonjs/editor/core-contract.d.ts +2 -0
  45. package/lib/commonjs/editor/core-contract.js +23 -0
  46. package/lib/commonjs/editor/geometry.d.ts +8 -0
  47. package/lib/commonjs/editor/geometry.js +109 -0
  48. package/lib/commonjs/editor/headless.d.ts +5 -0
  49. package/lib/commonjs/editor/headless.js +22 -0
  50. package/lib/commonjs/editor/presets.d.ts +19 -0
  51. package/lib/commonjs/editor/presets.js +64 -0
  52. package/lib/commonjs/editor/projection.d.ts +12 -0
  53. package/lib/commonjs/editor/projection.js +195 -0
  54. package/lib/commonjs/editor/types.d.ts +166 -0
  55. package/lib/commonjs/editor/types.js +2 -0
  56. package/lib/commonjs/editor-adapter.d.ts +7 -0
  57. package/lib/commonjs/editor-adapter.js +17 -0
  58. package/lib/commonjs/headless.d.ts +2 -0
  59. package/lib/commonjs/headless.js +26 -0
  60. package/lib/commonjs/index.d.ts +5 -0
  61. package/lib/commonjs/index.js +27 -0
  62. package/lib/commonjs/package.json +1 -0
  63. package/lib/module/core/batch.d.ts +32 -0
  64. package/lib/module/core/batch.js +82 -0
  65. package/lib/module/core/content-credentials.d.ts +53 -0
  66. package/lib/module/core/content-credentials.js +92 -0
  67. package/lib/module/core/image-info.d.ts +30 -0
  68. package/lib/module/core/image-info.js +343 -0
  69. package/lib/module/core/index.d.ts +1014 -0
  70. package/lib/module/core/index.js +56 -0
  71. package/lib/module/core/invisible-watermark.d.ts +102 -0
  72. package/lib/module/core/invisible-watermark.js +897 -0
  73. package/lib/module/core/job.d.ts +57 -0
  74. package/lib/module/core/job.js +143 -0
  75. package/lib/module/core/layout.d.ts +19 -0
  76. package/lib/module/core/layout.js +70 -0
  77. package/lib/module/core/marker.d.ts +1 -0
  78. package/lib/module/core/marker.js +4 -0
  79. package/lib/module/core/recipe.d.ts +44 -0
  80. package/lib/module/core/recipe.js +169 -0
  81. package/lib/module/core/result.d.ts +34 -0
  82. package/lib/module/core/result.js +38 -0
  83. package/lib/module/core/trace-runtime.d.ts +43 -0
  84. package/lib/module/core/trace-runtime.js +89 -0
  85. package/lib/module/core/validate.d.ts +4 -0
  86. package/lib/module/core/validate.js +187 -0
  87. package/lib/module/core/web/browser.d.ts +105 -0
  88. package/lib/module/core/web/browser.js +427 -0
  89. package/lib/module/core/web/helpers.d.ts +41 -0
  90. package/lib/module/core/web/helpers.js +251 -0
  91. package/lib/module/core/web/index.d.ts +59 -0
  92. package/lib/module/core/web/index.js +709 -0
  93. package/lib/module/core/web/invisible-worker-client.d.ts +13 -0
  94. package/lib/module/core/web/invisible-worker-client.js +137 -0
  95. package/lib/module/core/web/renderer.d.ts +20 -0
  96. package/lib/module/core/web/renderer.js +661 -0
  97. package/lib/module/core/web/runtime.d.ts +106 -0
  98. package/lib/module/core/web/runtime.js +1 -0
  99. package/lib/module/editor/adapter.d.ts +26 -0
  100. package/lib/module/editor/adapter.js +99 -0
  101. package/lib/module/editor/controller.d.ts +106 -0
  102. package/lib/module/editor/controller.js +915 -0
  103. package/lib/module/editor/core-contract.d.ts +2 -0
  104. package/lib/module/editor/core-contract.js +2 -0
  105. package/lib/module/editor/geometry.d.ts +8 -0
  106. package/lib/module/editor/geometry.js +103 -0
  107. package/lib/module/editor/headless.d.ts +5 -0
  108. package/lib/module/editor/headless.js +4 -0
  109. package/lib/module/editor/presets.d.ts +19 -0
  110. package/lib/module/editor/presets.js +57 -0
  111. package/lib/module/editor/projection.d.ts +12 -0
  112. package/lib/module/editor/projection.js +188 -0
  113. package/lib/module/editor/types.d.ts +166 -0
  114. package/lib/module/editor/types.js +1 -0
  115. package/lib/module/editor-adapter.d.ts +7 -0
  116. package/lib/module/editor-adapter.js +10 -0
  117. package/lib/module/headless.d.ts +2 -0
  118. package/lib/module/headless.js +2 -0
  119. package/lib/module/index.d.ts +5 -0
  120. package/lib/module/index.js +3 -0
  121. package/lib/module/package.json +1 -0
  122. package/lib/source-manifest.json +34 -0
  123. package/lib/worker/invisible-watermark.js +1 -0
  124. package/package.json +85 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2026-08-31)
4
+
5
+ - Added the standalone `@image-marker/web` package for browser rendering with
6
+ File, Blob, URL, data URL, and loaded image inputs.
7
+ - Added public `headless` and `editor-adapter` entries for custom editor UIs
8
+ without React Native dependencies.
9
+ - Added a packaged invisible-watermark Worker resource and Vite `?url` usage.
10
+ - Documented cooperative cancellation, host-owned termination, URL/CORS policy,
11
+ local file handling, and optional C2PA integration boundaries.
package/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # `@image-marker/web`
2
+
3
+ Standalone browser SDK for Image Marker. It runs with Canvas 2D and Web APIs and
4
+ does not install React Native, a Node sidecar, or native modules.
5
+
6
+ ```sh
7
+ npm install @image-marker/web@0.1.0
8
+ ```
9
+
10
+ ## Quick start
11
+
12
+ ```ts
13
+ import Marker, { ImageFormat, Position } from '@image-marker/web';
14
+
15
+ const result = await Marker.markText({
16
+ backgroundImage: { src: file }, // File, Blob, data URL, URL, or loaded image
17
+ watermarkTexts: [
18
+ {
19
+ text: '© Acme',
20
+ position: { position: Position.bottomRight, X: 24, Y: 24 },
21
+ style: { color: '#FFFFFF', fontSize: 28 },
22
+ },
23
+ ],
24
+ saveFormat: ImageFormat.png,
25
+ });
26
+
27
+ preview.src = result.uri;
28
+ ```
29
+
30
+ The root entry exports the Web `Marker`, the existing public enums and types,
31
+ Recipe v2 helpers, visible watermark methods, and invisible watermark
32
+ embed/detect methods. Web results expose a data URL in `result.uri`.
33
+
34
+ ## Public subpaths
35
+
36
+ - `@image-marker/web/headless` exports `ImageMarkerEditorController`, geometry,
37
+ projection, presets, and editor types without React or React Native UI.
38
+ - `@image-marker/web/editor-adapter` exports `createWebEditorAdapter` and
39
+ `resolveWebEditorSourceInfo`. The adapter connects a headless Recipe to Canvas
40
+ preview and original export.
41
+ - `@image-marker/web/worker` and
42
+ `@image-marker/web/worker/invisible-watermark.js` resolve to the packaged
43
+ invisible-watermark Worker. Vite can resolve it with
44
+ `import workerUrl from '@image-marker/web/worker?url'`.
45
+
46
+ ```ts
47
+ import { ImageMarkerEditorController } from '@image-marker/web/headless';
48
+ import { createWebEditorAdapter } from '@image-marker/web/editor-adapter';
49
+
50
+ const controller = new ImageMarkerEditorController({
51
+ document: { schemaVersion: 2, layers, output: { saveFormat: 'png' } },
52
+ });
53
+ const adapter = createWebEditorAdapter(1024);
54
+ const preview = await adapter.renderPreview({
55
+ recipe: controller.exportRecipe(),
56
+ input: { backgroundImage: { src: file } },
57
+ });
58
+ ```
59
+
60
+ ## Cancellation and ownership
61
+
62
+ Pass an `AbortSignal` to Recipe jobs or Worker detection. Abort stops queued and
63
+ cooperative work; synchronous Canvas work already in progress may only be
64
+ discarded when it returns. A host that needs immediate CPU termination must stop
65
+ the Worker or task it owns. Promise timeouts and ignoring late values do not stop
66
+ work. Revoke object URLs and dispose controllers/listeners when their UI or task
67
+ ends.
68
+
69
+ For a Tauri or other isolated host, use `createWebMarker(options?)` to inject
70
+ `resources` (`createImage`, `createCanvas`, object-URL cleanup, and byte reads)
71
+ and an `execution` adapter. `execution.start()` returns the real task promise
72
+ plus optional `terminate`/`cancel` and `dispose` hooks. `marker.cancel()` and
73
+ `marker.dispose()` wait for the host acknowledgement; a timeout or ignored
74
+ late value is not termination. Inspect `marker.capabilities` to distinguish
75
+ cooperative Worker detection from default DOM work that requires host
76
+ termination.
77
+
78
+ Create Recipes from that instance when its cancellation boundary should include
79
+ the Recipe task:
80
+
81
+ ```ts
82
+ import { createWebMarker } from '@image-marker/web';
83
+
84
+ const marker = createWebMarker();
85
+ const recipe = marker.createRecipe(recipeDocument, { resultType: 'blob' });
86
+ const pending = recipe.apply(input, { signal: abortController.signal });
87
+ const completion = pending.catch(() => undefined);
88
+ abortController.abort();
89
+ await marker.cancel();
90
+ await completion;
91
+ await marker.dispose();
92
+ ```
93
+
94
+ The packaged Worker handles invisible watermark detection only. It is not a
95
+ general Canvas image engine. Remote URLs remain supported when the remote server
96
+ allows CORS; local `File` and `Blob` inputs are recommended for desktop apps.
97
+ C2PA is an optional integration boundary and is not installed by this package.
98
+
99
+ See the [Web SDK guide](../../docs/web-sdk.md) and the runnable
100
+ [Vite example](../../examples/web-sdk/).
@@ -0,0 +1,224 @@
1
+ {
2
+ "packageExports": [
3
+ ".",
4
+ "./headless",
5
+ "./editor-adapter",
6
+ "./worker",
7
+ "./worker/invisible-watermark.js",
8
+ "./package.json"
9
+ ],
10
+ "dependencies": {
11
+ "@image-marker/recipe": "^0.1.0"
12
+ },
13
+ "peerDependencies": {},
14
+ "entries": {
15
+ ".": {
16
+ "values": [
17
+ "INVISIBLE_WATERMARK_ALGORITHM",
18
+ "INVISIBLE_WATERMARK_MAX_PAYLOAD_BYTES",
19
+ "INVISIBLE_WATERMARK_MIN_HEIGHT",
20
+ "INVISIBLE_WATERMARK_MIN_KEY_BYTES",
21
+ "INVISIBLE_WATERMARK_MIN_WIDTH",
22
+ "INVISIBLE_WATERMARK_RESIZE_SCALES",
23
+ "ImageFormat",
24
+ "ImageMarkerError",
25
+ "Marker",
26
+ "Position",
27
+ "RotationCanvasMode",
28
+ "TextBackgroundType",
29
+ "WATERMARK_RECIPE_SCHEMA_VERSION",
30
+ "WatermarkRecipeValidationError",
31
+ "WebMarker",
32
+ "addWatermarkRecipeLayer",
33
+ "cloneRecipeValue",
34
+ "cloneWatermarkRecipe",
35
+ "cloneWatermarkRecipeLayer",
36
+ "createWatermarkRecipeDefinition",
37
+ "createWebMarker",
38
+ "default",
39
+ "duplicateWatermarkRecipeLayer",
40
+ "groupWatermarkRecipeLayers",
41
+ "materializeWatermarkRecipe",
42
+ "migrateWatermarkRecipe",
43
+ "parseEncodedImageInfo",
44
+ "parseWatermarkRecipe",
45
+ "recipeValidationPatterns",
46
+ "removeWatermarkRecipeLayer",
47
+ "reorderWatermarkRecipeLayer",
48
+ "resolveWatermarkTemplate",
49
+ "safeValidateWatermarkRecipe",
50
+ "serializeWatermarkRecipe",
51
+ "ungroupWatermarkRecipeLayers",
52
+ "updateWatermarkRecipeLayer",
53
+ "validateWatermarkRecipe",
54
+ "validateWatermarkTemplate"
55
+ ],
56
+ "types": [
57
+ "BlendMode",
58
+ "ContentCredentialsAdapter",
59
+ "ContentCredentialsClaim",
60
+ "ContentCredentialsSignRequest",
61
+ "ContentCredentialsSignResult",
62
+ "ContentCredentialsVerificationResult",
63
+ "CornerRadius",
64
+ "DetectInvisibleWatermarkOptions",
65
+ "EmbedInvisibleWatermarkOptions",
66
+ "EmbedInvisibleWithCredentialsOptions",
67
+ "EmbedInvisibleWithCredentialsResult",
68
+ "ImageMarkOptions",
69
+ "ImageMarkerErrorCode",
70
+ "ImageOptions",
71
+ "ImageOrientation",
72
+ "ImageWatermarkLayer",
73
+ "InvisibleWatermarkCodec",
74
+ "InvisibleWatermarkDecodedImage",
75
+ "InvisibleWatermarkDetectionData",
76
+ "InvisibleWatermarkDetectionResult",
77
+ "InvisibleWatermarkImage",
78
+ "InvisibleWatermarkRuntime",
79
+ "InvisibleWatermarkRuntimeOptions",
80
+ "InvisibleWatermarkSearch",
81
+ "InvisibleWatermarkStrength",
82
+ "InvisibleWatermarkWorkerOptions",
83
+ "InvisibleWatermarkWorkerProgressPhase",
84
+ "LegacyWatermarkRecipeDefinition",
85
+ "MarkOptions",
86
+ "MarkerImageFormat",
87
+ "MarkerImageInfo",
88
+ "MarkerJobOptions",
89
+ "MarkerJobPhase",
90
+ "MarkerJobProgress",
91
+ "MarkerMetadataResult",
92
+ "MarkerOperation",
93
+ "MarkerOutputFormat",
94
+ "MarkerResult",
95
+ "MarkerResultDescriptor",
96
+ "PositionOptions",
97
+ "RadiusValue",
98
+ "RuntimeDetectInvisibleWatermarkOptions",
99
+ "RuntimeEmbedInvisibleWatermarkOptions",
100
+ "ShadowLayerStyle",
101
+ "TextBackgroundStyle",
102
+ "TextMarkOptions",
103
+ "TextOptions",
104
+ "TextStrokeStyle",
105
+ "TextStyle",
106
+ "TextWatermarkLayer",
107
+ "VerifyContentCredentialsOptions",
108
+ "WatermarkBatchAbortedResult",
109
+ "WatermarkBatchFulfilledResult",
110
+ "WatermarkBatchOptions",
111
+ "WatermarkBatchProgress",
112
+ "WatermarkBatchRejectedResult",
113
+ "WatermarkBatchResult",
114
+ "WatermarkBlobRecipeResultOptions",
115
+ "WatermarkImageOptions",
116
+ "WatermarkLayer",
117
+ "WatermarkLayout",
118
+ "WatermarkMarkerResultRecipeOptions",
119
+ "WatermarkRecipe",
120
+ "WatermarkRecipeCondition",
121
+ "WatermarkRecipeDefinition",
122
+ "WatermarkRecipeDefinitionLayer",
123
+ "WatermarkRecipeDocument",
124
+ "WatermarkRecipeInput",
125
+ "WatermarkRecipeLayer",
126
+ "WatermarkRecipeOptions",
127
+ "WatermarkRecipeOutputOptions",
128
+ "WatermarkRecipeResultOptions",
129
+ "WatermarkRecipeSchemaVersion",
130
+ "WatermarkRecipeValidationFailure",
131
+ "WatermarkRecipeValidationResult",
132
+ "WatermarkRecipeValidationSuccess",
133
+ "WatermarkRecipeVariable",
134
+ "WatermarkRecipeVariableContext",
135
+ "WatermarkTextDirection",
136
+ "WatermarkTextOverflow",
137
+ "WatermarkTextWrap",
138
+ "WebMarkerCancellationMode",
139
+ "WebMarkerCapabilities",
140
+ "WebMarkerExecutionAdapter",
141
+ "WebMarkerExecutionOperation",
142
+ "WebMarkerExecutionRequest",
143
+ "WebMarkerExecutionResultKind",
144
+ "WebMarkerExecutionTask",
145
+ "WebMarkerExecutionTermination",
146
+ "WebMarkerInstance",
147
+ "WebMarkerRuntimeOptions",
148
+ "WebResourceAdapter"
149
+ ]
150
+ },
151
+ "./headless": {
152
+ "values": [
153
+ "ImageMarkerEditorController",
154
+ "addWatermarkRecipeLayer",
155
+ "angle",
156
+ "createBrandLogoLayer",
157
+ "createBrandTextLayer",
158
+ "createEditorBrandKit",
159
+ "createEditorTemplate",
160
+ "createEditorViewportProjection",
161
+ "distance",
162
+ "duplicateWatermarkRecipeLayer",
163
+ "fitEditorSizeWithinMax",
164
+ "groupWatermarkRecipeLayers",
165
+ "materializeEditorTemplate",
166
+ "normalizeSafeArea",
167
+ "projectEditorPoint",
168
+ "projectEditorRecipe",
169
+ "removeWatermarkRecipeLayer",
170
+ "reorderWatermarkRecipeLayer",
171
+ "snapLayerPosition",
172
+ "ungroupWatermarkRecipeLayers",
173
+ "unprojectEditorPoint",
174
+ "updateWatermarkRecipeLayer"
175
+ ],
176
+ "types": [
177
+ "EditorAlignment",
178
+ "EditorAsset",
179
+ "EditorAutosaveOptions",
180
+ "EditorBrandKit",
181
+ "EditorClipboardDocument",
182
+ "EditorContentCredentialsExport",
183
+ "EditorDistribution",
184
+ "EditorExportOptions",
185
+ "EditorExportResult",
186
+ "EditorInvisibleExport",
187
+ "EditorKeyCommand",
188
+ "EditorLayerBounds",
189
+ "EditorPoint",
190
+ "EditorRect",
191
+ "EditorRenderRequest",
192
+ "EditorSafeArea",
193
+ "EditorSelectionMode",
194
+ "EditorSerializedState",
195
+ "EditorSize",
196
+ "EditorSnapContext",
197
+ "EditorSnapGuide",
198
+ "EditorState",
199
+ "EditorTemplate",
200
+ "EditorTextLayerPatch",
201
+ "EditorViewportProjection",
202
+ "EditorViewportState",
203
+ "ImageMarkerEditorControllerOptions",
204
+ "ImageMarkerEditorRenderAdapter",
205
+ "MarkerImageInfo",
206
+ "WatermarkBlendMode",
207
+ "WatermarkRecipeDefinition",
208
+ "WatermarkRecipeDefinitionLayer",
209
+ "WatermarkRecipeDocument",
210
+ "WatermarkRecipeInput",
211
+ "WatermarkTextStyle"
212
+ ]
213
+ },
214
+ "./editor-adapter": {
215
+ "values": [
216
+ "createWebEditorAdapter",
217
+ "resolveWebEditorSourceInfo"
218
+ ],
219
+ "types": [
220
+ "EditorMarkerEngine"
221
+ ]
222
+ }
223
+ }
224
+ }
@@ -0,0 +1,32 @@
1
+ export interface WatermarkBatchFulfilledResult<Result> {
2
+ status: 'fulfilled';
3
+ value: Result;
4
+ }
5
+ export interface WatermarkBatchRejectedResult {
6
+ status: 'rejected';
7
+ reason: unknown;
8
+ }
9
+ export interface WatermarkBatchAbortedResult {
10
+ status: 'aborted';
11
+ reason: Error;
12
+ }
13
+ export type WatermarkBatchResult<Result> = WatermarkBatchFulfilledResult<Result> | WatermarkBatchRejectedResult | WatermarkBatchAbortedResult;
14
+ export interface WatermarkBatchProgress<Result> {
15
+ total: number;
16
+ settled: number;
17
+ succeeded: number;
18
+ failed: number;
19
+ aborted: number;
20
+ /** Index of the item that produced this progress update. */
21
+ index: number;
22
+ result: WatermarkBatchResult<Result>;
23
+ }
24
+ export interface WatermarkBatchOptions<Result> {
25
+ /** Requested worker count. Web is capped at 4 and native targets at 1. */
26
+ concurrency?: number;
27
+ /** Stops new items from starting. Already-running items are allowed to finish. */
28
+ signal?: AbortSignal;
29
+ /** Called once when each item is fulfilled, rejected, or skipped after abort. */
30
+ onProgress?: (progress: WatermarkBatchProgress<Result>) => void;
31
+ }
32
+ export declare function runWatermarkBatch<Input, Result>(inputs: readonly Input[], task: (input: Input, index: number) => Promise<Result>, batchOptions?: WatermarkBatchOptions<Result>, maximumConcurrency?: number, inputLabel?: string): Promise<Array<WatermarkBatchResult<Result>>>;
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runWatermarkBatch = runWatermarkBatch;
4
+ function abortResult() {
5
+ const reason = new Error('Batch item was not started because the operation was aborted.');
6
+ reason.name = 'AbortError';
7
+ return { status: 'aborted', reason };
8
+ }
9
+ async function runWatermarkBatch(inputs, task, batchOptions = {}, maximumConcurrency = 1, inputLabel = 'batch') {
10
+ if (!Array.isArray(inputs)) {
11
+ throw new Error(`${inputLabel} inputs must be an array.`);
12
+ }
13
+ const requestedConcurrency = batchOptions.concurrency ?? 1;
14
+ if (!Number.isFinite(requestedConcurrency) ||
15
+ !Number.isInteger(requestedConcurrency) ||
16
+ requestedConcurrency <= 0) {
17
+ throw new Error('concurrency must be a positive finite integer.');
18
+ }
19
+ if (inputs.length === 0) {
20
+ return [];
21
+ }
22
+ const concurrency = Math.min(requestedConcurrency, maximumConcurrency, inputs.length);
23
+ const results = new Array(inputs.length);
24
+ let cursor = 0;
25
+ let settled = 0;
26
+ let succeeded = 0;
27
+ let failed = 0;
28
+ let aborted = 0;
29
+ const report = (index, result) => {
30
+ settled += 1;
31
+ if (result.status === 'fulfilled')
32
+ succeeded += 1;
33
+ else if (result.status === 'rejected')
34
+ failed += 1;
35
+ else
36
+ aborted += 1;
37
+ try {
38
+ batchOptions.onProgress?.({
39
+ total: inputs.length,
40
+ settled,
41
+ succeeded,
42
+ failed,
43
+ aborted,
44
+ index,
45
+ result,
46
+ });
47
+ }
48
+ catch {
49
+ // Progress observers must not interrupt the batch or change results.
50
+ }
51
+ };
52
+ const worker = async () => {
53
+ while (!batchOptions.signal?.aborted) {
54
+ const index = cursor;
55
+ cursor += 1;
56
+ if (index >= inputs.length) {
57
+ return;
58
+ }
59
+ if (batchOptions.signal?.aborted) {
60
+ return;
61
+ }
62
+ let result;
63
+ try {
64
+ result = {
65
+ status: 'fulfilled',
66
+ value: await task(inputs[index], index),
67
+ };
68
+ }
69
+ catch (reason) {
70
+ result = { status: 'rejected', reason };
71
+ }
72
+ results[index] = result;
73
+ report(index, result);
74
+ }
75
+ };
76
+ await Promise.all(Array.from({ length: concurrency }, worker));
77
+ for (let index = 0; index < inputs.length; index += 1) {
78
+ if (!results[index]) {
79
+ const result = abortResult();
80
+ results[index] = result;
81
+ report(index, result);
82
+ }
83
+ }
84
+ return results;
85
+ }
@@ -0,0 +1,53 @@
1
+ import type { EmbedInvisibleWatermarkOptions } from './invisible-watermark.js';
2
+ import { INVISIBLE_WATERMARK_ALGORITHM } from './invisible-watermark.js';
3
+ import type { MarkerResult } from './result.js';
4
+ export interface ContentCredentialsClaim {
5
+ /** Human-readable asset title included in the signed manifest. */
6
+ title: string;
7
+ /** Encoded image type. WebP is supported when the selected adapter accepts it. */
8
+ format?: 'image/jpeg' | 'image/png' | 'image/webp';
9
+ /** Claim generator name shown by Content Credentials readers. */
10
+ generator?: string;
11
+ /** Adapter-specific public metadata. Never include signing secrets. */
12
+ metadata?: Readonly<Record<string, unknown>>;
13
+ }
14
+ export interface ContentCredentialsSignRequest {
15
+ /** Image already containing the invisible locator. */
16
+ image: string;
17
+ /** Locator used to correlate the signed record. */
18
+ locator: string;
19
+ /** Pixel algorithm that wrote the locator. */
20
+ algorithm: typeof INVISIBLE_WATERMARK_ALGORITHM;
21
+ claim: ContentCredentialsClaim;
22
+ }
23
+ export interface ContentCredentialsSignResult {
24
+ /** Image containing the signed Content Credential. */
25
+ image: string;
26
+ manifestId?: string;
27
+ }
28
+ export interface ContentCredentialsVerificationResult {
29
+ valid: boolean;
30
+ manifestId?: string;
31
+ validationStatus?: readonly string[];
32
+ manifest?: unknown;
33
+ }
34
+ export interface ContentCredentialsAdapter {
35
+ sign(request: ContentCredentialsSignRequest): Promise<ContentCredentialsSignResult>;
36
+ verify(image: string): Promise<ContentCredentialsVerificationResult>;
37
+ }
38
+ export interface EmbedInvisibleWithCredentialsOptions {
39
+ watermark: EmbedInvisibleWatermarkOptions;
40
+ adapter: ContentCredentialsAdapter;
41
+ claim: ContentCredentialsClaim;
42
+ }
43
+ export interface EmbedInvisibleWithCredentialsResult {
44
+ watermarkedImage: MarkerResult;
45
+ signedImage: string;
46
+ manifestId?: string;
47
+ }
48
+ export interface VerifyContentCredentialsOptions {
49
+ image: string;
50
+ adapter: ContentCredentialsAdapter;
51
+ }
52
+ export declare function embedInvisibleWithCredentials(embed: (options: EmbedInvisibleWatermarkOptions) => Promise<MarkerResult>, options: EmbedInvisibleWithCredentialsOptions): Promise<EmbedInvisibleWithCredentialsResult>;
53
+ export declare function verifyContentCredentials(options: VerifyContentCredentialsOptions): Promise<ContentCredentialsVerificationResult>;
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.embedInvisibleWithCredentials = embedInvisibleWithCredentials;
4
+ exports.verifyContentCredentials = verifyContentCredentials;
5
+ const invisible_watermark_js_1 = require("./invisible-watermark.js");
6
+ function validateAdapter(adapter) {
7
+ if (!adapter ||
8
+ typeof adapter.sign !== 'function' ||
9
+ typeof adapter.verify !== 'function') {
10
+ throw new Error('Content Credentials adapter must provide sign() and verify() functions.');
11
+ }
12
+ return adapter;
13
+ }
14
+ function snapshotClaim(claim) {
15
+ if (!claim || typeof claim.title !== 'string' || !claim.title.trim()) {
16
+ throw new Error('Content Credentials claim title must not be empty.');
17
+ }
18
+ if (claim.format !== undefined &&
19
+ claim.format !== 'image/jpeg' &&
20
+ claim.format !== 'image/png' &&
21
+ claim.format !== 'image/webp') {
22
+ throw new Error('Content Credentials claim format must be image/jpeg, image/png, or image/webp.');
23
+ }
24
+ if (claim.generator !== undefined &&
25
+ (typeof claim.generator !== 'string' || !claim.generator.trim())) {
26
+ throw new Error('Content Credentials claim generator must be a non-empty string.');
27
+ }
28
+ if (claim.metadata !== undefined &&
29
+ (!claim.metadata ||
30
+ typeof claim.metadata !== 'object' ||
31
+ Array.isArray(claim.metadata))) {
32
+ throw new Error('Content Credentials claim metadata must be an object.');
33
+ }
34
+ return {
35
+ title: claim.title.trim(),
36
+ format: claim.format,
37
+ generator: claim.generator?.trim(),
38
+ metadata: claim.metadata ? { ...claim.metadata } : undefined,
39
+ };
40
+ }
41
+ function validateSignResult(result) {
42
+ if (!result || typeof result.image !== 'string' || !result.image) {
43
+ throw new Error('Content Credentials adapter returned an invalid signed image.');
44
+ }
45
+ if (result.manifestId !== undefined &&
46
+ typeof result.manifestId !== 'string') {
47
+ throw new Error('Content Credentials adapter returned an invalid manifest ID.');
48
+ }
49
+ return result;
50
+ }
51
+ function validateVerificationResult(result) {
52
+ if (!result || typeof result.valid !== 'boolean') {
53
+ throw new Error('Content Credentials adapter returned an invalid verification result.');
54
+ }
55
+ if (result.manifestId !== undefined &&
56
+ typeof result.manifestId !== 'string') {
57
+ throw new Error('Content Credentials adapter returned an invalid manifest ID.');
58
+ }
59
+ if (result.validationStatus !== undefined &&
60
+ (!Array.isArray(result.validationStatus) ||
61
+ result.validationStatus.some((status) => typeof status !== 'string'))) {
62
+ throw new Error('Content Credentials adapter returned invalid validation status data.');
63
+ }
64
+ return result;
65
+ }
66
+ async function embedInvisibleWithCredentials(embed, options) {
67
+ const adapter = validateAdapter(options?.adapter);
68
+ const claim = snapshotClaim(options?.claim);
69
+ const watermark = {
70
+ ...options.watermark,
71
+ image: { ...options.watermark?.image },
72
+ };
73
+ const locator = watermark.payload;
74
+ const watermarkedImage = await embed(watermark);
75
+ const signed = validateSignResult(await adapter.sign({
76
+ image: watermarkedImage.uri,
77
+ locator,
78
+ algorithm: invisible_watermark_js_1.INVISIBLE_WATERMARK_ALGORITHM,
79
+ claim,
80
+ }));
81
+ const result = {
82
+ watermarkedImage,
83
+ signedImage: signed.image,
84
+ };
85
+ if (signed.manifestId !== undefined) {
86
+ result.manifestId = signed.manifestId;
87
+ }
88
+ return result;
89
+ }
90
+ async function verifyContentCredentials(options) {
91
+ const adapter = validateAdapter(options?.adapter);
92
+ if (typeof options?.image !== 'string' || !options.image) {
93
+ throw new Error('Content Credentials image must not be empty.');
94
+ }
95
+ return validateVerificationResult(await adapter.verify(options.image));
96
+ }
@@ -0,0 +1,30 @@
1
+ export type ImageOrientation = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
2
+ export type MarkerImageFormat = 'jpeg' | 'png' | 'webp' | 'gif' | 'heif' | 'bmp' | 'unknown';
3
+ export interface MarkerImageInfo {
4
+ /** Display width after applying the encoded orientation. */
5
+ width: number;
6
+ /** Display height after applying the encoded orientation. */
7
+ height: number;
8
+ /** Width stored in the encoded pixel data before orientation is applied. */
9
+ encodedWidth: number;
10
+ /** Height stored in the encoded pixel data before orientation is applied. */
11
+ encodedHeight: number;
12
+ format: MarkerImageFormat;
13
+ mimeType?: string;
14
+ orientation: ImageOrientation;
15
+ rotationDegrees: 0 | 90 | 180 | 270;
16
+ mirrored: boolean;
17
+ requiresNormalization: boolean;
18
+ }
19
+ interface EncodedImageMetadata {
20
+ width: number;
21
+ height: number;
22
+ format: MarkerImageFormat;
23
+ mimeType?: string;
24
+ orientation?: ImageOrientation;
25
+ }
26
+ export declare function createMarkerImageInfo(metadata: EncodedImageMetadata): MarkerImageInfo;
27
+ /** Parse encoded metadata without decoding the image pixels. */
28
+ export declare function parseEncodedImageInfo(input: ArrayBuffer | ArrayBufferView): MarkerImageInfo;
29
+ export declare function parseNativeImageInfo(serialized: string): MarkerImageInfo;
30
+ export {};