@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
@@ -0,0 +1,251 @@
1
+ const DEFAULT_EDGE_INSET = 20;
2
+ export const DEFAULT_WEB_MAX_SIZE = 2048;
3
+ /** Fit an image inside the same square pixel bound used by native loaders. */
4
+ export function fitSizeWithinMax(size, maxSize = DEFAULT_WEB_MAX_SIZE) {
5
+ if (!Number.isFinite(maxSize) || !Number.isInteger(maxSize) || maxSize <= 0) {
6
+ throw new Error('maxSize must be a positive finite integer.');
7
+ }
8
+ if (!Number.isFinite(size.width) ||
9
+ !Number.isFinite(size.height) ||
10
+ size.width <= 0 ||
11
+ size.height <= 0) {
12
+ throw new Error('image dimensions must be finite numbers greater than 0.');
13
+ }
14
+ const largestDimension = Math.max(size.width, size.height);
15
+ if (largestDimension <= maxSize) {
16
+ return { ...size };
17
+ }
18
+ const ratio = maxSize / largestDimension;
19
+ return {
20
+ width: Math.max(Math.min(Math.round(size.width * ratio), maxSize), 1),
21
+ height: Math.max(Math.min(Math.round(size.height * ratio), maxSize), 1),
22
+ };
23
+ }
24
+ /** Resolve a pixel or percentage value against one canvas axis. */
25
+ export function resolveSpreadValue(value, relativeTo) {
26
+ if (value === undefined || value === null) {
27
+ return undefined;
28
+ }
29
+ if (typeof value === 'number') {
30
+ return Number.isFinite(value) ? value : 0;
31
+ }
32
+ const normalized = value.trim();
33
+ const parsed = Number.parseFloat(normalized.endsWith('%') ? normalized.slice(0, -1) : normalized);
34
+ if (!Number.isFinite(parsed)) {
35
+ return 0;
36
+ }
37
+ return normalized.endsWith('%') ? (relativeTo * parsed) / 100 : parsed;
38
+ }
39
+ /**
40
+ * Resolve the top-left point of an item using the same directional semantics
41
+ * as the native renderers. Named anchors default to a 20px inset; explicit
42
+ * X/Y values replace the inset on their respective axes.
43
+ */
44
+ export function resolveAnchoredPosition(options, canvas, item, unanchoredDefaultInset = DEFAULT_EDGE_INSET) {
45
+ const explicitX = resolveSpreadValue(options?.X, canvas.width);
46
+ const explicitY = resolveSpreadValue(options?.Y, canvas.height);
47
+ const configuredInsetX = resolveSpreadValue(options?.edgeInset, canvas.width);
48
+ const configuredInsetY = resolveSpreadValue(options?.edgeInset, canvas.height);
49
+ const anchor = options?.position;
50
+ if (!anchor) {
51
+ return {
52
+ x: explicitX ?? Math.max(configuredInsetX ?? unanchoredDefaultInset, 0),
53
+ y: explicitY ?? Math.max(configuredInsetY ?? unanchoredDefaultInset, 0),
54
+ };
55
+ }
56
+ const insetX = Math.max(configuredInsetX ?? DEFAULT_EDGE_INSET, 0);
57
+ const insetY = Math.max(configuredInsetY ?? DEFAULT_EDGE_INSET, 0);
58
+ const centeredX = (canvas.width - item.width) / 2;
59
+ const centeredY = (canvas.height - item.height) / 2;
60
+ let x = centeredX;
61
+ let y = centeredY;
62
+ switch (anchor) {
63
+ case 'topLeft':
64
+ case 'bottomLeft':
65
+ x = insetX;
66
+ break;
67
+ case 'topRight':
68
+ case 'bottomRight':
69
+ x = canvas.width - item.width - insetX;
70
+ break;
71
+ case 'topCenter':
72
+ case 'bottomCenter':
73
+ case 'center':
74
+ x = centeredX;
75
+ break;
76
+ default:
77
+ return {
78
+ x: explicitX ?? Math.max(configuredInsetX ?? unanchoredDefaultInset, 0),
79
+ y: explicitY ?? Math.max(configuredInsetY ?? unanchoredDefaultInset, 0),
80
+ };
81
+ }
82
+ switch (anchor) {
83
+ case 'topLeft':
84
+ case 'topCenter':
85
+ case 'topRight':
86
+ y = insetY;
87
+ break;
88
+ case 'bottomLeft':
89
+ case 'bottomCenter':
90
+ case 'bottomRight':
91
+ y = canvas.height - item.height - insetY;
92
+ break;
93
+ case 'center':
94
+ y = centeredY;
95
+ break;
96
+ }
97
+ if (explicitX !== undefined) {
98
+ if (anchor === 'topRight' || anchor === 'bottomRight') {
99
+ x = canvas.width - item.width - explicitX;
100
+ }
101
+ else if (anchor === 'topCenter' ||
102
+ anchor === 'bottomCenter' ||
103
+ anchor === 'center') {
104
+ x = centeredX + explicitX;
105
+ }
106
+ else {
107
+ x = explicitX;
108
+ }
109
+ }
110
+ if (explicitY !== undefined) {
111
+ if (anchor === 'bottomLeft' ||
112
+ anchor === 'bottomCenter' ||
113
+ anchor === 'bottomRight') {
114
+ y = canvas.height - item.height - explicitY;
115
+ }
116
+ else if (anchor === 'center') {
117
+ y = centeredY + explicitY;
118
+ }
119
+ else {
120
+ y = explicitY;
121
+ }
122
+ }
123
+ return { x, y };
124
+ }
125
+ export function degreesToRadians(degrees) {
126
+ if (!Number.isFinite(degrees)) {
127
+ return 0;
128
+ }
129
+ return ((degrees % 360) * Math.PI) / 180;
130
+ }
131
+ /** Bounds of a rectangle rotated around its top-left origin. */
132
+ export function getRotatedBounds(size, degrees) {
133
+ const radians = degreesToRadians(degrees);
134
+ if (radians === 0) {
135
+ return { left: 0, top: 0, ...size };
136
+ }
137
+ const cosine = Math.cos(radians);
138
+ const sine = Math.sin(radians);
139
+ const points = [
140
+ { x: 0, y: 0 },
141
+ { x: size.width * cosine, y: size.width * sine },
142
+ { x: -size.height * sine, y: size.height * cosine },
143
+ {
144
+ x: size.width * cosine - size.height * sine,
145
+ y: size.width * sine + size.height * cosine,
146
+ },
147
+ ];
148
+ const xs = points.map((point) => point.x);
149
+ const ys = points.map((point) => point.y);
150
+ const left = Math.min(...xs);
151
+ const right = Math.max(...xs);
152
+ const top = Math.min(...ys);
153
+ const bottom = Math.max(...ys);
154
+ return {
155
+ left,
156
+ top,
157
+ width: right - left,
158
+ height: bottom - top,
159
+ };
160
+ }
161
+ export function getExpandedCanvasSize(size, degrees) {
162
+ const bounds = getRotatedBounds(size, degrees);
163
+ return {
164
+ width: Math.max(Math.ceil(bounds.width - 1e-7), 1),
165
+ height: Math.max(Math.ceil(bounds.height - 1e-7), 1),
166
+ };
167
+ }
168
+ export function normalizeQuality(quality) {
169
+ const resolved = quality ?? 100;
170
+ if (!Number.isFinite(resolved) ||
171
+ !Number.isInteger(resolved) ||
172
+ resolved < 0 ||
173
+ resolved > 100) {
174
+ throw new Error('quality must be a finite integer between 0 and 100.');
175
+ }
176
+ return resolved;
177
+ }
178
+ export function normalizeOutputFormat(format) {
179
+ const resolved = format ?? 'jpg';
180
+ if (resolved !== 'png' &&
181
+ resolved !== 'jpg' &&
182
+ resolved !== 'webp' &&
183
+ resolved !== 'base64') {
184
+ throw new Error(`Unsupported web image format: ${resolved}.`);
185
+ }
186
+ return resolved;
187
+ }
188
+ /** Serialize a rendered canvas and turn browser security failures into a useful CORS error. */
189
+ export function encodeCanvas(canvas, format, quality) {
190
+ const normalizedFormat = normalizeOutputFormat(format);
191
+ const normalizedQuality = normalizeQuality(quality);
192
+ const mimeType = normalizedFormat === 'jpg'
193
+ ? 'image/jpeg'
194
+ : normalizedFormat === 'webp'
195
+ ? 'image/webp'
196
+ : 'image/png';
197
+ try {
198
+ const dataUrl = canvas.toDataURL(mimeType, normalizedQuality / 100);
199
+ if (!dataUrl.startsWith(`data:${mimeType}`)) {
200
+ throw new Error('The browser could not encode the rendered canvas.');
201
+ }
202
+ return dataUrl;
203
+ }
204
+ catch (error) {
205
+ const errorName = error && typeof error === 'object' && 'name' in error
206
+ ? String(error.name)
207
+ : '';
208
+ if (errorName === 'SecurityError') {
209
+ throw new Error('Unable to export the canvas because an image tainted it. Configure CORS on remote images (Access-Control-Allow-Origin) or use a local file/data URL.');
210
+ }
211
+ throw error;
212
+ }
213
+ }
214
+ /** Encode a rendered canvas without expanding its bytes into a data URL. */
215
+ export function encodeCanvasToBlob(canvas, format, quality) {
216
+ const normalizedFormat = normalizeOutputFormat(format);
217
+ const normalizedQuality = normalizeQuality(quality);
218
+ const mimeType = normalizedFormat === 'jpg'
219
+ ? 'image/jpeg'
220
+ : normalizedFormat === 'webp'
221
+ ? 'image/webp'
222
+ : 'image/png';
223
+ if (typeof canvas.toBlob !== 'function') {
224
+ return Promise.reject(new Error('This browser does not support Canvas toBlob() encoding.'));
225
+ }
226
+ return new Promise((resolve, reject) => {
227
+ try {
228
+ canvas.toBlob((blob) => {
229
+ if (!blob) {
230
+ reject(new Error('The browser could not encode the rendered canvas.'));
231
+ return;
232
+ }
233
+ if (blob.type !== mimeType) {
234
+ reject(new Error(`The browser encoded an unexpected MIME type: ${blob.type || 'empty'}.`));
235
+ return;
236
+ }
237
+ resolve(blob);
238
+ }, mimeType, normalizedQuality / 100);
239
+ }
240
+ catch (error) {
241
+ const errorName = error && typeof error === 'object' && 'name' in error
242
+ ? String(error.name)
243
+ : '';
244
+ if (errorName === 'SecurityError') {
245
+ reject(new Error('Unable to export the canvas because an image tainted it. Configure CORS on remote images (Access-Control-Allow-Origin) or use a local file/data URL.'));
246
+ return;
247
+ }
248
+ reject(error);
249
+ }
250
+ });
251
+ }
@@ -0,0 +1,59 @@
1
+ import type { ImageMarkOptions, ImageOptions, MarkOptions, TextMarkOptions } from '../index.js';
2
+ import type { WatermarkBlobRecipeResultOptions, WatermarkRecipe, WatermarkRecipeDocument, WatermarkRecipeOptions, WatermarkRecipeResultOptions } from '../recipe.js';
3
+ import type { DetectInvisibleWatermarkOptions, EmbedInvisibleWatermarkOptions, InvisibleWatermarkDetectionResult } from '../invisible-watermark.js';
4
+ import type { MarkerImageInfo } from '../image-info.js';
5
+ import type { MarkerJobOptions } from '../job.js';
6
+ import type { MarkerResult } from '../result.js';
7
+ import type { WatermarkBatchOptions, WatermarkBatchResult } from '../batch.js';
8
+ import type { ContentCredentialsVerificationResult, EmbedInvisibleWithCredentialsOptions, EmbedInvisibleWithCredentialsResult, VerifyContentCredentialsOptions } from '../content-credentials.js';
9
+ import type { WebMarkerInstance, WebMarkerRuntimeOptions } from './runtime.js';
10
+ export type { WebMarkerCapabilities, WebMarkerCancellationMode, WebMarkerExecutionAdapter, WebMarkerExecutionOperation, WebMarkerExecutionRequest, WebMarkerExecutionResultKind, WebMarkerExecutionTask, WebMarkerExecutionTermination, WebMarkerInstance, WebMarkerRuntimeOptions, } from './runtime.js';
11
+ export type { WebResourceAdapter } from './browser.js';
12
+ /**
13
+ * Public image-marking API shared by native and Web targets.
14
+ *
15
+ * Metro selects the native TurboModule implementation on iOS and Android.
16
+ * React Native Web and standard browser bundlers select this SSR-safe Canvas
17
+ * 2D implementation, which only touches DOM globals when a method is called.
18
+ */
19
+ declare class Marker {
20
+ /** Read display dimensions, encoded dimensions, format, and orientation. */
21
+ static getImageInfo(source: ImageOptions['src']): Promise<MarkerImageInfo>;
22
+ /** Embed a locator first, then ask the supplied adapter to sign the result. */
23
+ static embedInvisibleWithCredentials(options: EmbedInvisibleWithCredentialsOptions, control?: MarkerJobOptions): Promise<EmbedInvisibleWithCredentialsResult>;
24
+ /** Verify Content Credentials through an application-supplied adapter. */
25
+ static verifyContentCredentials(options: VerifyContentCredentialsOptions): Promise<ContentCredentialsVerificationResult>;
26
+ /** Embed authenticated locators into many images while preserving input order. */
27
+ static embedInvisibleMany(inputs: readonly EmbedInvisibleWatermarkOptions[], options?: WatermarkBatchOptions<MarkerResult>): Promise<Array<WatermarkBatchResult<MarkerResult>>>;
28
+ /** Detect authenticated locators in many images while preserving input order. */
29
+ static detectInvisibleMany(inputs: readonly DetectInvisibleWatermarkOptions[], options?: WatermarkBatchOptions<InvisibleWatermarkDetectionResult>): Promise<Array<WatermarkBatchResult<InvisibleWatermarkDetectionResult>>>;
30
+ /**
31
+ * Embed a short, authenticated locator into the final image pixels.
32
+ *
33
+ * This Beta API supports distribution tracing. It is not DRM, encryption,
34
+ * or proof that the image was never edited.
35
+ */
36
+ static embedInvisible(options: EmbedInvisibleWatermarkOptions, control?: MarkerJobOptions): Promise<MarkerResult>;
37
+ /** Detect and authenticate an invisible locator in an image without writing a file. */
38
+ static detectInvisible(options: DetectInvisibleWatermarkOptions, control?: MarkerJobOptions): Promise<InvisibleWatermarkDetectionResult>;
39
+ /** Save ordered layers and output settings for reuse across one or many images. */
40
+ static createRecipe<ResultOptions extends WatermarkRecipeResultOptions | undefined = undefined>(options: WatermarkRecipeOptions, resultOptions?: ResultOptions): WatermarkRecipe<ResultOptions extends WatermarkBlobRecipeResultOptions ? Blob : MarkerResult>;
41
+ /** Import a persisted Recipe v1/v2 document, migrating v1 to v2 first. */
42
+ static importRecipe<ResultOptions extends WatermarkRecipeResultOptions | undefined = undefined>(document: WatermarkRecipeDocument, resultOptions?: ResultOptions): WatermarkRecipe<ResultOptions extends WatermarkBlobRecipeResultOptions ? Blob : MarkerResult>;
43
+ /** Render one or more text watermark layers. */
44
+ static markText(options: TextMarkOptions, control?: MarkerJobOptions): Promise<MarkerResult>;
45
+ /** Render one or more image watermark layers. */
46
+ static markImage(options: ImageMarkOptions, control?: MarkerJobOptions): Promise<MarkerResult>;
47
+ /** Render ordered mixed text and image watermark layers. */
48
+ static mark(options: MarkOptions, control?: MarkerJobOptions): Promise<MarkerResult>;
49
+ }
50
+ /**
51
+ * Create an isolated Web Marker instance. The legacy static `Marker` API
52
+ * remains the default DOM path; use this factory when task cancellation and
53
+ * resource ownership need to be scoped to a desktop application instance.
54
+ */
55
+ export declare function createWebMarker(options?: WebMarkerRuntimeOptions): WebMarkerInstance;
56
+ export { Marker, Marker as WebMarker };
57
+ export { degreesToRadians, encodeCanvas, encodeCanvasToBlob, fitSizeWithinMax, getExpandedCanvasSize, getRotatedBounds, normalizeOutputFormat, normalizeQuality, resolveAnchoredPosition, resolveSpreadValue, } from './helpers.js';
58
+ export type { CanvasEncoder, Point, RotatedBounds, Size, WebOutputFormat, } from './helpers.js';
59
+ export default Marker;