@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,57 @@
1
+ import type { MarkerOperation, MarkerResult } from './result.js';
2
+ import { type MarkerResultDescriptor } from './result.js';
3
+ export type ImageMarkerErrorCode = 'ABORTED' | 'TIMEOUT' | 'INVALID_OPTIONS' | 'LINKING_ERROR' | 'NATIVE_ERROR' | 'RENDER_FAILED' | 'ENCODE_FAILED' | 'DECODE_FAILED' | 'UNKNOWN';
4
+ export type MarkerJobPhase = 'queued' | 'validating' | 'rendering' | 'detecting' | 'encoding' | 'completed';
5
+ export interface MarkerJobProgress {
6
+ jobId: string;
7
+ operation: MarkerOperation;
8
+ phase: MarkerJobPhase;
9
+ completed: number;
10
+ total: 1;
11
+ progress: number;
12
+ }
13
+ export interface MarkerJobOptions {
14
+ signal?: AbortSignal;
15
+ /** Reject and cancel the native operation after this many milliseconds. */
16
+ timeoutMs?: number;
17
+ /** Observer failures never interrupt image processing. */
18
+ onProgress?: (progress: MarkerJobProgress) => void;
19
+ }
20
+ export declare class ImageMarkerError extends Error {
21
+ readonly code: ImageMarkerErrorCode;
22
+ readonly jobId?: string;
23
+ readonly operation?: MarkerOperation;
24
+ readonly cause?: unknown;
25
+ constructor(code: ImageMarkerErrorCode, message: string, context?: {
26
+ jobId?: string;
27
+ operation?: MarkerOperation;
28
+ cause?: unknown;
29
+ });
30
+ }
31
+ export declare function createMarkerJobId(operation: MarkerOperation): string;
32
+ export declare function normalizeMarkerError(error: unknown, context: {
33
+ jobId: string;
34
+ operation: MarkerOperation;
35
+ }): ImageMarkerError;
36
+ interface RunControlledMarkerJobOptions<Result> {
37
+ operation: MarkerOperation;
38
+ control?: MarkerJobOptions;
39
+ task: (jobId: string) => Promise<Result>;
40
+ cancel?: (jobId: string) => Promise<unknown> | unknown;
41
+ workPhase?: Extract<MarkerJobPhase, 'rendering' | 'detecting'>;
42
+ finishingPhase?: Extract<MarkerJobPhase, 'encoding'>;
43
+ }
44
+ export interface ControlledMarkerJobResult<Result> {
45
+ jobId: string;
46
+ durationMs: number;
47
+ value: Result;
48
+ }
49
+ export declare function runControlledMarkerJob<Result>({ operation, control, task, cancel, workPhase, finishingPhase, }: RunControlledMarkerJobOptions<Result>): Promise<ControlledMarkerJobResult<Result>>;
50
+ interface RunMarkerJobOptions {
51
+ descriptor: MarkerResultDescriptor;
52
+ control?: MarkerJobOptions;
53
+ task: (jobId: string) => Promise<string>;
54
+ cancel?: (jobId: string) => Promise<unknown> | unknown;
55
+ }
56
+ export declare function runMarkerJob({ descriptor, control, task, cancel, }: RunMarkerJobOptions): Promise<MarkerResult>;
57
+ export {};
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ImageMarkerError = void 0;
4
+ exports.createMarkerJobId = createMarkerJobId;
5
+ exports.normalizeMarkerError = normalizeMarkerError;
6
+ exports.runControlledMarkerJob = runControlledMarkerJob;
7
+ exports.runMarkerJob = runMarkerJob;
8
+ const result_js_1 = require("./result.js");
9
+ class ImageMarkerError extends Error {
10
+ constructor(code, message, context = {}) {
11
+ super(message);
12
+ this.name = 'ImageMarkerError';
13
+ this.code = code;
14
+ this.jobId = context.jobId;
15
+ this.operation = context.operation;
16
+ this.cause = context.cause;
17
+ }
18
+ }
19
+ exports.ImageMarkerError = ImageMarkerError;
20
+ let jobSequence = 0;
21
+ function createMarkerJobId(operation) {
22
+ jobSequence = (jobSequence + 1) % Number.MAX_SAFE_INTEGER;
23
+ return `${operation}-${Date.now().toString(36)}-${jobSequence.toString(36)}`;
24
+ }
25
+ function validateControlOptions(options) {
26
+ if (options.timeoutMs !== undefined &&
27
+ (!Number.isFinite(options.timeoutMs) ||
28
+ !Number.isInteger(options.timeoutMs) ||
29
+ options.timeoutMs <= 0)) {
30
+ throw new ImageMarkerError('INVALID_OPTIONS', 'timeoutMs must be a positive finite integer.');
31
+ }
32
+ }
33
+ function report(options, jobId, operation, phase, progress) {
34
+ try {
35
+ options.onProgress?.({
36
+ jobId,
37
+ operation,
38
+ phase,
39
+ completed: phase === 'completed' ? 1 : 0,
40
+ total: 1,
41
+ progress,
42
+ });
43
+ }
44
+ catch {
45
+ // Progress observers cannot change operation results.
46
+ }
47
+ }
48
+ function inferErrorCode(error) {
49
+ const message = error instanceof Error
50
+ ? error.message.toLowerCase()
51
+ : String(error).toLowerCase();
52
+ if (message.includes('link') || message.includes('expo go')) {
53
+ return 'LINKING_ERROR';
54
+ }
55
+ if (message.includes('decode') || message.includes('load image')) {
56
+ return 'DECODE_FAILED';
57
+ }
58
+ if (message.includes('encode') || message.includes('save')) {
59
+ return 'ENCODE_FAILED';
60
+ }
61
+ if (message.includes('required') ||
62
+ message.includes('invalid') ||
63
+ message.includes('must be') ||
64
+ message.includes('please set')) {
65
+ return 'INVALID_OPTIONS';
66
+ }
67
+ if (message.includes('render') || message.includes('watermark')) {
68
+ return 'RENDER_FAILED';
69
+ }
70
+ return 'NATIVE_ERROR';
71
+ }
72
+ function normalizeMarkerError(error, context) {
73
+ if (error instanceof ImageMarkerError) {
74
+ return error;
75
+ }
76
+ const message = error instanceof Error && error.message
77
+ ? error.message
78
+ : 'Image marker operation failed.';
79
+ return new ImageMarkerError(inferErrorCode(error), message, {
80
+ ...context,
81
+ cause: error,
82
+ });
83
+ }
84
+ async function runControlledMarkerJob({ operation, control = {}, task, cancel, workPhase = 'rendering', finishingPhase, }) {
85
+ validateControlOptions(control);
86
+ const jobId = createMarkerJobId(operation);
87
+ const startedAt = Date.now();
88
+ let timeout;
89
+ let rejectControl;
90
+ let controlSettled = false;
91
+ const cancelWith = (code, message) => {
92
+ if (controlSettled)
93
+ return;
94
+ controlSettled = true;
95
+ Promise.resolve(cancel?.(jobId)).catch(() => {
96
+ // The original abort/timeout remains authoritative.
97
+ });
98
+ rejectControl?.(new ImageMarkerError(code, message, {
99
+ jobId,
100
+ operation,
101
+ }));
102
+ };
103
+ const onAbort = () => cancelWith('ABORTED', 'Image marker operation was aborted.');
104
+ if (control.signal?.aborted) {
105
+ throw new ImageMarkerError('ABORTED', 'Image marker operation was aborted before it started.', { jobId, operation });
106
+ }
107
+ const controlPromise = new Promise((_resolve, reject) => {
108
+ rejectControl = reject;
109
+ });
110
+ control.signal?.addEventListener('abort', onAbort, { once: true });
111
+ if (control.timeoutMs !== undefined) {
112
+ timeout = setTimeout(() => cancelWith('TIMEOUT', `Image marker operation timed out after ${control.timeoutMs}ms.`), control.timeoutMs);
113
+ }
114
+ report(control, jobId, operation, 'queued', 0);
115
+ report(control, jobId, operation, 'validating', 0.1);
116
+ try {
117
+ report(control, jobId, operation, workPhase, 0.25);
118
+ const value = await Promise.race([task(jobId), controlPromise]);
119
+ controlSettled = true;
120
+ if (finishingPhase) {
121
+ report(control, jobId, operation, finishingPhase, 0.85);
122
+ }
123
+ const result = {
124
+ jobId,
125
+ durationMs: Math.max(0, Date.now() - startedAt),
126
+ value,
127
+ };
128
+ report(control, jobId, operation, 'completed', 1);
129
+ return result;
130
+ }
131
+ catch (error) {
132
+ throw normalizeMarkerError(error, { jobId, operation });
133
+ }
134
+ finally {
135
+ controlSettled = true;
136
+ if (timeout !== undefined)
137
+ clearTimeout(timeout);
138
+ control.signal?.removeEventListener('abort', onAbort);
139
+ }
140
+ }
141
+ async function runMarkerJob({ descriptor, control = {}, task, cancel, }) {
142
+ const result = await runControlledMarkerJob({
143
+ operation: descriptor.operation,
144
+ control,
145
+ task,
146
+ cancel,
147
+ workPhase: 'rendering',
148
+ finishingPhase: 'encoding',
149
+ });
150
+ return (0, result_js_1.createMarkerResult)(result.jobId, result.value, result.durationMs, descriptor);
151
+ }
@@ -0,0 +1,19 @@
1
+ import type { WatermarkLayout } from './index.js';
2
+ export declare const MAX_TILE_COPIES = 4096;
3
+ export interface LayoutSize {
4
+ width: number;
5
+ height: number;
6
+ }
7
+ export interface LayoutPoint {
8
+ x: number;
9
+ y: number;
10
+ }
11
+ export type TileWatermarkLayout = Extract<WatermarkLayout, {
12
+ type: 'tile';
13
+ }>;
14
+ export declare function resolveLayoutValue(value: number | string | undefined, relativeTo: number, label: string): number;
15
+ /**
16
+ * Resolve the top-left visible bounds for every tile. Rows and columns begin
17
+ * one step outside the canvas so partial edge tiles preserve the grid phase.
18
+ */
19
+ export declare function resolveTilePlacements(layout: TileWatermarkLayout, canvas: LayoutSize, item: LayoutSize): LayoutPoint[];
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_TILE_COPIES = void 0;
4
+ exports.resolveLayoutValue = resolveLayoutValue;
5
+ exports.resolveTilePlacements = resolveTilePlacements;
6
+ exports.MAX_TILE_COPIES = 4096;
7
+ function assertSize(size, label) {
8
+ if (!Number.isFinite(size.width) ||
9
+ !Number.isFinite(size.height) ||
10
+ size.width <= 0 ||
11
+ size.height <= 0) {
12
+ throw new Error(`${label} dimensions must be finite numbers greater than 0.`);
13
+ }
14
+ }
15
+ function resolveLayoutValue(value, relativeTo, label) {
16
+ if (value === undefined) {
17
+ return 0;
18
+ }
19
+ if (typeof value === 'number') {
20
+ if (!Number.isFinite(value)) {
21
+ throw new Error(`${label} must be a finite number or percentage.`);
22
+ }
23
+ return value;
24
+ }
25
+ const normalized = value.trim();
26
+ const match = /^([+-]?(?:\d+(?:\.\d+)?|\.\d+))(%)?$/.exec(normalized);
27
+ if (!match) {
28
+ throw new Error(`${label} must be a finite number or percentage.`);
29
+ }
30
+ const parsed = Number(match[1]);
31
+ return match[2] ? (relativeTo * parsed) / 100 : parsed;
32
+ }
33
+ function normalizedOffset(value, step) {
34
+ return ((value % step) + step) % step;
35
+ }
36
+ /**
37
+ * Resolve the top-left visible bounds for every tile. Rows and columns begin
38
+ * one step outside the canvas so partial edge tiles preserve the grid phase.
39
+ */
40
+ function resolveTilePlacements(layout, canvas, item) {
41
+ assertSize(canvas, 'canvas');
42
+ assertSize(item, 'watermark');
43
+ const gapX = resolveLayoutValue(layout.gapX, canvas.width, 'layout.gapX');
44
+ const gapY = resolveLayoutValue(layout.gapY, canvas.height, 'layout.gapY');
45
+ if (gapX < 0 || gapY < 0) {
46
+ throw new Error('layout gaps must be non-negative.');
47
+ }
48
+ const offsetX = resolveLayoutValue(layout.offsetX, canvas.width, 'layout.offsetX');
49
+ const offsetY = resolveLayoutValue(layout.offsetY, canvas.height, 'layout.offsetY');
50
+ const stepX = item.width + gapX;
51
+ const stepY = item.height + gapY;
52
+ if (!Number.isFinite(stepX) || !Number.isFinite(stepY)) {
53
+ throw new Error('tile layout step must be finite.');
54
+ }
55
+ const phaseX = normalizedOffset(offsetX, stepX);
56
+ const phaseY = normalizedOffset(offsetY, stepY);
57
+ const placements = [];
58
+ for (let row = -1, y = phaseY - stepY; y < canvas.height; row += 1, y += stepY) {
59
+ if (y + item.height <= 0) {
60
+ continue;
61
+ }
62
+ const staggerOffset = layout.stagger && row % 2 !== 0 ? stepX / 2 : 0;
63
+ const rowPhaseX = normalizedOffset(phaseX + staggerOffset, stepX);
64
+ for (let x = rowPhaseX - stepX; x < canvas.width; x += stepX) {
65
+ if (x + item.width <= 0) {
66
+ continue;
67
+ }
68
+ placements.push({ x, y });
69
+ if (placements.length > exports.MAX_TILE_COPIES) {
70
+ throw new Error(`tile layout exceeds the maximum of ${exports.MAX_TILE_COPIES} copies per layer.`);
71
+ }
72
+ }
73
+ }
74
+ return placements;
75
+ }
@@ -0,0 +1 @@
1
+ export { default } from './web/index.js';
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.default = void 0;
7
+ // The default implementation is browser-safe so standard web bundlers and
8
+ // server-side rendering can import the package without loading React Native's
9
+ // NativeModules. Metro selects marker.native.ts on iOS and Android.
10
+ var index_js_1 = require("./web/index.js");
11
+ Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(index_js_1).default; } });
@@ -0,0 +1,44 @@
1
+ import { WATERMARK_RECIPE_SCHEMA_VERSION, type LegacyWatermarkRecipeDefinition as SharedLegacyWatermarkRecipeDefinition, type WatermarkRecipeCondition as SharedWatermarkRecipeCondition, type WatermarkRecipeDefinition as SharedWatermarkRecipeDefinition, type WatermarkRecipeDefinitionLayer as SharedWatermarkRecipeDefinitionLayer, type WatermarkRecipeDocument as SharedWatermarkRecipeDocument, type WatermarkRecipeInput as SharedWatermarkRecipeInput, type WatermarkRecipeLayer as SharedWatermarkRecipeLayer, type WatermarkRecipeOptions as SharedWatermarkRecipeOptions, type WatermarkRecipeOutputOptions as SharedWatermarkRecipeOutputOptions, type WatermarkRecipeSchemaVersion as SharedWatermarkRecipeSchemaVersion, type WatermarkRecipeVariable as SharedWatermarkRecipeVariable } from '@image-marker/recipe';
2
+ import type { MarkOptions } from './index.js';
3
+ import type { MarkerResult } from './result.js';
4
+ import type { MarkerJobOptions } from './job.js';
5
+ import type { WatermarkBatchOptions, WatermarkBatchResult } from './batch.js';
6
+ export type { WatermarkBatchAbortedResult, WatermarkBatchFulfilledResult, WatermarkBatchOptions, WatermarkBatchProgress, WatermarkBatchRejectedResult, WatermarkBatchResult, } from './batch.js';
7
+ export { WATERMARK_RECIPE_SCHEMA_VERSION };
8
+ export type WatermarkRecipeSchemaVersion = SharedWatermarkRecipeSchemaVersion;
9
+ export type WatermarkRecipeVariable = SharedWatermarkRecipeVariable;
10
+ export type WatermarkRecipeCondition = SharedWatermarkRecipeCondition;
11
+ export type WatermarkRecipeLayer = SharedWatermarkRecipeLayer<any>;
12
+ export type WatermarkRecipeDefinitionLayer = SharedWatermarkRecipeDefinitionLayer<any>;
13
+ export type WatermarkRecipeOutputOptions = SharedWatermarkRecipeOutputOptions;
14
+ export type WatermarkRecipeOptions = SharedWatermarkRecipeOptions<any>;
15
+ export type WatermarkRecipeDefinition = SharedWatermarkRecipeDefinition<any>;
16
+ export type LegacyWatermarkRecipeDefinition = SharedLegacyWatermarkRecipeDefinition<any>;
17
+ export type WatermarkRecipeDocument = SharedWatermarkRecipeDocument<any>;
18
+ export type WatermarkRecipeInput = SharedWatermarkRecipeInput<any>;
19
+ /** Return the Core 2 structured result. */
20
+ export interface WatermarkMarkerResultRecipeOptions {
21
+ resultType?: 'result';
22
+ }
23
+ /** Return encoded browser bytes without creating an object URL. Web only. */
24
+ export interface WatermarkBlobRecipeResultOptions {
25
+ resultType: 'blob';
26
+ }
27
+ export type WatermarkRecipeResultOptions = WatermarkMarkerResultRecipeOptions | WatermarkBlobRecipeResultOptions;
28
+ export interface WatermarkRecipe<Result = MarkerResult> {
29
+ /** Version of the serializable recipe definition. */
30
+ readonly schemaVersion: WatermarkRecipeSchemaVersion;
31
+ /** Return a detached definition suitable for JSON persistence. */
32
+ toJSON(): WatermarkRecipeDefinition;
33
+ /** Apply the saved layers and output settings to one source image. */
34
+ apply(input: WatermarkRecipeInput, control?: MarkerJobOptions): Promise<Result>;
35
+ /** Apply the recipe to many images while preserving input result order. */
36
+ applyMany(inputs: readonly WatermarkRecipeInput[], options?: WatermarkBatchOptions<Result>): Promise<Array<WatermarkBatchResult<Result>>>;
37
+ }
38
+ type RecipeRenderer<Result> = (options: MarkOptions, control?: MarkerJobOptions) => Promise<Result>;
39
+ /**
40
+ * Upgrade a serialized Recipe v1 document to the v2 layer/output structure.
41
+ */
42
+ export declare function migrateWatermarkRecipe(document: WatermarkRecipeDocument): WatermarkRecipeDefinition;
43
+ export declare function createWatermarkRecipe<Result>(options: WatermarkRecipeOptions, renderer: RecipeRenderer<Result>, maximumConcurrency?: number): WatermarkRecipe<Result>;
44
+ export declare function importWatermarkRecipe<Result>(document: WatermarkRecipeDocument, renderer: RecipeRenderer<Result>, maximumConcurrency?: number): WatermarkRecipe<Result>;
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WATERMARK_RECIPE_SCHEMA_VERSION = void 0;
4
+ exports.migrateWatermarkRecipe = migrateWatermarkRecipe;
5
+ exports.createWatermarkRecipe = createWatermarkRecipe;
6
+ exports.importWatermarkRecipe = importWatermarkRecipe;
7
+ const recipe_1 = require("@image-marker/recipe");
8
+ Object.defineProperty(exports, "WATERMARK_RECIPE_SCHEMA_VERSION", { enumerable: true, get: function () { return recipe_1.WATERMARK_RECIPE_SCHEMA_VERSION; } });
9
+ const validate_js_1 = require("./validate.js");
10
+ const batch_js_1 = require("./batch.js");
11
+ function toRenderLayer(layer) {
12
+ const renderLayer = { ...layer };
13
+ delete renderLayer.id;
14
+ delete renderLayer.name;
15
+ delete renderLayer.visible;
16
+ delete renderLayer.locked;
17
+ delete renderLayer.groupId;
18
+ delete renderLayer.visibleWhen;
19
+ return renderLayer;
20
+ }
21
+ function snapshotRecipeOptions(options) {
22
+ if (!options || !Array.isArray(options.layers)) {
23
+ throw new Error('createRecipe requires an ordered layers array.');
24
+ }
25
+ if (options.schemaVersion !== undefined &&
26
+ options.schemaVersion !== recipe_1.WATERMARK_RECIPE_SCHEMA_VERSION) {
27
+ throw new Error(`Unsupported recipe schemaVersion: ${String(options.schemaVersion)}.`);
28
+ }
29
+ if (options.layers.length === 0) {
30
+ throw new Error('createRecipe requires at least one layer.');
31
+ }
32
+ const ids = new Set();
33
+ options.layers.forEach((layer, index) => {
34
+ if (layer.type === 'image' && !layer.src) {
35
+ throw new Error(`layers[${index}].src is required.`);
36
+ }
37
+ const id = layer.id ?? `layer-${index + 1}`;
38
+ if (ids.has(id))
39
+ throw new Error(`Duplicate layer id "${id}".`);
40
+ ids.add(id);
41
+ });
42
+ const runtimeOptions = options;
43
+ const unsupportedKey = [
44
+ 'backgroundImage',
45
+ 'filename',
46
+ 'watermarkTexts',
47
+ 'watermarkImage',
48
+ 'watermarkPositions',
49
+ 'watermarkImages',
50
+ 'watermarks',
51
+ 'quality',
52
+ 'saveFormat',
53
+ 'matteColor',
54
+ 'rotationCanvasMode',
55
+ 'maxSize',
56
+ ].find((key) => Object.prototype.hasOwnProperty.call(runtimeOptions, key));
57
+ if (unsupportedKey) {
58
+ throw new Error(`createRecipe does not accept "${unsupportedKey}" in Recipe v2; use ordered layers, put encoding options under output, and pass per-image fields to apply().`);
59
+ }
60
+ const definition = (0, recipe_1.createWatermarkRecipeDefinition)(options);
61
+ definition.layers.forEach((layer, index) => {
62
+ const original = options.layers[index];
63
+ if (layer.type === 'image' && original?.type === 'image') {
64
+ layer.src = original.src;
65
+ }
66
+ });
67
+ (0, validate_js_1.validateMarkOptions)({
68
+ backgroundImage: { src: 'recipe-validation-placeholder' },
69
+ watermarks: definition.layers.map(toRenderLayer),
70
+ ...definition.output,
71
+ });
72
+ return definition;
73
+ }
74
+ /**
75
+ * Upgrade a serialized Recipe v1 document to the v2 layer/output structure.
76
+ */
77
+ function migrateWatermarkRecipe(document) {
78
+ return snapshotRecipeOptions((0, recipe_1.migrateWatermarkRecipe)(document));
79
+ }
80
+ function snapshotInput(input) {
81
+ return {
82
+ backgroundImage: input?.backgroundImage
83
+ ? { ...input.backgroundImage }
84
+ : input?.backgroundImage,
85
+ filename: input?.filename,
86
+ variables: input?.variables ? { ...input.variables } : input?.variables,
87
+ };
88
+ }
89
+ function createMarkOptions(recipe, input, index) {
90
+ const materialized = (0, recipe_1.materializeWatermarkRecipe)(recipe, {
91
+ filename: input.filename,
92
+ index,
93
+ variables: input.variables,
94
+ });
95
+ const imageSources = new Map();
96
+ recipe.layers.forEach((layer) => {
97
+ if (layer.type === 'image') {
98
+ imageSources.set(layer.id, layer.src);
99
+ }
100
+ });
101
+ materialized.layers.forEach((layer) => {
102
+ if (layer.type === 'image' && imageSources.has(layer.id)) {
103
+ layer.src = imageSources.get(layer.id);
104
+ }
105
+ });
106
+ return {
107
+ backgroundImage: { ...input.backgroundImage },
108
+ watermarks: materialized.layers.map(toRenderLayer),
109
+ filename: input.filename,
110
+ ...materialized.output,
111
+ };
112
+ }
113
+ function canonicalOutputFilename(filename, saveFormat) {
114
+ const trimmed = filename.trim();
115
+ if (!trimmed)
116
+ return undefined;
117
+ const knownExtension = ['.jpeg', '.jpg', '.png', '.webp'].find((extension) => trimmed.toLowerCase().endsWith(extension));
118
+ const stem = knownExtension
119
+ ? trimmed.slice(0, -knownExtension.length)
120
+ : trimmed;
121
+ const extension = saveFormat === 'png' ? '.png' : saveFormat === 'webp' ? '.webp' : '.jpg';
122
+ return `${stem}${extension}`.toLowerCase();
123
+ }
124
+ function validateUniqueFilenames(inputs, saveFormat) {
125
+ const seen = new Map();
126
+ inputs.forEach((input, index) => {
127
+ if (input.filename === undefined)
128
+ return;
129
+ if (typeof input.filename !== 'string') {
130
+ throw new Error(`inputs[${index}].filename must be a string.`);
131
+ }
132
+ const canonical = canonicalOutputFilename(input.filename, saveFormat);
133
+ if (!canonical)
134
+ return;
135
+ const previousIndex = seen.get(canonical);
136
+ if (previousIndex !== undefined) {
137
+ throw new Error(`Duplicate output filename "${canonical}" for inputs ${previousIndex} and ${index}.`);
138
+ }
139
+ seen.set(canonical, index);
140
+ });
141
+ }
142
+ function createWatermarkRecipe(options, renderer, maximumConcurrency = 1) {
143
+ const recipe = snapshotRecipeOptions(options);
144
+ const applySnapshot = (input, index, control) => {
145
+ if (!input?.backgroundImage?.src) {
146
+ return Promise.reject(new Error('please set image!'));
147
+ }
148
+ try {
149
+ return renderer(createMarkOptions(recipe, input, index), control);
150
+ }
151
+ catch (error) {
152
+ return Promise.reject(error);
153
+ }
154
+ };
155
+ return {
156
+ schemaVersion: recipe_1.WATERMARK_RECIPE_SCHEMA_VERSION,
157
+ toJSON() {
158
+ return (0, recipe_1.cloneRecipeValue)(recipe);
159
+ },
160
+ apply(input, control) {
161
+ return applySnapshot(snapshotInput(input), 0, control);
162
+ },
163
+ async applyMany(inputs, batchOptions = {}) {
164
+ if (!Array.isArray(inputs)) {
165
+ throw new Error('applyMany inputs must be an array.');
166
+ }
167
+ const queuedInputs = inputs.map(snapshotInput);
168
+ validateUniqueFilenames(queuedInputs, recipe.output.saveFormat);
169
+ return (0, batch_js_1.runWatermarkBatch)(queuedInputs, (input, index) => applySnapshot(input, index, { signal: batchOptions.signal }), batchOptions, maximumConcurrency, 'applyMany');
170
+ },
171
+ };
172
+ }
173
+ function importWatermarkRecipe(document, renderer, maximumConcurrency = 1) {
174
+ return createWatermarkRecipe(migrateWatermarkRecipe(document), renderer, maximumConcurrency);
175
+ }
@@ -0,0 +1,34 @@
1
+ export type MarkerOperation = 'mark' | 'markText' | 'markImage' | 'embedInvisible' | 'detectInvisible';
2
+ export type MarkerOutputFormat = 'png' | 'jpeg' | 'webp';
3
+ export interface MarkerMetadataResult {
4
+ /** Source orientation is applied to pixels and output orientation is upright. */
5
+ orientation: 'normalized';
6
+ /**
7
+ * Core v2 strips EXIF/GPS and other source metadata by default so stale or
8
+ * sensitive source claims are not copied onto newly rendered pixels.
9
+ */
10
+ policy: 'strip';
11
+ }
12
+ export interface MarkerResult {
13
+ /** Stable identifier shared by progress, errors, and native cancellation. */
14
+ jobId: string;
15
+ /** Public operation that produced this result. */
16
+ operation: MarkerOperation;
17
+ /** File path/URI on native or data URL in a browser/base64 request. */
18
+ uri: string;
19
+ /** Whether `uri` points at a file or embeds the encoded bytes. */
20
+ output: 'file' | 'data-url';
21
+ format: MarkerOutputFormat;
22
+ mimeType: 'image/png' | 'image/jpeg' | 'image/webp';
23
+ /** Total JavaScript-observed operation duration. */
24
+ durationMs: number;
25
+ metadata: MarkerMetadataResult;
26
+ /** Requested output basename, when supplied. */
27
+ filename?: string;
28
+ }
29
+ export interface MarkerResultDescriptor {
30
+ operation: MarkerOperation;
31
+ saveFormat?: 'png' | 'jpg' | 'webp' | 'base64';
32
+ filename?: string;
33
+ }
34
+ export declare function createMarkerResult(jobId: string, uri: string, durationMs: number, descriptor: MarkerResultDescriptor): MarkerResult;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createMarkerResult = createMarkerResult;
4
+ function resolveFormat(saveFormat, uri) {
5
+ if (uri?.startsWith('data:image/png'))
6
+ return 'png';
7
+ if (uri?.startsWith('data:image/jpeg'))
8
+ return 'jpeg';
9
+ if (uri?.startsWith('data:image/webp'))
10
+ return 'webp';
11
+ if (saveFormat === 'webp')
12
+ return 'webp';
13
+ return saveFormat === 'png' || saveFormat === 'base64' ? 'png' : 'jpeg';
14
+ }
15
+ function createMarkerResult(jobId, uri, durationMs, descriptor) {
16
+ if (typeof uri !== 'string' || !uri) {
17
+ throw new Error('The renderer returned an empty output URI.');
18
+ }
19
+ const format = resolveFormat(descriptor.saveFormat, uri);
20
+ const result = {
21
+ jobId,
22
+ operation: descriptor.operation,
23
+ uri,
24
+ output: uri.startsWith('data:') ? 'data-url' : 'file',
25
+ format,
26
+ mimeType: format === 'png'
27
+ ? 'image/png'
28
+ : format === 'webp'
29
+ ? 'image/webp'
30
+ : 'image/jpeg',
31
+ durationMs: Math.max(0, durationMs),
32
+ metadata: {
33
+ orientation: 'normalized',
34
+ policy: 'strip',
35
+ },
36
+ };
37
+ if (descriptor.filename !== undefined) {
38
+ result.filename = descriptor.filename;
39
+ }
40
+ return result;
41
+ }
@@ -0,0 +1,43 @@
1
+ import type { WatermarkBatchOptions, WatermarkBatchResult } from './batch.js';
2
+ import type { DetectInvisibleWatermarkOptions, EmbedInvisibleWatermarkOptions, InvisibleWatermarkDetectionData, InvisibleWatermarkPixelBuffer } from './invisible-watermark.js';
3
+ /** RGBA pixels decoded in display orientation by a server-side image codec. */
4
+ export interface InvisibleWatermarkDecodedImage extends InvisibleWatermarkPixelBuffer {
5
+ }
6
+ /** Image I/O boundary used by the dependency-free trace runtime. */
7
+ export interface InvisibleWatermarkCodec<Source, Output> {
8
+ decode(source: Source, options: {
9
+ maxSize: number;
10
+ }): Promise<InvisibleWatermarkDecodedImage>;
11
+ encode(image: InvisibleWatermarkDecodedImage, options: {
12
+ format: 'jpeg' | 'png';
13
+ quality: number;
14
+ filename?: string;
15
+ }): Promise<Output>;
16
+ }
17
+ export interface InvisibleWatermarkRuntimeOptions<Source, Output> {
18
+ codec: InvisibleWatermarkCodec<Source, Output>;
19
+ /** Hard upper bound for parallel codec/CPU work. @defaultValue 4 */
20
+ maxConcurrency?: number;
21
+ }
22
+ export type RuntimeEmbedInvisibleWatermarkOptions<Source> = Omit<EmbedInvisibleWatermarkOptions, 'image' | 'saveFormat'> & {
23
+ image: {
24
+ src: Source;
25
+ };
26
+ saveFormat?: 'png' | 'jpg';
27
+ };
28
+ export type RuntimeDetectInvisibleWatermarkOptions<Source> = Omit<DetectInvisibleWatermarkOptions, 'image' | 'worker'> & {
29
+ image: {
30
+ src: Source;
31
+ };
32
+ };
33
+ export interface InvisibleWatermarkRuntime<Source, Output> {
34
+ embedInvisible(options: RuntimeEmbedInvisibleWatermarkOptions<Source>): Promise<Output>;
35
+ detectInvisible(options: RuntimeDetectInvisibleWatermarkOptions<Source>): Promise<InvisibleWatermarkDetectionData>;
36
+ embedInvisibleMany(inputs: readonly RuntimeEmbedInvisibleWatermarkOptions<Source>[], options?: WatermarkBatchOptions<Output>): Promise<Array<WatermarkBatchResult<Output>>>;
37
+ detectInvisibleMany(inputs: readonly RuntimeDetectInvisibleWatermarkOptions<Source>[], options?: WatermarkBatchOptions<InvisibleWatermarkDetectionData>): Promise<Array<WatermarkBatchResult<InvisibleWatermarkDetectionData>>>;
38
+ }
39
+ /**
40
+ * Create a DOM-free invisible watermark runtime around an injected image codec.
41
+ * The core package does not install or choose a server-side image dependency.
42
+ */
43
+ export declare function createInvisibleWatermarkRuntime<Source, Output>(options: InvisibleWatermarkRuntimeOptions<Source, Output>): InvisibleWatermarkRuntime<Source, Output>;