@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,2 @@
1
+ export * from '../core/index.js';
2
+ export { default } from '../core/index.js';
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.default = void 0;
21
+ __exportStar(require("../core/index.js"), exports);
22
+ var index_js_1 = require("../core/index.js");
23
+ Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(index_js_1).default; } });
@@ -0,0 +1,8 @@
1
+ import type { EditorPoint, EditorSafeArea, EditorSnapContext, EditorSnapGuide } from './types.js';
2
+ export declare function normalizeSafeArea(safeArea?: Partial<EditorSafeArea>): EditorSafeArea;
3
+ export declare function snapLayerPosition(point: EditorPoint, context: EditorSnapContext): {
4
+ point: EditorPoint;
5
+ guides: EditorSnapGuide[];
6
+ };
7
+ export declare function distance(first: EditorPoint, second: EditorPoint): number;
8
+ export declare function angle(first: EditorPoint, second: EditorPoint): number;
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeSafeArea = normalizeSafeArea;
4
+ exports.snapLayerPosition = snapLayerPosition;
5
+ exports.distance = distance;
6
+ exports.angle = angle;
7
+ const DEFAULT_SNAP_THRESHOLD = 6;
8
+ function closest(value, candidates, threshold) {
9
+ return candidates
10
+ .map((candidate) => ({
11
+ ...candidate,
12
+ distance: Math.abs(candidate.value - value),
13
+ }))
14
+ .filter((candidate) => candidate.distance <= threshold)
15
+ .sort((left, right) => left.distance - right.distance)[0];
16
+ }
17
+ function normalizeSafeArea(safeArea = {}) {
18
+ return {
19
+ top: Math.max(0, safeArea.top ?? 0),
20
+ right: Math.max(0, safeArea.right ?? 0),
21
+ bottom: Math.max(0, safeArea.bottom ?? 0),
22
+ left: Math.max(0, safeArea.left ?? 0),
23
+ };
24
+ }
25
+ function snapLayerPosition(point, context) {
26
+ const safe = normalizeSafeArea(context.safeArea);
27
+ const threshold = context.threshold ?? DEFAULT_SNAP_THRESHOLD;
28
+ const canvasCenterX = context.canvas.width / 2;
29
+ const canvasCenterY = context.canvas.height / 2;
30
+ const layerCenterX = context.layer.width / 2;
31
+ const layerCenterY = context.layer.height / 2;
32
+ const right = context.canvas.width - safe.right - context.layer.width;
33
+ const bottom = context.canvas.height - safe.bottom - context.layer.height;
34
+ const xCandidates = [
35
+ {
36
+ value: 0,
37
+ guide: { axis: 'x', position: 0, kind: 'edge' },
38
+ },
39
+ {
40
+ value: safe.left,
41
+ guide: { axis: 'x', position: safe.left, kind: 'safe-area' },
42
+ },
43
+ {
44
+ value: canvasCenterX - layerCenterX,
45
+ guide: { axis: 'x', position: canvasCenterX, kind: 'center' },
46
+ },
47
+ {
48
+ value: right,
49
+ guide: {
50
+ axis: 'x',
51
+ position: context.canvas.width - safe.right,
52
+ kind: 'safe-area',
53
+ },
54
+ },
55
+ {
56
+ value: context.canvas.width - context.layer.width,
57
+ guide: {
58
+ axis: 'x',
59
+ position: context.canvas.width,
60
+ kind: 'edge',
61
+ },
62
+ },
63
+ ];
64
+ const yCandidates = [
65
+ {
66
+ value: 0,
67
+ guide: { axis: 'y', position: 0, kind: 'edge' },
68
+ },
69
+ {
70
+ value: safe.top,
71
+ guide: { axis: 'y', position: safe.top, kind: 'safe-area' },
72
+ },
73
+ {
74
+ value: canvasCenterY - layerCenterY,
75
+ guide: { axis: 'y', position: canvasCenterY, kind: 'center' },
76
+ },
77
+ {
78
+ value: bottom,
79
+ guide: {
80
+ axis: 'y',
81
+ position: context.canvas.height - safe.bottom,
82
+ kind: 'safe-area',
83
+ },
84
+ },
85
+ {
86
+ value: context.canvas.height - context.layer.height,
87
+ guide: {
88
+ axis: 'y',
89
+ position: context.canvas.height,
90
+ kind: 'edge',
91
+ },
92
+ },
93
+ ];
94
+ const snappedX = closest(point.x, xCandidates, threshold);
95
+ const snappedY = closest(point.y, yCandidates, threshold);
96
+ return {
97
+ point: {
98
+ x: snappedX?.value ?? point.x,
99
+ y: snappedY?.value ?? point.y,
100
+ },
101
+ guides: [snappedX?.guide, snappedY?.guide].filter((guide) => Boolean(guide)),
102
+ };
103
+ }
104
+ function distance(first, second) {
105
+ return Math.hypot(second.x - first.x, second.y - first.y);
106
+ }
107
+ function angle(first, second) {
108
+ return (Math.atan2(second.y - first.y, second.x - first.x) * 180) / Math.PI;
109
+ }
@@ -0,0 +1,5 @@
1
+ export { ImageMarkerEditorController } from './controller.js';
2
+ export { angle, distance, normalizeSafeArea, snapLayerPosition, } from './geometry.js';
3
+ export { createEditorViewportProjection, fitEditorSizeWithinMax, projectEditorPoint, projectEditorRecipe, unprojectEditorPoint, type EditorViewportProjection, } from './projection.js';
4
+ export { createBrandLogoLayer, createBrandTextLayer, createEditorBrandKit, createEditorTemplate, materializeEditorTemplate, } from './presets.js';
5
+ export type { EditorAlignment, EditorAsset, EditorAutosaveOptions, EditorBrandKit, EditorClipboardDocument, EditorContentCredentialsExport, EditorDistribution, EditorExportOptions, EditorExportResult, EditorInvisibleExport, EditorKeyCommand, EditorLayerBounds, EditorPoint, EditorRect, EditorRenderRequest, EditorSafeArea, EditorSelectionMode, EditorSerializedState, EditorSize, EditorSnapContext, EditorSnapGuide, EditorState, EditorTemplate, EditorTextLayerPatch, EditorViewportState, ImageMarkerEditorControllerOptions, ImageMarkerEditorRenderAdapter, MarkerImageInfo, WatermarkBlendMode, WatermarkRecipeDefinition, WatermarkRecipeDefinitionLayer, WatermarkRecipeDocument, WatermarkRecipeInput, WatermarkTextStyle, } from './types.js';
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.materializeEditorTemplate = exports.createEditorTemplate = exports.createEditorBrandKit = exports.createBrandTextLayer = exports.createBrandLogoLayer = exports.unprojectEditorPoint = exports.projectEditorRecipe = exports.projectEditorPoint = exports.fitEditorSizeWithinMax = exports.createEditorViewportProjection = exports.snapLayerPosition = exports.normalizeSafeArea = exports.distance = exports.angle = exports.ImageMarkerEditorController = void 0;
4
+ var controller_js_1 = require("./controller.js");
5
+ Object.defineProperty(exports, "ImageMarkerEditorController", { enumerable: true, get: function () { return controller_js_1.ImageMarkerEditorController; } });
6
+ var geometry_js_1 = require("./geometry.js");
7
+ Object.defineProperty(exports, "angle", { enumerable: true, get: function () { return geometry_js_1.angle; } });
8
+ Object.defineProperty(exports, "distance", { enumerable: true, get: function () { return geometry_js_1.distance; } });
9
+ Object.defineProperty(exports, "normalizeSafeArea", { enumerable: true, get: function () { return geometry_js_1.normalizeSafeArea; } });
10
+ Object.defineProperty(exports, "snapLayerPosition", { enumerable: true, get: function () { return geometry_js_1.snapLayerPosition; } });
11
+ var projection_js_1 = require("./projection.js");
12
+ Object.defineProperty(exports, "createEditorViewportProjection", { enumerable: true, get: function () { return projection_js_1.createEditorViewportProjection; } });
13
+ Object.defineProperty(exports, "fitEditorSizeWithinMax", { enumerable: true, get: function () { return projection_js_1.fitEditorSizeWithinMax; } });
14
+ Object.defineProperty(exports, "projectEditorPoint", { enumerable: true, get: function () { return projection_js_1.projectEditorPoint; } });
15
+ Object.defineProperty(exports, "projectEditorRecipe", { enumerable: true, get: function () { return projection_js_1.projectEditorRecipe; } });
16
+ Object.defineProperty(exports, "unprojectEditorPoint", { enumerable: true, get: function () { return projection_js_1.unprojectEditorPoint; } });
17
+ var presets_js_1 = require("./presets.js");
18
+ Object.defineProperty(exports, "createBrandLogoLayer", { enumerable: true, get: function () { return presets_js_1.createBrandLogoLayer; } });
19
+ Object.defineProperty(exports, "createBrandTextLayer", { enumerable: true, get: function () { return presets_js_1.createBrandTextLayer; } });
20
+ Object.defineProperty(exports, "createEditorBrandKit", { enumerable: true, get: function () { return presets_js_1.createEditorBrandKit; } });
21
+ Object.defineProperty(exports, "createEditorTemplate", { enumerable: true, get: function () { return presets_js_1.createEditorTemplate; } });
22
+ Object.defineProperty(exports, "materializeEditorTemplate", { enumerable: true, get: function () { return presets_js_1.materializeEditorTemplate; } });
@@ -0,0 +1,19 @@
1
+ import { type WatermarkRecipeDefinition, type WatermarkRecipeLayer, type WatermarkRecipeVariableContext } from '@image-marker/recipe';
2
+ import type { EditorAsset, EditorBrandKit, EditorTemplate } from './types.js';
3
+ export declare function createEditorBrandKit<Source = unknown>(brandKit: EditorBrandKit<Source>): EditorBrandKit<Source>;
4
+ export declare function createEditorTemplate<Source = unknown>(template: EditorTemplate<Source>): EditorTemplate<Source>;
5
+ /**
6
+ * Resolves `{{variable}}` placeholders and conditional layers with the same
7
+ * Recipe materializer used by Core, Node, and CLI.
8
+ */
9
+ export declare function materializeEditorTemplate<Source = unknown>(template: EditorTemplate<Source>, context: WatermarkRecipeVariableContext): WatermarkRecipeDefinition<Source>;
10
+ export declare function createBrandTextLayer(text: string, brandKit: EditorBrandKit, overrides?: Partial<Extract<WatermarkRecipeLayer, {
11
+ type: 'text';
12
+ }>>): Extract<WatermarkRecipeLayer, {
13
+ type: 'text';
14
+ }>;
15
+ export declare function createBrandLogoLayer<Source>(asset: EditorAsset<Source>, overrides?: Partial<Extract<WatermarkRecipeLayer<Source>, {
16
+ type: 'image';
17
+ }>>): Extract<WatermarkRecipeLayer<Source>, {
18
+ type: 'image';
19
+ }>;
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createEditorBrandKit = createEditorBrandKit;
4
+ exports.createEditorTemplate = createEditorTemplate;
5
+ exports.materializeEditorTemplate = materializeEditorTemplate;
6
+ exports.createBrandTextLayer = createBrandTextLayer;
7
+ exports.createBrandLogoLayer = createBrandLogoLayer;
8
+ const recipe_1 = require("@image-marker/recipe");
9
+ function assertIdentifier(value, label) {
10
+ const normalized = value.trim();
11
+ if (!normalized)
12
+ throw new Error(`${label} must not be empty.`);
13
+ return normalized;
14
+ }
15
+ function createEditorBrandKit(brandKit) {
16
+ return {
17
+ colors: brandKit.colors
18
+ ? [...new Set(brandKit.colors.map((color) => color.trim()))].filter(Boolean)
19
+ : undefined,
20
+ fonts: brandKit.fonts
21
+ ? [...new Set(brandKit.fonts.map((font) => font.trim()))].filter(Boolean)
22
+ : undefined,
23
+ logos: brandKit.logos?.map((logo) => ({ ...logo })),
24
+ };
25
+ }
26
+ function createEditorTemplate(template) {
27
+ return {
28
+ ...template,
29
+ id: assertIdentifier(template.id, 'Template id'),
30
+ name: assertIdentifier(template.name, 'Template name'),
31
+ recipe: (0, recipe_1.migrateWatermarkRecipe)(template.recipe),
32
+ brandKit: template.brandKit
33
+ ? createEditorBrandKit(template.brandKit)
34
+ : undefined,
35
+ };
36
+ }
37
+ /**
38
+ * Resolves `{{variable}}` placeholders and conditional layers with the same
39
+ * Recipe materializer used by Core, Node, and CLI.
40
+ */
41
+ function materializeEditorTemplate(template, context) {
42
+ return (0, recipe_1.materializeWatermarkRecipe)(template.recipe, context);
43
+ }
44
+ function createBrandTextLayer(text, brandKit, overrides = {}) {
45
+ return {
46
+ type: 'text',
47
+ text,
48
+ ...overrides,
49
+ style: {
50
+ color: brandKit.colors?.[0] ?? '#FFFFFF',
51
+ fontName: brandKit.fonts?.[0],
52
+ fontSize: 36,
53
+ ...overrides.style,
54
+ },
55
+ };
56
+ }
57
+ function createBrandLogoLayer(asset, overrides = {}) {
58
+ return {
59
+ type: 'image',
60
+ src: asset.source,
61
+ name: asset.name,
62
+ ...overrides,
63
+ };
64
+ }
@@ -0,0 +1,12 @@
1
+ import type { EditorPoint, EditorRect, EditorSize, WatermarkRecipeDefinition } from './types.js';
2
+ export interface EditorViewportProjection {
3
+ source: EditorSize;
4
+ viewport: EditorSize;
5
+ content: EditorRect;
6
+ scale: number;
7
+ }
8
+ export declare function fitEditorSizeWithinMax(source: EditorSize, maxSize: number): EditorSize;
9
+ export declare function createEditorViewportProjection(source: EditorSize, viewport: EditorSize): EditorViewportProjection;
10
+ export declare function projectEditorPoint(point: EditorPoint, projection: EditorViewportProjection): EditorPoint;
11
+ export declare function unprojectEditorPoint(point: EditorPoint, projection: EditorViewportProjection): EditorPoint;
12
+ export declare function projectEditorRecipe(recipe: WatermarkRecipeDefinition, source: EditorSize, target: EditorSize): WatermarkRecipeDefinition;
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fitEditorSizeWithinMax = fitEditorSizeWithinMax;
4
+ exports.createEditorViewportProjection = createEditorViewportProjection;
5
+ exports.projectEditorPoint = projectEditorPoint;
6
+ exports.unprojectEditorPoint = unprojectEditorPoint;
7
+ exports.projectEditorRecipe = projectEditorRecipe;
8
+ function assertSize(size, label) {
9
+ if (!Number.isFinite(size.width) ||
10
+ !Number.isFinite(size.height) ||
11
+ size.width <= 0 ||
12
+ size.height <= 0) {
13
+ throw new Error(`${label} dimensions must be finite numbers greater than 0.`);
14
+ }
15
+ }
16
+ function scaleMeasure(value, factor) {
17
+ if (typeof value === 'number')
18
+ return value * factor;
19
+ if (typeof value !== 'string')
20
+ return value;
21
+ const normalized = value.trim();
22
+ if (!normalized || normalized.endsWith('%'))
23
+ return value;
24
+ const parsed = Number(normalized);
25
+ return Number.isFinite(parsed) ? String(parsed * factor) : value;
26
+ }
27
+ function scalePadding(value, factor) {
28
+ if (typeof value === 'number')
29
+ return value * factor;
30
+ if (typeof value !== 'string')
31
+ return value;
32
+ return value
33
+ .trim()
34
+ .split(/\s+/)
35
+ .map((part) => scaleMeasure(part, factor))
36
+ .join(' ');
37
+ }
38
+ function projectPosition(layer, scaleX, scaleY, scale) {
39
+ const position = layer.position;
40
+ const defaultInset = layer.type === 'text' ? 20 : 0;
41
+ if (position?.position) {
42
+ return {
43
+ ...position,
44
+ X: scaleMeasure(position.X, scaleX),
45
+ Y: scaleMeasure(position.Y, scaleY),
46
+ edgeInset: position.edgeInset === undefined
47
+ ? 20 * scale
48
+ : scaleMeasure(position.edgeInset, scale),
49
+ };
50
+ }
51
+ return {
52
+ ...position,
53
+ X: position?.X === undefined
54
+ ? defaultInset * scaleX
55
+ : scaleMeasure(position.X, scaleX),
56
+ Y: position?.Y === undefined
57
+ ? defaultInset * scaleY
58
+ : scaleMeasure(position.Y, scaleY),
59
+ edgeInset: position?.edgeInset === undefined
60
+ ? undefined
61
+ : scaleMeasure(position.edgeInset, scale),
62
+ };
63
+ }
64
+ function projectLayout(layer, scaleX, scaleY) {
65
+ if (layer.layout?.type !== 'tile')
66
+ return layer.layout && { ...layer.layout };
67
+ return {
68
+ ...layer.layout,
69
+ gapX: scaleMeasure(layer.layout.gapX, scaleX),
70
+ gapY: scaleMeasure(layer.layout.gapY, scaleY),
71
+ offsetX: scaleMeasure(layer.layout.offsetX, scaleX),
72
+ offsetY: scaleMeasure(layer.layout.offsetY, scaleY),
73
+ };
74
+ }
75
+ function projectTextLayer(layer, scaleX, scaleY, scale) {
76
+ const style = layer.style;
77
+ const background = style?.textBackgroundStyle;
78
+ const cornerRadius = background?.cornerRadius;
79
+ return {
80
+ ...layer,
81
+ position: projectPosition(layer, scaleX, scaleY, scale),
82
+ layout: projectLayout(layer, scaleX, scaleY),
83
+ style: {
84
+ ...style,
85
+ fontSize: style?.fontSizeRatio === undefined
86
+ ? (style?.fontSize ?? 14) * scale
87
+ : style.fontSize,
88
+ shadowStyle: style?.shadowStyle
89
+ ? {
90
+ ...style.shadowStyle,
91
+ dx: style.shadowStyle.dx * scaleX,
92
+ dy: style.shadowStyle.dy * scaleY,
93
+ radius: style.shadowStyle.radius * scale,
94
+ }
95
+ : style?.shadowStyle,
96
+ strokeStyle: style?.strokeStyle
97
+ ? {
98
+ ...style.strokeStyle,
99
+ width: style.strokeStyle.width * scale,
100
+ }
101
+ : style?.strokeStyle,
102
+ textBackgroundStyle: background
103
+ ? {
104
+ ...background,
105
+ padding: scalePadding(background.padding, scale),
106
+ paddingLeft: scaleMeasure(background.paddingLeft, scaleX),
107
+ paddingRight: scaleMeasure(background.paddingRight, scaleX),
108
+ paddingTop: scaleMeasure(background.paddingTop, scaleY),
109
+ paddingBottom: scaleMeasure(background.paddingBottom, scaleY),
110
+ paddingHorizontal: scaleMeasure(background.paddingHorizontal, scaleX),
111
+ paddingVertical: scaleMeasure(background.paddingVertical, scaleY),
112
+ paddingX: scaleMeasure(background.paddingX, scaleX),
113
+ paddingY: scaleMeasure(background.paddingY, scaleY),
114
+ cornerRadius: cornerRadius
115
+ ? Object.fromEntries(Object.entries(cornerRadius).map(([key, value]) => [
116
+ key,
117
+ value
118
+ ? {
119
+ x: scaleMeasure(value.x, scaleX),
120
+ y: scaleMeasure(value.y, scaleY),
121
+ }
122
+ : value,
123
+ ]))
124
+ : cornerRadius,
125
+ }
126
+ : background,
127
+ },
128
+ };
129
+ }
130
+ function projectImageLayer(layer, scaleX, scaleY, scale) {
131
+ return {
132
+ ...layer,
133
+ position: projectPosition(layer, scaleX, scaleY, scale),
134
+ layout: projectLayout(layer, scaleX, scaleY),
135
+ scale: (layer.scale ?? 1) * scale,
136
+ };
137
+ }
138
+ function fitEditorSizeWithinMax(source, maxSize) {
139
+ assertSize(source, 'sourceSize');
140
+ if (!Number.isFinite(maxSize) || maxSize <= 0) {
141
+ throw new Error('maxSize must be a finite number greater than 0.');
142
+ }
143
+ const largest = Math.max(source.width, source.height);
144
+ if (largest <= maxSize)
145
+ return { ...source };
146
+ const scale = maxSize / largest;
147
+ return {
148
+ width: Math.max(1, Math.round(source.width * scale)),
149
+ height: Math.max(1, Math.round(source.height * scale)),
150
+ };
151
+ }
152
+ function createEditorViewportProjection(source, viewport) {
153
+ assertSize(source, 'sourceSize');
154
+ assertSize(viewport, 'viewport');
155
+ const scale = Math.min(viewport.width / source.width, viewport.height / source.height);
156
+ const width = source.width * scale;
157
+ const height = source.height * scale;
158
+ return {
159
+ source: { ...source },
160
+ viewport: { ...viewport },
161
+ scale,
162
+ content: {
163
+ x: (viewport.width - width) / 2,
164
+ y: (viewport.height - height) / 2,
165
+ width,
166
+ height,
167
+ },
168
+ };
169
+ }
170
+ function projectEditorPoint(point, projection) {
171
+ return {
172
+ x: projection.content.x + point.x * projection.scale,
173
+ y: projection.content.y + point.y * projection.scale,
174
+ };
175
+ }
176
+ function unprojectEditorPoint(point, projection) {
177
+ return {
178
+ x: (point.x - projection.content.x) / projection.scale,
179
+ y: (point.y - projection.content.y) / projection.scale,
180
+ };
181
+ }
182
+ function projectEditorRecipe(recipe, source, target) {
183
+ assertSize(source, 'sourceSize');
184
+ assertSize(target, 'targetSize');
185
+ const scaleX = target.width / source.width;
186
+ const scaleY = target.height / source.height;
187
+ const scale = Math.min(scaleX, scaleY);
188
+ return {
189
+ schemaVersion: 2,
190
+ layers: recipe.layers.map((layer) => layer.type === 'text'
191
+ ? projectTextLayer(layer, scaleX, scaleY, scale)
192
+ : projectImageLayer(layer, scaleX, scaleY, scale)),
193
+ output: { ...recipe.output },
194
+ };
195
+ }
@@ -0,0 +1,166 @@
1
+ import type { ContentCredentialsAdapter, ContentCredentialsClaim, MarkerImageInfo, MarkerJobOptions, MarkerResult } from './core-contract.js';
2
+ import type { WatermarkBlendMode, WatermarkRecipeDefinition, WatermarkRecipeDefinitionLayer, WatermarkRecipeDocument, WatermarkRecipeInput, WatermarkTextStyle } from '@image-marker/recipe';
3
+ export interface EditorPoint {
4
+ x: number;
5
+ y: number;
6
+ }
7
+ export interface EditorSize {
8
+ width: number;
9
+ height: number;
10
+ }
11
+ export interface EditorRect extends EditorPoint, EditorSize {
12
+ }
13
+ export interface EditorViewportState {
14
+ zoom: number;
15
+ pan: EditorPoint;
16
+ fitMode: 'contain' | 'manual';
17
+ }
18
+ export interface EditorSafeArea {
19
+ top: number;
20
+ right: number;
21
+ bottom: number;
22
+ left: number;
23
+ }
24
+ export type EditorSnapGuide = {
25
+ axis: 'x';
26
+ position: number;
27
+ kind: 'edge' | 'center' | 'safe-area';
28
+ } | {
29
+ axis: 'y';
30
+ position: number;
31
+ kind: 'edge' | 'center' | 'safe-area';
32
+ };
33
+ export interface EditorInvisibleExport {
34
+ payload: string;
35
+ key: string;
36
+ strength?: 'subtle' | 'balanced' | 'robust';
37
+ }
38
+ export interface EditorContentCredentialsExport {
39
+ adapter: ContentCredentialsAdapter;
40
+ claim: ContentCredentialsClaim;
41
+ }
42
+ export interface EditorExportOptions {
43
+ invisible?: EditorInvisibleExport;
44
+ contentCredentials?: EditorContentCredentialsExport;
45
+ }
46
+ export interface EditorState {
47
+ recipe: WatermarkRecipeDefinition;
48
+ /** Ordered selection. The final item is the primary selection. */
49
+ selectedLayerIds: readonly string[];
50
+ /** Convenience alias for the primary selection. */
51
+ selectedLayerId?: string;
52
+ safeArea: EditorSafeArea;
53
+ snapGuides: readonly EditorSnapGuide[];
54
+ exportOptions: EditorExportOptions;
55
+ viewport: EditorViewportState;
56
+ canUndo: boolean;
57
+ canRedo: boolean;
58
+ }
59
+ export type EditorSelectionMode = 'replace' | 'add' | 'toggle';
60
+ export type EditorAlignment = 'left' | 'center' | 'right' | 'top' | 'middle' | 'bottom';
61
+ export type EditorDistribution = 'horizontal' | 'vertical';
62
+ export interface EditorLayerBounds extends EditorRect {
63
+ id: string;
64
+ }
65
+ export interface EditorClipboardDocument {
66
+ kind: 'image-marker/editor-layers';
67
+ version: 1;
68
+ layers: WatermarkRecipeDefinitionLayer[];
69
+ }
70
+ export type EditorTextLayerPatch = {
71
+ text?: string;
72
+ name?: string;
73
+ alpha?: number;
74
+ blendMode?: WatermarkBlendMode;
75
+ style?: Partial<WatermarkTextStyle>;
76
+ };
77
+ export interface EditorAsset<Source = unknown> {
78
+ id: string;
79
+ name: string;
80
+ source: Source;
81
+ thumbnail?: Source;
82
+ tags?: readonly string[];
83
+ }
84
+ export interface EditorBrandKit<Source = unknown> {
85
+ colors?: readonly string[];
86
+ fonts?: readonly string[];
87
+ logos?: readonly EditorAsset<Source>[];
88
+ }
89
+ export interface EditorTemplate<Source = unknown> {
90
+ id: string;
91
+ name: string;
92
+ description?: string;
93
+ recipe: WatermarkRecipeDocument<Source>;
94
+ brandKit?: EditorBrandKit<Source>;
95
+ }
96
+ export interface EditorPersistenceAdapter {
97
+ load(key: string): string | null | Promise<string | null>;
98
+ save(key: string, value: string): void | Promise<void>;
99
+ remove?(key: string): void | Promise<void>;
100
+ }
101
+ export interface EditorAutosaveOptions {
102
+ key: string;
103
+ storage: EditorPersistenceAdapter;
104
+ debounceMs?: number;
105
+ onError?: (error: Error) => void;
106
+ }
107
+ export interface ImageMarkerEditorControllerOptions {
108
+ document: WatermarkRecipeDocument;
109
+ historyLimit?: number;
110
+ autosave?: EditorAutosaveOptions;
111
+ onChange?: (state: EditorState) => void;
112
+ }
113
+ export interface EditorSerializedState {
114
+ version: 1;
115
+ recipe: WatermarkRecipeDefinition;
116
+ selectedLayerIds: string[];
117
+ safeArea: EditorSafeArea;
118
+ /**
119
+ * Reserved for non-sensitive application metadata. Core signing adapters,
120
+ * invisible-watermark keys, and export credentials are never autosaved.
121
+ */
122
+ exportOptions?: EditorExportOptions;
123
+ viewport: EditorViewportState;
124
+ }
125
+ export interface EditorSnapContext {
126
+ canvas: EditorSize;
127
+ layer: EditorSize;
128
+ safeArea?: EditorSafeArea;
129
+ threshold?: number;
130
+ }
131
+ export interface EditorKeyCommand {
132
+ key: string;
133
+ shiftKey?: boolean;
134
+ ctrlKey?: boolean;
135
+ metaKey?: boolean;
136
+ altKey?: boolean;
137
+ }
138
+ export interface EditorRenderRequest {
139
+ recipe: WatermarkRecipeDefinition;
140
+ input: WatermarkRecipeInput;
141
+ /**
142
+ * Pixel dimensions of the background before Core applies output.maxSize.
143
+ * Providing this keeps numeric Recipe coordinates visually stable between
144
+ * the interactive surface, bounded previews, and original export.
145
+ */
146
+ sourceSize?: EditorSize;
147
+ control?: MarkerJobOptions;
148
+ }
149
+ export interface EditorExportResult {
150
+ visible: MarkerResult;
151
+ final: MarkerResult;
152
+ }
153
+ /**
154
+ * Rendering remains injectable. The editor package owns interaction state,
155
+ * while Core or an application adapter owns image decoding and encoding.
156
+ */
157
+ export interface ImageMarkerEditorRenderAdapter {
158
+ getSourceInfo?(source: unknown): Promise<MarkerImageInfo>;
159
+ renderPreview(request: EditorRenderRequest & {
160
+ maxSize?: number;
161
+ }): Promise<MarkerResult>;
162
+ exportOriginal(request: EditorRenderRequest & {
163
+ options?: EditorExportOptions;
164
+ }): Promise<EditorExportResult>;
165
+ }
166
+ export type { MarkerImageInfo, WatermarkBlendMode, WatermarkRecipeDefinition, WatermarkRecipeDefinitionLayer, WatermarkRecipeDocument, WatermarkRecipeInput, WatermarkTextStyle, };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,7 @@
1
+ import type { EditorMarkerEngine } from './editor/adapter.js';
2
+ import type { ImageMarkerEditorRenderAdapter, MarkerImageInfo } from './editor/types.js';
3
+ export type { EditorMarkerEngine } from './editor/adapter.js';
4
+ /** Bind the shared editor renderer to Canvas or an isolated Web Marker instance. */
5
+ export declare function createWebEditorAdapter(previewMaxSize?: number, marker?: EditorMarkerEngine): ImageMarkerEditorRenderAdapter;
6
+ /** Read local file bytes first; the default browser fallback remains available. */
7
+ export declare function resolveWebEditorSourceInfo(source: unknown): Promise<MarkerImageInfo>;
@@ -0,0 +1,17 @@
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.createWebEditorAdapter = createWebEditorAdapter;
7
+ exports.resolveWebEditorSourceInfo = resolveWebEditorSourceInfo;
8
+ const index_js_1 = __importDefault(require("./core/web/index.js"));
9
+ const adapter_js_1 = require("./editor/adapter.js");
10
+ /** Bind the shared editor renderer to Canvas or an isolated Web Marker instance. */
11
+ function createWebEditorAdapter(previewMaxSize = 1024, marker = index_js_1.default) {
12
+ return (0, adapter_js_1.createEditorRenderAdapter)(marker, previewMaxSize);
13
+ }
14
+ /** Read local file bytes first; the default browser fallback remains available. */
15
+ function resolveWebEditorSourceInfo(source) {
16
+ return index_js_1.default.getImageInfo(source);
17
+ }
@@ -0,0 +1,2 @@
1
+ export * from './editor/headless.js';
2
+ export { addWatermarkRecipeLayer, duplicateWatermarkRecipeLayer, groupWatermarkRecipeLayers, removeWatermarkRecipeLayer, reorderWatermarkRecipeLayer, ungroupWatermarkRecipeLayers, updateWatermarkRecipeLayer, } from '@image-marker/recipe';