@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,89 @@
1
+ import { runWatermarkBatch } from './batch.js';
2
+ import { detectInvisibleWatermarkPixelsAsync, embedInvisibleWatermarkPixels, validateDetectInvisibleOptions, validateEmbedInvisibleOptions, } from './invisible-watermark.js';
3
+ const DEFAULT_RUNTIME_MAX_SIZE = 2048;
4
+ function validatePositiveInteger(value, label) {
5
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
6
+ throw new Error(`${label} must be a positive finite integer.`);
7
+ }
8
+ }
9
+ function validateDecodedImage(image, maxSize) {
10
+ if (!image ||
11
+ !(image.data instanceof Uint8Array ||
12
+ image.data instanceof Uint8ClampedArray)) {
13
+ throw new Error('codec.decode() must return RGBA Uint8Array pixels.');
14
+ }
15
+ validatePositiveInteger(image.width, 'decoded image width');
16
+ validatePositiveInteger(image.height, 'decoded image height');
17
+ if (Math.max(image.width, image.height) > maxSize) {
18
+ throw new Error('codec.decode() returned an image larger than maxSize.');
19
+ }
20
+ if (image.data.length !== image.width * image.height * 4) {
21
+ throw new Error('codec.decode() RGBA byte length must exactly match width × height × 4.');
22
+ }
23
+ }
24
+ function snapshotEmbedOptions(options) {
25
+ return { ...options, image: { ...options?.image } };
26
+ }
27
+ function snapshotDetectOptions(options) {
28
+ return { ...options, image: { ...options?.image } };
29
+ }
30
+ /**
31
+ * Create a DOM-free invisible watermark runtime around an injected image codec.
32
+ * The core package does not install or choose a server-side image dependency.
33
+ */
34
+ export function createInvisibleWatermarkRuntime(options) {
35
+ if (!options?.codec || typeof options.codec.decode !== 'function') {
36
+ throw new Error('codec.decode must be a function.');
37
+ }
38
+ if (typeof options.codec.encode !== 'function') {
39
+ throw new Error('codec.encode must be a function.');
40
+ }
41
+ const maximumConcurrency = options.maxConcurrency ?? 4;
42
+ validatePositiveInteger(maximumConcurrency, 'maxConcurrency');
43
+ const embedInvisible = async (input) => {
44
+ const snapshot = snapshotEmbedOptions(input);
45
+ validateEmbedInvisibleOptions(snapshot);
46
+ const saveFormat = snapshot.saveFormat;
47
+ if (saveFormat === 'base64') {
48
+ throw new Error('The generic invisible watermark runtime does not support base64 output.');
49
+ }
50
+ const maxSize = snapshot.maxSize ?? DEFAULT_RUNTIME_MAX_SIZE;
51
+ const decoded = await options.codec.decode(snapshot.image.src, { maxSize });
52
+ validateDecodedImage(decoded, maxSize);
53
+ const outputPixels = {
54
+ data: new Uint8ClampedArray(decoded.data),
55
+ width: decoded.width,
56
+ height: decoded.height,
57
+ };
58
+ embedInvisibleWatermarkPixels(outputPixels, snapshot);
59
+ return options.codec.encode(outputPixels, {
60
+ format: saveFormat === 'png' ? 'png' : 'jpeg',
61
+ quality: snapshot.quality ?? 90,
62
+ filename: snapshot.filename,
63
+ });
64
+ };
65
+ const detectInvisible = async (input) => {
66
+ const snapshot = snapshotDetectOptions(input);
67
+ validateDetectInvisibleOptions(snapshot);
68
+ const maxSize = snapshot.maxSize ?? DEFAULT_RUNTIME_MAX_SIZE;
69
+ const decoded = await options.codec.decode(snapshot.image.src, { maxSize });
70
+ validateDecodedImage(decoded, maxSize);
71
+ return detectInvisibleWatermarkPixelsAsync(decoded, snapshot);
72
+ };
73
+ return {
74
+ embedInvisible,
75
+ detectInvisible,
76
+ embedInvisibleMany(inputs, batchOptions) {
77
+ const snapshots = Array.isArray(inputs)
78
+ ? inputs.map((input) => snapshotEmbedOptions(input))
79
+ : inputs;
80
+ return runWatermarkBatch(snapshots, embedInvisible, batchOptions, maximumConcurrency, 'trace runtime embedInvisibleMany');
81
+ },
82
+ detectInvisibleMany(inputs, batchOptions) {
83
+ const snapshots = Array.isArray(inputs)
84
+ ? inputs.map((input) => snapshotDetectOptions(input))
85
+ : inputs;
86
+ return runWatermarkBatch(snapshots, detectInvisible, batchOptions, maximumConcurrency, 'trace runtime detectInvisibleMany');
87
+ },
88
+ };
89
+ }
@@ -0,0 +1,4 @@
1
+ import type { ImageMarkOptions, MarkOptions, TextMarkOptions } from './index.js';
2
+ export declare function validateTextMarkOptions(options: TextMarkOptions): void;
3
+ export declare function validateImageMarkOptions(options: ImageMarkOptions): void;
4
+ export declare function validateMarkOptions(options: MarkOptions): void;
@@ -0,0 +1,187 @@
1
+ const SUPPORTED_BLEND_MODES = new Set([
2
+ 'normal',
3
+ 'multiply',
4
+ 'screen',
5
+ 'overlay',
6
+ 'darken',
7
+ 'lighten',
8
+ ]);
9
+ function validateQuality(options) {
10
+ const { quality } = options;
11
+ if (quality !== undefined &&
12
+ (!Number.isFinite(quality) ||
13
+ !Number.isInteger(quality) ||
14
+ quality < 0 ||
15
+ quality > 100)) {
16
+ throw new Error('quality must be a finite integer between 0 and 100.');
17
+ }
18
+ }
19
+ function validateMaxSize(options) {
20
+ const { maxSize } = options;
21
+ if (maxSize !== undefined &&
22
+ (!Number.isFinite(maxSize) || !Number.isInteger(maxSize) || maxSize <= 0)) {
23
+ throw new Error('maxSize must be a positive finite integer.');
24
+ }
25
+ }
26
+ function validateAlpha(options, path) {
27
+ const alpha = options?.alpha;
28
+ if (alpha !== undefined &&
29
+ (!Number.isFinite(alpha) || alpha < 0 || alpha > 1)) {
30
+ throw new Error(`${path}.alpha must be a finite number between 0 and 1.`);
31
+ }
32
+ }
33
+ function validateBlendMode(blendMode, path) {
34
+ if (blendMode !== undefined && !SUPPORTED_BLEND_MODES.has(blendMode)) {
35
+ throw new Error(`${path}.blendMode is not supported: ${String(blendMode)}.`);
36
+ }
37
+ }
38
+ function validateCommonOptions(options) {
39
+ validateQuality(options);
40
+ validateMaxSize(options);
41
+ validateAlpha(options.backgroundImage, 'backgroundImage');
42
+ const saveFormat = options.saveFormat;
43
+ if (saveFormat !== undefined &&
44
+ saveFormat !== 'jpg' &&
45
+ saveFormat !== 'png' &&
46
+ saveFormat !== 'webp' &&
47
+ saveFormat !== 'base64') {
48
+ throw new Error(`saveFormat is not supported: ${String(saveFormat)}.`);
49
+ }
50
+ }
51
+ function validateLayoutValue(value, path, nonNegative) {
52
+ if (value === undefined) {
53
+ return;
54
+ }
55
+ const normalized = typeof value === 'string' ? value.trim() : value;
56
+ const valid = typeof normalized === 'number'
57
+ ? Number.isFinite(normalized)
58
+ : /^([+-]?(?:\d+(?:\.\d+)?|\.\d+))(%)?$/.test(normalized);
59
+ if (!valid) {
60
+ throw new Error(`${path} must be a finite number or percentage.`);
61
+ }
62
+ if (nonNegative && Number.parseFloat(String(normalized)) < 0) {
63
+ throw new Error(`${path} must be non-negative.`);
64
+ }
65
+ }
66
+ function validateWatermarkLayout(layout, hasPosition, path) {
67
+ if (!layout) {
68
+ return;
69
+ }
70
+ if (typeof layout !== 'object' || Array.isArray(layout)) {
71
+ throw new Error(`${path} must be an object.`);
72
+ }
73
+ const type = layout.type ?? 'single';
74
+ if (type !== 'single' && type !== 'tile') {
75
+ throw new Error(`${path}.type must be "single" or "tile".`);
76
+ }
77
+ if (type === 'tile' && hasPosition) {
78
+ throw new Error(`${path} cannot be combined with position.`);
79
+ }
80
+ const runtimeLayout = layout;
81
+ validateLayoutValue(runtimeLayout.gapX, `${path}.gapX`, true);
82
+ validateLayoutValue(runtimeLayout.gapY, `${path}.gapY`, true);
83
+ validateLayoutValue(runtimeLayout.offsetX, `${path}.offsetX`, false);
84
+ validateLayoutValue(runtimeLayout.offsetY, `${path}.offsetY`, false);
85
+ if (runtimeLayout.stagger !== undefined &&
86
+ typeof runtimeLayout.stagger !== 'boolean') {
87
+ throw new Error(`${path}.stagger must be a boolean.`);
88
+ }
89
+ }
90
+ function validateTextOptions(options, path) {
91
+ if (Object.prototype.hasOwnProperty.call(options, 'positionOptions')) {
92
+ throw new Error(`${path}.positionOptions was removed in v2; use position instead.`);
93
+ }
94
+ validateAlpha(options, path);
95
+ validateBlendMode(options.blendMode, path);
96
+ const fontFallbacks = options.style?.fontFallbacks;
97
+ if (fontFallbacks !== undefined &&
98
+ (!Array.isArray(fontFallbacks) ||
99
+ fontFallbacks.some((font) => typeof font !== 'string' || font.trim().length === 0))) {
100
+ throw new Error(`${path}.style.fontFallbacks must contain non-empty font family names.`);
101
+ }
102
+ const style = options.style;
103
+ if (style?.maxWidth !== undefined) {
104
+ validateLayoutValue(style.maxWidth, `${path}.style.maxWidth`, true);
105
+ if (Number.parseFloat(String(style.maxWidth)) <= 0) {
106
+ throw new Error(`${path}.style.maxWidth must be greater than zero.`);
107
+ }
108
+ }
109
+ if (style?.lineHeight !== undefined &&
110
+ (!Number.isFinite(style.lineHeight) || style.lineHeight <= 0)) {
111
+ throw new Error(`${path}.style.lineHeight must be a finite number greater than zero.`);
112
+ }
113
+ if (style?.letterSpacing !== undefined &&
114
+ !Number.isFinite(style.letterSpacing)) {
115
+ throw new Error(`${path}.style.letterSpacing must be a finite number.`);
116
+ }
117
+ if (style?.maxLines !== undefined &&
118
+ (!Number.isFinite(style.maxLines) ||
119
+ !Number.isInteger(style.maxLines) ||
120
+ style.maxLines < 1)) {
121
+ throw new Error(`${path}.style.maxLines must be a positive integer.`);
122
+ }
123
+ if (style?.direction !== undefined &&
124
+ !['auto', 'ltr', 'rtl'].includes(style.direction)) {
125
+ throw new Error(`${path}.style.direction must be "auto", "ltr", or "rtl".`);
126
+ }
127
+ if (style?.wrap !== undefined &&
128
+ !['word', 'character', 'none'].includes(style.wrap)) {
129
+ throw new Error(`${path}.style.wrap must be "word", "character", or "none".`);
130
+ }
131
+ if (style?.overflow !== undefined &&
132
+ !['clip', 'ellipsis'].includes(style.overflow)) {
133
+ throw new Error(`${path}.style.overflow must be "clip" or "ellipsis".`);
134
+ }
135
+ const strokeStyle = options.style?.strokeStyle;
136
+ if (strokeStyle) {
137
+ if (!Number.isFinite(strokeStyle.width) || strokeStyle.width < 0) {
138
+ throw new Error(`${path}.style.strokeStyle.width must be a non-negative finite number.`);
139
+ }
140
+ if (typeof strokeStyle.color !== 'string' || !strokeStyle.color.trim()) {
141
+ throw new Error(`${path}.style.strokeStyle.color must be a non-empty string.`);
142
+ }
143
+ }
144
+ validateWatermarkLayout(options.layout, options.position !== undefined, `${path}.layout`);
145
+ }
146
+ function validateImageOptions(options, path, hasExternalPosition = false) {
147
+ validateAlpha(options, path);
148
+ validateBlendMode(options.blendMode, path);
149
+ validateWatermarkLayout(options.layout, options.position !== undefined || hasExternalPosition, `${path}.layout`);
150
+ }
151
+ export function validateTextMarkOptions(options) {
152
+ validateCommonOptions(options);
153
+ options.watermarkTexts.forEach((textOptions, index) => {
154
+ validateTextOptions(textOptions, `watermarkTexts[${index}]`);
155
+ });
156
+ }
157
+ export function validateImageMarkOptions(options) {
158
+ validateCommonOptions(options);
159
+ if (Object.prototype.hasOwnProperty.call(options, 'watermarkImage') ||
160
+ Object.prototype.hasOwnProperty.call(options, 'watermarkPositions')) {
161
+ throw new Error('watermarkImage and watermarkPositions were removed in v2; use watermarkImages[].position.');
162
+ }
163
+ options.watermarkImages?.forEach((imageOptions, index) => {
164
+ validateImageOptions(imageOptions, `watermarkImages[${index}]`);
165
+ });
166
+ }
167
+ export function validateMarkOptions(options) {
168
+ validateCommonOptions(options);
169
+ if (Object.prototype.hasOwnProperty.call(options, 'watermarkImage') ||
170
+ Object.prototype.hasOwnProperty.call(options, 'watermarkPositions')) {
171
+ throw new Error('watermarkImage and watermarkPositions were removed in v2; use watermarks with an image layer.');
172
+ }
173
+ options.watermarkImages?.forEach((imageOptions, index) => {
174
+ validateImageOptions(imageOptions, `watermarkImages[${index}]`);
175
+ });
176
+ options.watermarks?.forEach((layer, index) => {
177
+ if (layer.type === 'image') {
178
+ validateImageOptions(layer, `watermarks[${index}]`);
179
+ }
180
+ else {
181
+ validateTextOptions(layer, `watermarks[${index}]`);
182
+ }
183
+ });
184
+ options.watermarkTexts?.forEach((textOptions, index) => {
185
+ validateTextOptions(textOptions, `watermarkTexts[${index}]`);
186
+ });
187
+ }
@@ -0,0 +1,105 @@
1
+ import type { MarkerImageInfo } from '../image-info.js';
2
+ export interface WebCanvas {
3
+ width: number;
4
+ height: number;
5
+ getContext(contextId: '2d'): WebCanvasContext | null;
6
+ toDataURL(type?: string, quality?: number): string;
7
+ toBlob(callback: (blob: Blob | null) => void, type?: string, quality?: number): void;
8
+ }
9
+ export interface WebCanvasContext {
10
+ [key: string]: unknown;
11
+ canvas: WebCanvas;
12
+ globalAlpha: number;
13
+ globalCompositeOperation: string;
14
+ fillStyle: unknown;
15
+ font: string;
16
+ textAlign: string;
17
+ textBaseline: string;
18
+ direction?: string;
19
+ letterSpacing?: string;
20
+ shadowBlur: number;
21
+ shadowColor: string;
22
+ shadowOffsetX: number;
23
+ shadowOffsetY: number;
24
+ imageSmoothingEnabled: boolean;
25
+ lineJoin: string;
26
+ lineWidth: number;
27
+ strokeStyle: unknown;
28
+ save(): void;
29
+ restore(): void;
30
+ beginPath(): void;
31
+ closePath(): void;
32
+ clip(): void;
33
+ moveTo(x: number, y: number): void;
34
+ lineTo(x: number, y: number): void;
35
+ quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): void;
36
+ rect(x: number, y: number, width: number, height: number): void;
37
+ fill(): void;
38
+ fillRect(x: number, y: number, width: number, height: number): void;
39
+ translate(x: number, y: number): void;
40
+ rotate(angle: number): void;
41
+ scale(x: number, y: number): void;
42
+ transform(a: number, b: number, c: number, d: number, e: number, f: number): void;
43
+ drawImage(...args: unknown[]): void;
44
+ fillText(text: string, x: number, y: number): void;
45
+ strokeText(text: string, x: number, y: number): void;
46
+ measureText(text: string): WebTextMetrics;
47
+ getImageData(sx: number, sy: number, sw: number, sh: number): WebImageData;
48
+ putImageData(imageData: WebImageData, dx: number, dy: number): void;
49
+ }
50
+ export interface WebImageData {
51
+ data: Uint8Array | Uint8ClampedArray;
52
+ width?: number;
53
+ height?: number;
54
+ }
55
+ export interface WebTextMetrics {
56
+ width: number;
57
+ actualBoundingBoxAscent?: number;
58
+ actualBoundingBoxDescent?: number;
59
+ fontBoundingBoxAscent?: number;
60
+ fontBoundingBoxDescent?: number;
61
+ }
62
+ export interface WebImageElement {
63
+ src: string;
64
+ crossOrigin?: string | null;
65
+ complete?: boolean;
66
+ width?: number;
67
+ height?: number;
68
+ naturalWidth?: number;
69
+ naturalHeight?: number;
70
+ onload: (() => void) | null;
71
+ onerror: ((event?: unknown) => void) | null;
72
+ decode?: () => Promise<void>;
73
+ }
74
+ /**
75
+ * DOM-adjacent resources used by a Web Marker instance. Providing this object
76
+ * keeps object-URL ownership with the instance and makes browser APIs
77
+ * replaceable by a desktop host without changing rendering algorithms.
78
+ */
79
+ export interface WebResourceAdapter {
80
+ /** Standard `new Image()` and `document.createElement('img')` both fit. */
81
+ createImage?: () => unknown;
82
+ /** Standard `document.createElement('canvas')` fits without a cast. */
83
+ createCanvas?: (width: number, height: number) => unknown;
84
+ createObjectURL?: (source: Blob) => string;
85
+ revokeObjectURL?: (url: string) => void;
86
+ /** Optional byte reader for File/Blob policy boundaries. */
87
+ readBlobBytes?: (source: Blob) => Promise<ArrayBuffer>;
88
+ }
89
+ export interface LoadedWebImage {
90
+ image: unknown;
91
+ width: number;
92
+ height: number;
93
+ cleanup(): void;
94
+ }
95
+ /**
96
+ * Read dimensions, encoded format, and orientation without rendering a
97
+ * composition. Encoded bytes are preferred so EXIF orientation remains
98
+ * observable; drawable dimensions are the cross-origin fallback.
99
+ */
100
+ export declare function getWebImageInfo(source: unknown, resources?: WebResourceAdapter, signal?: AbortSignal): Promise<MarkerImageInfo>;
101
+ /** Load a browser drawable from a URL, data URL, {uri}, Blob/File, or image element. */
102
+ export declare function loadWebImage(source: unknown, resources?: WebResourceAdapter, signal?: AbortSignal): Promise<LoadedWebImage>;
103
+ export declare function createWebCanvas(width: number, height: number, resources?: WebResourceAdapter): WebCanvas;
104
+ export declare function getCanvasContext(canvas: WebCanvas): WebCanvasContext;
105
+ export declare function getSecurityErrorMessage(operation: string): string;