@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,427 @@
1
+ import { createMarkerImageInfo, parseEncodedImageInfo } from '../image-info.js';
2
+ function getBrowserRuntime() {
3
+ return globalThis;
4
+ }
5
+ function getImageDimensions(image) {
6
+ const width = image.naturalWidth ?? image.width ?? 0;
7
+ const height = image.naturalHeight ?? image.height ?? 0;
8
+ return width > 0 && height > 0 ? { width, height } : null;
9
+ }
10
+ function isImageLike(value) {
11
+ if (!value || typeof value !== 'object') {
12
+ return false;
13
+ }
14
+ const candidate = value;
15
+ const hasDrawableIdentity = 'naturalWidth' in candidate ||
16
+ 'complete' in candidate ||
17
+ typeof candidate.decode === 'function' ||
18
+ typeof candidate.close === 'function' ||
19
+ typeof candidate.getContext === 'function' ||
20
+ typeof candidate.videoWidth === 'number';
21
+ return (hasDrawableIdentity &&
22
+ Boolean(getImageDimensions(candidate)));
23
+ }
24
+ function isBlobLike(value) {
25
+ if (!value || typeof value !== 'object') {
26
+ return false;
27
+ }
28
+ const tag = Object.prototype.toString.call(value);
29
+ if (tag === '[object Blob]' || tag === '[object File]') {
30
+ return true;
31
+ }
32
+ const candidate = value;
33
+ return (typeof candidate.arrayBuffer === 'function' &&
34
+ typeof candidate.size === 'number' &&
35
+ typeof candidate.type === 'string');
36
+ }
37
+ function resolveImageUri(source) {
38
+ if (typeof source === 'string') {
39
+ return source;
40
+ }
41
+ if (!source || typeof source !== 'object') {
42
+ return null;
43
+ }
44
+ const candidate = source;
45
+ if (typeof candidate.uri === 'string') {
46
+ return candidate.uri;
47
+ }
48
+ if (typeof candidate.src === 'string') {
49
+ return candidate.src;
50
+ }
51
+ if (typeof candidate.href === 'string') {
52
+ return candidate.href;
53
+ }
54
+ if (typeof candidate.default === 'string') {
55
+ return candidate.default;
56
+ }
57
+ if (candidate.default && candidate.default !== source) {
58
+ return resolveImageUri(candidate.default);
59
+ }
60
+ return null;
61
+ }
62
+ function inferImageFormat(uri, mimeType) {
63
+ const normalizedMimeType = mimeType?.split(';', 1)[0]?.toLowerCase();
64
+ if (normalizedMimeType === 'image/jpeg')
65
+ return 'jpeg';
66
+ if (normalizedMimeType === 'image/png')
67
+ return 'png';
68
+ if (normalizedMimeType === 'image/webp')
69
+ return 'webp';
70
+ if (normalizedMimeType === 'image/gif')
71
+ return 'gif';
72
+ if (normalizedMimeType === 'image/heif' ||
73
+ normalizedMimeType === 'image/heic' ||
74
+ normalizedMimeType === 'image/avif') {
75
+ return 'heif';
76
+ }
77
+ if (normalizedMimeType === 'image/bmp')
78
+ return 'bmp';
79
+ const pathname = uri?.split(/[?#]/u, 1)[0]?.toLowerCase() ?? '';
80
+ if (/\.(?:jpe?g)$/u.test(pathname))
81
+ return 'jpeg';
82
+ if (/\.png$/u.test(pathname))
83
+ return 'png';
84
+ if (/\.webp$/u.test(pathname))
85
+ return 'webp';
86
+ if (/\.gif$/u.test(pathname))
87
+ return 'gif';
88
+ if (/\.(?:heic|heif|avif)$/u.test(pathname))
89
+ return 'heif';
90
+ if (/\.bmp$/u.test(pathname))
91
+ return 'bmp';
92
+ return 'unknown';
93
+ }
94
+ function decodeDataUrl(uri) {
95
+ const match = /^data:([^,]*?),(.*)$/su.exec(uri);
96
+ if (!match)
97
+ return null;
98
+ const metadata = match[1] ?? '';
99
+ const payload = match[2] ?? '';
100
+ const mimeType = metadata.split(';', 1)[0] || undefined;
101
+ try {
102
+ if (/(?:^|;)base64(?:;|$)/iu.test(metadata)) {
103
+ const decode = globalThis.atob;
104
+ if (!decode)
105
+ return null;
106
+ const decoded = decode(payload.replace(/\s/gu, ''));
107
+ const bytes = Uint8Array.from(decoded, (character) => character.charCodeAt(0));
108
+ return { data: bytes.buffer, mimeType };
109
+ }
110
+ return { data: decodePercentEncodedDataUrl(payload), mimeType };
111
+ }
112
+ catch {
113
+ return null;
114
+ }
115
+ }
116
+ function encodeUtf8(value) {
117
+ const TextEncoderClass = globalThis.TextEncoder;
118
+ if (TextEncoderClass) {
119
+ return new TextEncoderClass().encode(value).buffer;
120
+ }
121
+ const bytes = [];
122
+ for (let index = 0; index < value.length; index += 1) {
123
+ const first = value.charCodeAt(index);
124
+ const second = value.charCodeAt(index + 1);
125
+ let codePoint = first;
126
+ if (first >= 0xd800 &&
127
+ first <= 0xdbff &&
128
+ second >= 0xdc00 &&
129
+ second <= 0xdfff) {
130
+ index += 1;
131
+ codePoint = (first - 0xd800) * 0x400 + second - 0xdc00 + 0x10000;
132
+ }
133
+ if (codePoint <= 0x7f) {
134
+ bytes.push(codePoint);
135
+ }
136
+ else if (codePoint <= 0x7ff) {
137
+ bytes.push(0xc0 + Math.floor(codePoint / 0x40), 0x80 + (codePoint % 0x40));
138
+ }
139
+ else if (codePoint <= 0xffff) {
140
+ bytes.push(0xe0 + Math.floor(codePoint / 0x1000), 0x80 + (Math.floor(codePoint / 0x40) % 0x40), 0x80 + (codePoint % 0x40));
141
+ }
142
+ else {
143
+ bytes.push(0xf0 + Math.floor(codePoint / 0x40000), 0x80 + (Math.floor(codePoint / 0x1000) % 0x40), 0x80 + (Math.floor(codePoint / 0x40) % 0x40), 0x80 + (codePoint % 0x40));
144
+ }
145
+ }
146
+ return Uint8Array.from(bytes).buffer;
147
+ }
148
+ /** Decode `%xx` as bytes, keeping unescaped Unicode text encoded as UTF-8. */
149
+ function decodePercentEncodedDataUrl(payload) {
150
+ const bytes = [];
151
+ let text = '';
152
+ const appendText = () => {
153
+ if (!text)
154
+ return;
155
+ bytes.push(...new Uint8Array(encodeUtf8(text)));
156
+ text = '';
157
+ };
158
+ for (let index = 0; index < payload.length; index += 1) {
159
+ if (payload[index] !== '%' || index + 2 >= payload.length) {
160
+ text += payload[index];
161
+ continue;
162
+ }
163
+ const encoded = payload.slice(index + 1, index + 3);
164
+ if (!/^[\da-f]{2}$/iu.test(encoded)) {
165
+ text += payload[index];
166
+ continue;
167
+ }
168
+ appendText();
169
+ bytes.push(Number.parseInt(encoded, 16));
170
+ index += 2;
171
+ }
172
+ appendText();
173
+ return Uint8Array.from(bytes).buffer;
174
+ }
175
+ async function readEncodedImageSource(source, resources, signal) {
176
+ if (signal?.aborted)
177
+ throw createAbortError();
178
+ if (isBlobLike(source)) {
179
+ const blob = source;
180
+ const data = resources?.readBlobBytes
181
+ ? await resources.readBlobBytes(source)
182
+ : await blob.arrayBuffer();
183
+ if (signal?.aborted)
184
+ throw createAbortError();
185
+ return { data, mimeType: blob.type || undefined };
186
+ }
187
+ const uri = resolveImageUri(source);
188
+ if (uri?.startsWith('data:')) {
189
+ if (signal?.aborted)
190
+ throw createAbortError();
191
+ return decodeDataUrl(uri);
192
+ }
193
+ const fetchImage = globalThis.fetch;
194
+ if (!uri || !fetchImage) {
195
+ return null;
196
+ }
197
+ try {
198
+ const response = signal
199
+ ? await fetchImage(uri, { signal })
200
+ : await fetchImage(uri);
201
+ if (!response.ok && !uri.startsWith('data:')) {
202
+ return null;
203
+ }
204
+ const data = await response.arrayBuffer();
205
+ if (signal?.aborted)
206
+ throw createAbortError();
207
+ return {
208
+ data,
209
+ mimeType: response.headers.get('content-type') ?? undefined,
210
+ };
211
+ }
212
+ catch {
213
+ if (signal?.aborted)
214
+ throw createAbortError();
215
+ return null;
216
+ }
217
+ }
218
+ /**
219
+ * Read dimensions, encoded format, and orientation without rendering a
220
+ * composition. Encoded bytes are preferred so EXIF orientation remains
221
+ * observable; drawable dimensions are the cross-origin fallback.
222
+ */
223
+ export async function getWebImageInfo(source, resources, signal) {
224
+ if (signal?.aborted)
225
+ throw createAbortError();
226
+ const encoded = await readEncodedImageSource(source, resources, signal);
227
+ if (encoded) {
228
+ try {
229
+ const info = parseEncodedImageInfo(encoded.data);
230
+ return encoded.mimeType && !info.mimeType
231
+ ? { ...info, mimeType: encoded.mimeType }
232
+ : info;
233
+ }
234
+ catch {
235
+ // SVG and browser-specific formats can still be drawable. Continue with
236
+ // decoded dimensions rather than rejecting a valid browser image.
237
+ }
238
+ }
239
+ const loaded = await loadWebImage(source, resources, signal);
240
+ try {
241
+ const uri = resolveImageUri(source);
242
+ const mimeType = encoded?.mimeType ||
243
+ (isBlobLike(source)
244
+ ? source.type || undefined
245
+ : undefined);
246
+ return createMarkerImageInfo({
247
+ width: Math.round(loaded.width),
248
+ height: Math.round(loaded.height),
249
+ format: inferImageFormat(uri, mimeType),
250
+ mimeType,
251
+ orientation: 1,
252
+ });
253
+ }
254
+ finally {
255
+ loaded.cleanup();
256
+ }
257
+ }
258
+ function createImageElement(resources) {
259
+ if (resources?.createImage) {
260
+ const image = resources.createImage();
261
+ if (!image || typeof image !== 'object' || !('src' in image)) {
262
+ throw new Error('Web Marker resource adapter returned an invalid image.');
263
+ }
264
+ return image;
265
+ }
266
+ const runtime = getBrowserRuntime();
267
+ if (runtime.Image) {
268
+ return new runtime.Image();
269
+ }
270
+ const element = runtime.document?.createElement('img');
271
+ if (element && typeof element === 'object') {
272
+ return element;
273
+ }
274
+ throw new Error('WebMarker requires a browser image runtime. Importing it during SSR is supported, but mark*() must run in the browser.');
275
+ }
276
+ function remoteUrl(uri) {
277
+ return /^(?:https?:)?\/\//i.test(uri);
278
+ }
279
+ function createAbortError() {
280
+ const error = new Error('Web image loading was aborted.');
281
+ error.name = 'AbortError';
282
+ return error;
283
+ }
284
+ /** Load a browser drawable from a URL, data URL, {uri}, Blob/File, or image element. */
285
+ export async function loadWebImage(source, resources, signal) {
286
+ if (signal?.aborted) {
287
+ throw createAbortError();
288
+ }
289
+ if (isImageLike(source)) {
290
+ const dimensions = getImageDimensions(source);
291
+ if (!dimensions) {
292
+ throw new Error('The supplied image has no drawable dimensions.');
293
+ }
294
+ return { image: source, ...dimensions, cleanup() { } };
295
+ }
296
+ let objectUrl;
297
+ let uri = resolveImageUri(source);
298
+ if (!uri && isBlobLike(source)) {
299
+ const runtime = getBrowserRuntime();
300
+ const createObjectURL = resources?.createObjectURL ?? runtime.URL?.createObjectURL;
301
+ if (!createObjectURL) {
302
+ throw new Error('This browser cannot load Blob/File image sources.');
303
+ }
304
+ objectUrl = createObjectURL(source);
305
+ uri = objectUrl;
306
+ }
307
+ if (!uri) {
308
+ if (typeof source === 'number') {
309
+ throw new Error('Numeric React Native asset IDs are not available on web. Pass the bundler-resolved URL, { uri }, a data URL, Blob, or File instead.');
310
+ }
311
+ throw new Error('Unsupported web image source. Expected a URL, data URL, { uri }, Blob, File, or loaded image element.');
312
+ }
313
+ let image;
314
+ try {
315
+ image = createImageElement(resources);
316
+ }
317
+ catch (error) {
318
+ if (objectUrl) {
319
+ const runtime = getBrowserRuntime();
320
+ try {
321
+ (resources?.revokeObjectURL ?? runtime.URL?.revokeObjectURL)?.(objectUrl);
322
+ }
323
+ catch {
324
+ // Preserve the image-factory error; cleanup must not conceal it.
325
+ }
326
+ }
327
+ throw error;
328
+ }
329
+ if (remoteUrl(uri)) {
330
+ image.crossOrigin = 'anonymous';
331
+ }
332
+ let cleanedUp = false;
333
+ const cleanup = () => {
334
+ if (cleanedUp)
335
+ return;
336
+ cleanedUp = true;
337
+ image.onload = null;
338
+ image.onerror = null;
339
+ signal?.removeEventListener('abort', abort);
340
+ if (objectUrl) {
341
+ const runtime = getBrowserRuntime();
342
+ (resources?.revokeObjectURL ?? runtime.URL?.revokeObjectURL)?.(objectUrl);
343
+ objectUrl = undefined;
344
+ }
345
+ };
346
+ let rejectLoading;
347
+ const abort = () => {
348
+ image.src = '';
349
+ cleanup();
350
+ rejectLoading?.(createAbortError());
351
+ };
352
+ try {
353
+ await new Promise((resolve, reject) => {
354
+ rejectLoading = reject;
355
+ image.onload = () => resolve();
356
+ image.onerror = () => {
357
+ const corsHint = remoteUrl(uri)
358
+ ? ' The server must allow cross-origin image access with Access-Control-Allow-Origin.'
359
+ : '';
360
+ reject(new Error(`Unable to load web image.${corsHint}`));
361
+ };
362
+ image.src = uri;
363
+ signal?.addEventListener('abort', abort, { once: true });
364
+ if (signal?.aborted) {
365
+ abort();
366
+ return;
367
+ }
368
+ if (image.complete && getImageDimensions(image)) {
369
+ resolve();
370
+ }
371
+ });
372
+ if (image.decode) {
373
+ try {
374
+ await image.decode();
375
+ }
376
+ catch {
377
+ // onload already proved that the image is drawable. Some browsers reject
378
+ // decode() after resolving cached images, so this is intentionally best effort.
379
+ }
380
+ }
381
+ const dimensions = getImageDimensions(image);
382
+ if (!dimensions) {
383
+ throw new Error('The loaded image has no drawable dimensions.');
384
+ }
385
+ return {
386
+ image,
387
+ ...dimensions,
388
+ cleanup,
389
+ };
390
+ }
391
+ catch (error) {
392
+ cleanup();
393
+ throw error;
394
+ }
395
+ finally {
396
+ image.onload = null;
397
+ image.onerror = null;
398
+ }
399
+ }
400
+ export function createWebCanvas(width, height, resources) {
401
+ const runtime = getBrowserRuntime();
402
+ const canvas = resources?.createCanvas?.(width, height) ??
403
+ runtime.document?.createElement('canvas');
404
+ if (!canvas || typeof canvas !== 'object') {
405
+ throw new Error('WebMarker requires Canvas 2D. Importing it during SSR is supported, but mark*() must run in a browser with canvas support.');
406
+ }
407
+ const result = canvas;
408
+ if (typeof result.getContext !== 'function') {
409
+ throw new Error('Web Marker resource adapter returned an invalid canvas.');
410
+ }
411
+ result.width = Math.max(Math.round(width), 1);
412
+ result.height = Math.max(Math.round(height), 1);
413
+ if (!result.getContext('2d')) {
414
+ throw new Error('Canvas 2D is unavailable in this browser.');
415
+ }
416
+ return result;
417
+ }
418
+ export function getCanvasContext(canvas) {
419
+ const context = canvas.getContext('2d');
420
+ if (!context) {
421
+ throw new Error('Canvas 2D is unavailable in this browser.');
422
+ }
423
+ return context;
424
+ }
425
+ export function getSecurityErrorMessage(operation) {
426
+ return `Unable to ${operation} because a remote image tainted the canvas. Configure Access-Control-Allow-Origin on the image server or use a local file/data URL.`;
427
+ }
@@ -0,0 +1,41 @@
1
+ import type { PositionOptions } from '../index.js';
2
+ export interface Size {
3
+ width: number;
4
+ height: number;
5
+ }
6
+ export interface Point {
7
+ x: number;
8
+ y: number;
9
+ }
10
+ export interface RotatedBounds extends Size {
11
+ left: number;
12
+ top: number;
13
+ }
14
+ export interface CanvasEncoder {
15
+ width: number;
16
+ height: number;
17
+ toDataURL(type?: string, quality?: number): string;
18
+ toBlob?(callback: (blob: Blob | null) => void, type?: string, quality?: number): void;
19
+ }
20
+ export type WebOutputFormat = 'png' | 'jpg' | 'webp' | 'base64';
21
+ export declare const DEFAULT_WEB_MAX_SIZE = 2048;
22
+ /** Fit an image inside the same square pixel bound used by native loaders. */
23
+ export declare function fitSizeWithinMax(size: Size, maxSize?: number): Size;
24
+ /** Resolve a pixel or percentage value against one canvas axis. */
25
+ export declare function resolveSpreadValue(value: number | string | undefined, relativeTo: number): number | undefined;
26
+ /**
27
+ * Resolve the top-left point of an item using the same directional semantics
28
+ * as the native renderers. Named anchors default to a 20px inset; explicit
29
+ * X/Y values replace the inset on their respective axes.
30
+ */
31
+ export declare function resolveAnchoredPosition(options: PositionOptions | undefined, canvas: Size, item: Size, unanchoredDefaultInset?: number): Point;
32
+ export declare function degreesToRadians(degrees: number): number;
33
+ /** Bounds of a rectangle rotated around its top-left origin. */
34
+ export declare function getRotatedBounds(size: Size, degrees: number): RotatedBounds;
35
+ export declare function getExpandedCanvasSize(size: Size, degrees: number): Size;
36
+ export declare function normalizeQuality(quality: number | undefined): number;
37
+ export declare function normalizeOutputFormat(format: string | undefined): WebOutputFormat;
38
+ /** Serialize a rendered canvas and turn browser security failures into a useful CORS error. */
39
+ export declare function encodeCanvas(canvas: CanvasEncoder, format: string | undefined, quality: number | undefined): string;
40
+ /** Encode a rendered canvas without expanding its bytes into a data URL. */
41
+ export declare function encodeCanvasToBlob(canvas: CanvasEncoder, format: string | undefined, quality: number | undefined): Promise<Blob>;