@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.
- package/CHANGELOG.md +11 -0
- package/README.md +100 -0
- package/api-contract.json +224 -0
- package/lib/commonjs/core/batch.d.ts +32 -0
- package/lib/commonjs/core/batch.js +85 -0
- package/lib/commonjs/core/content-credentials.d.ts +53 -0
- package/lib/commonjs/core/content-credentials.js +96 -0
- package/lib/commonjs/core/image-info.d.ts +30 -0
- package/lib/commonjs/core/image-info.js +348 -0
- package/lib/commonjs/core/index.d.ts +1014 -0
- package/lib/commonjs/core/index.js +92 -0
- package/lib/commonjs/core/invisible-watermark.d.ts +102 -0
- package/lib/commonjs/core/invisible-watermark.js +913 -0
- package/lib/commonjs/core/job.d.ts +57 -0
- package/lib/commonjs/core/job.js +151 -0
- package/lib/commonjs/core/layout.d.ts +19 -0
- package/lib/commonjs/core/layout.js +75 -0
- package/lib/commonjs/core/marker.d.ts +1 -0
- package/lib/commonjs/core/marker.js +11 -0
- package/lib/commonjs/core/recipe.d.ts +44 -0
- package/lib/commonjs/core/recipe.js +175 -0
- package/lib/commonjs/core/result.d.ts +34 -0
- package/lib/commonjs/core/result.js +41 -0
- package/lib/commonjs/core/trace-runtime.d.ts +43 -0
- package/lib/commonjs/core/trace-runtime.js +92 -0
- package/lib/commonjs/core/validate.d.ts +4 -0
- package/lib/commonjs/core/validate.js +192 -0
- package/lib/commonjs/core/web/browser.d.ts +105 -0
- package/lib/commonjs/core/web/browser.js +434 -0
- package/lib/commonjs/core/web/helpers.d.ts +41 -0
- package/lib/commonjs/core/web/helpers.js +264 -0
- package/lib/commonjs/core/web/index.d.ts +59 -0
- package/lib/commonjs/core/web/index.js +724 -0
- package/lib/commonjs/core/web/invisible-worker-client.d.ts +13 -0
- package/lib/commonjs/core/web/invisible-worker-client.js +140 -0
- package/lib/commonjs/core/web/renderer.d.ts +20 -0
- package/lib/commonjs/core/web/renderer.js +665 -0
- package/lib/commonjs/core/web/runtime.d.ts +106 -0
- package/lib/commonjs/core/web/runtime.js +2 -0
- package/lib/commonjs/editor/adapter.d.ts +26 -0
- package/lib/commonjs/editor/adapter.js +102 -0
- package/lib/commonjs/editor/controller.d.ts +106 -0
- package/lib/commonjs/editor/controller.js +919 -0
- package/lib/commonjs/editor/core-contract.d.ts +2 -0
- package/lib/commonjs/editor/core-contract.js +23 -0
- package/lib/commonjs/editor/geometry.d.ts +8 -0
- package/lib/commonjs/editor/geometry.js +109 -0
- package/lib/commonjs/editor/headless.d.ts +5 -0
- package/lib/commonjs/editor/headless.js +22 -0
- package/lib/commonjs/editor/presets.d.ts +19 -0
- package/lib/commonjs/editor/presets.js +64 -0
- package/lib/commonjs/editor/projection.d.ts +12 -0
- package/lib/commonjs/editor/projection.js +195 -0
- package/lib/commonjs/editor/types.d.ts +166 -0
- package/lib/commonjs/editor/types.js +2 -0
- package/lib/commonjs/editor-adapter.d.ts +7 -0
- package/lib/commonjs/editor-adapter.js +17 -0
- package/lib/commonjs/headless.d.ts +2 -0
- package/lib/commonjs/headless.js +26 -0
- package/lib/commonjs/index.d.ts +5 -0
- package/lib/commonjs/index.js +27 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/module/core/batch.d.ts +32 -0
- package/lib/module/core/batch.js +82 -0
- package/lib/module/core/content-credentials.d.ts +53 -0
- package/lib/module/core/content-credentials.js +92 -0
- package/lib/module/core/image-info.d.ts +30 -0
- package/lib/module/core/image-info.js +343 -0
- package/lib/module/core/index.d.ts +1014 -0
- package/lib/module/core/index.js +56 -0
- package/lib/module/core/invisible-watermark.d.ts +102 -0
- package/lib/module/core/invisible-watermark.js +897 -0
- package/lib/module/core/job.d.ts +57 -0
- package/lib/module/core/job.js +143 -0
- package/lib/module/core/layout.d.ts +19 -0
- package/lib/module/core/layout.js +70 -0
- package/lib/module/core/marker.d.ts +1 -0
- package/lib/module/core/marker.js +4 -0
- package/lib/module/core/recipe.d.ts +44 -0
- package/lib/module/core/recipe.js +169 -0
- package/lib/module/core/result.d.ts +34 -0
- package/lib/module/core/result.js +38 -0
- package/lib/module/core/trace-runtime.d.ts +43 -0
- package/lib/module/core/trace-runtime.js +89 -0
- package/lib/module/core/validate.d.ts +4 -0
- package/lib/module/core/validate.js +187 -0
- package/lib/module/core/web/browser.d.ts +105 -0
- package/lib/module/core/web/browser.js +427 -0
- package/lib/module/core/web/helpers.d.ts +41 -0
- package/lib/module/core/web/helpers.js +251 -0
- package/lib/module/core/web/index.d.ts +59 -0
- package/lib/module/core/web/index.js +709 -0
- package/lib/module/core/web/invisible-worker-client.d.ts +13 -0
- package/lib/module/core/web/invisible-worker-client.js +137 -0
- package/lib/module/core/web/renderer.d.ts +20 -0
- package/lib/module/core/web/renderer.js +661 -0
- package/lib/module/core/web/runtime.d.ts +106 -0
- package/lib/module/core/web/runtime.js +1 -0
- package/lib/module/editor/adapter.d.ts +26 -0
- package/lib/module/editor/adapter.js +99 -0
- package/lib/module/editor/controller.d.ts +106 -0
- package/lib/module/editor/controller.js +915 -0
- package/lib/module/editor/core-contract.d.ts +2 -0
- package/lib/module/editor/core-contract.js +2 -0
- package/lib/module/editor/geometry.d.ts +8 -0
- package/lib/module/editor/geometry.js +103 -0
- package/lib/module/editor/headless.d.ts +5 -0
- package/lib/module/editor/headless.js +4 -0
- package/lib/module/editor/presets.d.ts +19 -0
- package/lib/module/editor/presets.js +57 -0
- package/lib/module/editor/projection.d.ts +12 -0
- package/lib/module/editor/projection.js +188 -0
- package/lib/module/editor/types.d.ts +166 -0
- package/lib/module/editor/types.js +1 -0
- package/lib/module/editor-adapter.d.ts +7 -0
- package/lib/module/editor-adapter.js +10 -0
- package/lib/module/headless.d.ts +2 -0
- package/lib/module/headless.js +2 -0
- package/lib/module/index.d.ts +5 -0
- package/lib/module/index.js +3 -0
- package/lib/module/package.json +1 -0
- package/lib/source-manifest.json +34 -0
- package/lib/worker/invisible-watermark.js +1 -0
- package/package.json +85 -0
|
@@ -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,103 @@
|
|
|
1
|
+
const DEFAULT_SNAP_THRESHOLD = 6;
|
|
2
|
+
function closest(value, candidates, threshold) {
|
|
3
|
+
return candidates
|
|
4
|
+
.map((candidate) => ({
|
|
5
|
+
...candidate,
|
|
6
|
+
distance: Math.abs(candidate.value - value),
|
|
7
|
+
}))
|
|
8
|
+
.filter((candidate) => candidate.distance <= threshold)
|
|
9
|
+
.sort((left, right) => left.distance - right.distance)[0];
|
|
10
|
+
}
|
|
11
|
+
export function normalizeSafeArea(safeArea = {}) {
|
|
12
|
+
return {
|
|
13
|
+
top: Math.max(0, safeArea.top ?? 0),
|
|
14
|
+
right: Math.max(0, safeArea.right ?? 0),
|
|
15
|
+
bottom: Math.max(0, safeArea.bottom ?? 0),
|
|
16
|
+
left: Math.max(0, safeArea.left ?? 0),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function snapLayerPosition(point, context) {
|
|
20
|
+
const safe = normalizeSafeArea(context.safeArea);
|
|
21
|
+
const threshold = context.threshold ?? DEFAULT_SNAP_THRESHOLD;
|
|
22
|
+
const canvasCenterX = context.canvas.width / 2;
|
|
23
|
+
const canvasCenterY = context.canvas.height / 2;
|
|
24
|
+
const layerCenterX = context.layer.width / 2;
|
|
25
|
+
const layerCenterY = context.layer.height / 2;
|
|
26
|
+
const right = context.canvas.width - safe.right - context.layer.width;
|
|
27
|
+
const bottom = context.canvas.height - safe.bottom - context.layer.height;
|
|
28
|
+
const xCandidates = [
|
|
29
|
+
{
|
|
30
|
+
value: 0,
|
|
31
|
+
guide: { axis: 'x', position: 0, kind: 'edge' },
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
value: safe.left,
|
|
35
|
+
guide: { axis: 'x', position: safe.left, kind: 'safe-area' },
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
value: canvasCenterX - layerCenterX,
|
|
39
|
+
guide: { axis: 'x', position: canvasCenterX, kind: 'center' },
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
value: right,
|
|
43
|
+
guide: {
|
|
44
|
+
axis: 'x',
|
|
45
|
+
position: context.canvas.width - safe.right,
|
|
46
|
+
kind: 'safe-area',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
value: context.canvas.width - context.layer.width,
|
|
51
|
+
guide: {
|
|
52
|
+
axis: 'x',
|
|
53
|
+
position: context.canvas.width,
|
|
54
|
+
kind: 'edge',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
const yCandidates = [
|
|
59
|
+
{
|
|
60
|
+
value: 0,
|
|
61
|
+
guide: { axis: 'y', position: 0, kind: 'edge' },
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
value: safe.top,
|
|
65
|
+
guide: { axis: 'y', position: safe.top, kind: 'safe-area' },
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
value: canvasCenterY - layerCenterY,
|
|
69
|
+
guide: { axis: 'y', position: canvasCenterY, kind: 'center' },
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
value: bottom,
|
|
73
|
+
guide: {
|
|
74
|
+
axis: 'y',
|
|
75
|
+
position: context.canvas.height - safe.bottom,
|
|
76
|
+
kind: 'safe-area',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
value: context.canvas.height - context.layer.height,
|
|
81
|
+
guide: {
|
|
82
|
+
axis: 'y',
|
|
83
|
+
position: context.canvas.height,
|
|
84
|
+
kind: 'edge',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
const snappedX = closest(point.x, xCandidates, threshold);
|
|
89
|
+
const snappedY = closest(point.y, yCandidates, threshold);
|
|
90
|
+
return {
|
|
91
|
+
point: {
|
|
92
|
+
x: snappedX?.value ?? point.x,
|
|
93
|
+
y: snappedY?.value ?? point.y,
|
|
94
|
+
},
|
|
95
|
+
guides: [snappedX?.guide, snappedY?.guide].filter((guide) => Boolean(guide)),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export function distance(first, second) {
|
|
99
|
+
return Math.hypot(second.x - first.x, second.y - first.y);
|
|
100
|
+
}
|
|
101
|
+
export function angle(first, second) {
|
|
102
|
+
return (Math.atan2(second.y - first.y, second.x - first.x) * 180) / Math.PI;
|
|
103
|
+
}
|
|
@@ -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,4 @@
|
|
|
1
|
+
export { ImageMarkerEditorController } from './controller.js';
|
|
2
|
+
export { angle, distance, normalizeSafeArea, snapLayerPosition, } from './geometry.js';
|
|
3
|
+
export { createEditorViewportProjection, fitEditorSizeWithinMax, projectEditorPoint, projectEditorRecipe, unprojectEditorPoint, } from './projection.js';
|
|
4
|
+
export { createBrandLogoLayer, createBrandTextLayer, createEditorBrandKit, createEditorTemplate, materializeEditorTemplate, } from './presets.js';
|
|
@@ -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,57 @@
|
|
|
1
|
+
import { materializeWatermarkRecipe, migrateWatermarkRecipe, } from '@image-marker/recipe';
|
|
2
|
+
function assertIdentifier(value, label) {
|
|
3
|
+
const normalized = value.trim();
|
|
4
|
+
if (!normalized)
|
|
5
|
+
throw new Error(`${label} must not be empty.`);
|
|
6
|
+
return normalized;
|
|
7
|
+
}
|
|
8
|
+
export function createEditorBrandKit(brandKit) {
|
|
9
|
+
return {
|
|
10
|
+
colors: brandKit.colors
|
|
11
|
+
? [...new Set(brandKit.colors.map((color) => color.trim()))].filter(Boolean)
|
|
12
|
+
: undefined,
|
|
13
|
+
fonts: brandKit.fonts
|
|
14
|
+
? [...new Set(brandKit.fonts.map((font) => font.trim()))].filter(Boolean)
|
|
15
|
+
: undefined,
|
|
16
|
+
logos: brandKit.logos?.map((logo) => ({ ...logo })),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function createEditorTemplate(template) {
|
|
20
|
+
return {
|
|
21
|
+
...template,
|
|
22
|
+
id: assertIdentifier(template.id, 'Template id'),
|
|
23
|
+
name: assertIdentifier(template.name, 'Template name'),
|
|
24
|
+
recipe: migrateWatermarkRecipe(template.recipe),
|
|
25
|
+
brandKit: template.brandKit
|
|
26
|
+
? createEditorBrandKit(template.brandKit)
|
|
27
|
+
: undefined,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolves `{{variable}}` placeholders and conditional layers with the same
|
|
32
|
+
* Recipe materializer used by Core, Node, and CLI.
|
|
33
|
+
*/
|
|
34
|
+
export function materializeEditorTemplate(template, context) {
|
|
35
|
+
return materializeWatermarkRecipe(template.recipe, context);
|
|
36
|
+
}
|
|
37
|
+
export function createBrandTextLayer(text, brandKit, overrides = {}) {
|
|
38
|
+
return {
|
|
39
|
+
type: 'text',
|
|
40
|
+
text,
|
|
41
|
+
...overrides,
|
|
42
|
+
style: {
|
|
43
|
+
color: brandKit.colors?.[0] ?? '#FFFFFF',
|
|
44
|
+
fontName: brandKit.fonts?.[0],
|
|
45
|
+
fontSize: 36,
|
|
46
|
+
...overrides.style,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function createBrandLogoLayer(asset, overrides = {}) {
|
|
51
|
+
return {
|
|
52
|
+
type: 'image',
|
|
53
|
+
src: asset.source,
|
|
54
|
+
name: asset.name,
|
|
55
|
+
...overrides,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -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,188 @@
|
|
|
1
|
+
function assertSize(size, label) {
|
|
2
|
+
if (!Number.isFinite(size.width) ||
|
|
3
|
+
!Number.isFinite(size.height) ||
|
|
4
|
+
size.width <= 0 ||
|
|
5
|
+
size.height <= 0) {
|
|
6
|
+
throw new Error(`${label} dimensions must be finite numbers greater than 0.`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function scaleMeasure(value, factor) {
|
|
10
|
+
if (typeof value === 'number')
|
|
11
|
+
return value * factor;
|
|
12
|
+
if (typeof value !== 'string')
|
|
13
|
+
return value;
|
|
14
|
+
const normalized = value.trim();
|
|
15
|
+
if (!normalized || normalized.endsWith('%'))
|
|
16
|
+
return value;
|
|
17
|
+
const parsed = Number(normalized);
|
|
18
|
+
return Number.isFinite(parsed) ? String(parsed * factor) : value;
|
|
19
|
+
}
|
|
20
|
+
function scalePadding(value, factor) {
|
|
21
|
+
if (typeof value === 'number')
|
|
22
|
+
return value * factor;
|
|
23
|
+
if (typeof value !== 'string')
|
|
24
|
+
return value;
|
|
25
|
+
return value
|
|
26
|
+
.trim()
|
|
27
|
+
.split(/\s+/)
|
|
28
|
+
.map((part) => scaleMeasure(part, factor))
|
|
29
|
+
.join(' ');
|
|
30
|
+
}
|
|
31
|
+
function projectPosition(layer, scaleX, scaleY, scale) {
|
|
32
|
+
const position = layer.position;
|
|
33
|
+
const defaultInset = layer.type === 'text' ? 20 : 0;
|
|
34
|
+
if (position?.position) {
|
|
35
|
+
return {
|
|
36
|
+
...position,
|
|
37
|
+
X: scaleMeasure(position.X, scaleX),
|
|
38
|
+
Y: scaleMeasure(position.Y, scaleY),
|
|
39
|
+
edgeInset: position.edgeInset === undefined
|
|
40
|
+
? 20 * scale
|
|
41
|
+
: scaleMeasure(position.edgeInset, scale),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
...position,
|
|
46
|
+
X: position?.X === undefined
|
|
47
|
+
? defaultInset * scaleX
|
|
48
|
+
: scaleMeasure(position.X, scaleX),
|
|
49
|
+
Y: position?.Y === undefined
|
|
50
|
+
? defaultInset * scaleY
|
|
51
|
+
: scaleMeasure(position.Y, scaleY),
|
|
52
|
+
edgeInset: position?.edgeInset === undefined
|
|
53
|
+
? undefined
|
|
54
|
+
: scaleMeasure(position.edgeInset, scale),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function projectLayout(layer, scaleX, scaleY) {
|
|
58
|
+
if (layer.layout?.type !== 'tile')
|
|
59
|
+
return layer.layout && { ...layer.layout };
|
|
60
|
+
return {
|
|
61
|
+
...layer.layout,
|
|
62
|
+
gapX: scaleMeasure(layer.layout.gapX, scaleX),
|
|
63
|
+
gapY: scaleMeasure(layer.layout.gapY, scaleY),
|
|
64
|
+
offsetX: scaleMeasure(layer.layout.offsetX, scaleX),
|
|
65
|
+
offsetY: scaleMeasure(layer.layout.offsetY, scaleY),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function projectTextLayer(layer, scaleX, scaleY, scale) {
|
|
69
|
+
const style = layer.style;
|
|
70
|
+
const background = style?.textBackgroundStyle;
|
|
71
|
+
const cornerRadius = background?.cornerRadius;
|
|
72
|
+
return {
|
|
73
|
+
...layer,
|
|
74
|
+
position: projectPosition(layer, scaleX, scaleY, scale),
|
|
75
|
+
layout: projectLayout(layer, scaleX, scaleY),
|
|
76
|
+
style: {
|
|
77
|
+
...style,
|
|
78
|
+
fontSize: style?.fontSizeRatio === undefined
|
|
79
|
+
? (style?.fontSize ?? 14) * scale
|
|
80
|
+
: style.fontSize,
|
|
81
|
+
shadowStyle: style?.shadowStyle
|
|
82
|
+
? {
|
|
83
|
+
...style.shadowStyle,
|
|
84
|
+
dx: style.shadowStyle.dx * scaleX,
|
|
85
|
+
dy: style.shadowStyle.dy * scaleY,
|
|
86
|
+
radius: style.shadowStyle.radius * scale,
|
|
87
|
+
}
|
|
88
|
+
: style?.shadowStyle,
|
|
89
|
+
strokeStyle: style?.strokeStyle
|
|
90
|
+
? {
|
|
91
|
+
...style.strokeStyle,
|
|
92
|
+
width: style.strokeStyle.width * scale,
|
|
93
|
+
}
|
|
94
|
+
: style?.strokeStyle,
|
|
95
|
+
textBackgroundStyle: background
|
|
96
|
+
? {
|
|
97
|
+
...background,
|
|
98
|
+
padding: scalePadding(background.padding, scale),
|
|
99
|
+
paddingLeft: scaleMeasure(background.paddingLeft, scaleX),
|
|
100
|
+
paddingRight: scaleMeasure(background.paddingRight, scaleX),
|
|
101
|
+
paddingTop: scaleMeasure(background.paddingTop, scaleY),
|
|
102
|
+
paddingBottom: scaleMeasure(background.paddingBottom, scaleY),
|
|
103
|
+
paddingHorizontal: scaleMeasure(background.paddingHorizontal, scaleX),
|
|
104
|
+
paddingVertical: scaleMeasure(background.paddingVertical, scaleY),
|
|
105
|
+
paddingX: scaleMeasure(background.paddingX, scaleX),
|
|
106
|
+
paddingY: scaleMeasure(background.paddingY, scaleY),
|
|
107
|
+
cornerRadius: cornerRadius
|
|
108
|
+
? Object.fromEntries(Object.entries(cornerRadius).map(([key, value]) => [
|
|
109
|
+
key,
|
|
110
|
+
value
|
|
111
|
+
? {
|
|
112
|
+
x: scaleMeasure(value.x, scaleX),
|
|
113
|
+
y: scaleMeasure(value.y, scaleY),
|
|
114
|
+
}
|
|
115
|
+
: value,
|
|
116
|
+
]))
|
|
117
|
+
: cornerRadius,
|
|
118
|
+
}
|
|
119
|
+
: background,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function projectImageLayer(layer, scaleX, scaleY, scale) {
|
|
124
|
+
return {
|
|
125
|
+
...layer,
|
|
126
|
+
position: projectPosition(layer, scaleX, scaleY, scale),
|
|
127
|
+
layout: projectLayout(layer, scaleX, scaleY),
|
|
128
|
+
scale: (layer.scale ?? 1) * scale,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export function fitEditorSizeWithinMax(source, maxSize) {
|
|
132
|
+
assertSize(source, 'sourceSize');
|
|
133
|
+
if (!Number.isFinite(maxSize) || maxSize <= 0) {
|
|
134
|
+
throw new Error('maxSize must be a finite number greater than 0.');
|
|
135
|
+
}
|
|
136
|
+
const largest = Math.max(source.width, source.height);
|
|
137
|
+
if (largest <= maxSize)
|
|
138
|
+
return { ...source };
|
|
139
|
+
const scale = maxSize / largest;
|
|
140
|
+
return {
|
|
141
|
+
width: Math.max(1, Math.round(source.width * scale)),
|
|
142
|
+
height: Math.max(1, Math.round(source.height * scale)),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
export function createEditorViewportProjection(source, viewport) {
|
|
146
|
+
assertSize(source, 'sourceSize');
|
|
147
|
+
assertSize(viewport, 'viewport');
|
|
148
|
+
const scale = Math.min(viewport.width / source.width, viewport.height / source.height);
|
|
149
|
+
const width = source.width * scale;
|
|
150
|
+
const height = source.height * scale;
|
|
151
|
+
return {
|
|
152
|
+
source: { ...source },
|
|
153
|
+
viewport: { ...viewport },
|
|
154
|
+
scale,
|
|
155
|
+
content: {
|
|
156
|
+
x: (viewport.width - width) / 2,
|
|
157
|
+
y: (viewport.height - height) / 2,
|
|
158
|
+
width,
|
|
159
|
+
height,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
export function projectEditorPoint(point, projection) {
|
|
164
|
+
return {
|
|
165
|
+
x: projection.content.x + point.x * projection.scale,
|
|
166
|
+
y: projection.content.y + point.y * projection.scale,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
export function unprojectEditorPoint(point, projection) {
|
|
170
|
+
return {
|
|
171
|
+
x: (point.x - projection.content.x) / projection.scale,
|
|
172
|
+
y: (point.y - projection.content.y) / projection.scale,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
export function projectEditorRecipe(recipe, source, target) {
|
|
176
|
+
assertSize(source, 'sourceSize');
|
|
177
|
+
assertSize(target, 'targetSize');
|
|
178
|
+
const scaleX = target.width / source.width;
|
|
179
|
+
const scaleY = target.height / source.height;
|
|
180
|
+
const scale = Math.min(scaleX, scaleY);
|
|
181
|
+
return {
|
|
182
|
+
schemaVersion: 2,
|
|
183
|
+
layers: recipe.layers.map((layer) => layer.type === 'text'
|
|
184
|
+
? projectTextLayer(layer, scaleX, scaleY, scale)
|
|
185
|
+
: projectImageLayer(layer, scaleX, scaleY, scale)),
|
|
186
|
+
output: { ...recipe.output },
|
|
187
|
+
};
|
|
188
|
+
}
|
|
@@ -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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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,10 @@
|
|
|
1
|
+
import Marker from './core/web/index.js';
|
|
2
|
+
import { createEditorRenderAdapter } from './editor/adapter.js';
|
|
3
|
+
/** Bind the shared editor renderer to Canvas or an isolated Web Marker instance. */
|
|
4
|
+
export function createWebEditorAdapter(previewMaxSize = 1024, marker = Marker) {
|
|
5
|
+
return createEditorRenderAdapter(marker, previewMaxSize);
|
|
6
|
+
}
|
|
7
|
+
/** Read local file bytes first; the default browser fallback remains available. */
|
|
8
|
+
export function resolveWebEditorSourceInfo(source) {
|
|
9
|
+
return Marker.getImageInfo(source);
|
|
10
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export * from './editor/headless.js';
|
|
2
|
+
export { addWatermarkRecipeLayer, duplicateWatermarkRecipeLayer, groupWatermarkRecipeLayers, removeWatermarkRecipeLayer, reorderWatermarkRecipeLayer, ungroupWatermarkRecipeLayers, updateWatermarkRecipeLayer, } from '@image-marker/recipe';
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export * from './editor/headless.js';
|
|
2
|
+
export { addWatermarkRecipeLayer, duplicateWatermarkRecipeLayer, groupWatermarkRecipeLayers, removeWatermarkRecipeLayer, reorderWatermarkRecipeLayer, ungroupWatermarkRecipeLayers, updateWatermarkRecipeLayer, } from '@image-marker/recipe';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Generated from shared authoritative sources; editor/core-contract uses the Web Core binding.",
|
|
3
|
+
"sources": {
|
|
4
|
+
"packages/editor/src/adapter.ts": "530d2ad346f4c1be82a5eb632119cb43e46c8f67a6a0120c984b9214b1e86c67",
|
|
5
|
+
"packages/editor/src/controller.ts": "e097e11aa9297174707aa7eee089107aecabf66d9229f88a10ebae674e9d6781",
|
|
6
|
+
"packages/editor/src/core-contract.ts": "c56b2ce04daf5cbac5f531f4ad4b0bedfcce237e41a7eed59f14831f31db96b8",
|
|
7
|
+
"packages/editor/src/geometry.ts": "6fc09e7e586739ebc7e390da1260bdd85e7f0f35523584a44f1c2d1d1a10bf2d",
|
|
8
|
+
"packages/editor/src/headless.ts": "21704f8a9ed5af9b2414feb9d24f975b47e7b3746c6f39bd1575e7048c652470",
|
|
9
|
+
"packages/editor/src/presets.ts": "e680d7ec731e2525e6bcc09ad62630e16e9a84bf8c473d6fcfc8e87e60fc2002",
|
|
10
|
+
"packages/editor/src/projection.ts": "f2e0de309fe9acc061655f310defae38b746d87c675a6471574010b23f1455a1",
|
|
11
|
+
"packages/editor/src/types.ts": "7c311de43577555ac18305cac236f80c0090946f2714b1f5debabf1a25e82eb2",
|
|
12
|
+
"packages/web/src/editor-adapter.ts": "143fb98ca56936062574d0d1d2f00eba6928e67b6ae1dd73a0accae8de4d141e",
|
|
13
|
+
"packages/web/src/headless.ts": "4a1860a19eef496c2747dd2c32c18d1a6a9b9b29a242f16227793a9c750b7cdd",
|
|
14
|
+
"packages/web/src/index.ts": "be23ae2474a2d45d23dff60a09a214d209b43c01ca9680a3b79fdd37c557b1dc",
|
|
15
|
+
"src/batch.ts": "387c77b6bf25b3ab1989dd34540f60380453d13448a785585a094daeb86a4395",
|
|
16
|
+
"src/content-credentials.ts": "095cf78f92060cfc526e70bc8bde1acb044e4a975fc4b9a44dbd9251e8113b95",
|
|
17
|
+
"src/image-info.ts": "6083966af4f28153aff1b7305ef61ed3b1469aaefdc9aff712585833594b3777",
|
|
18
|
+
"src/index.ts": "0515a28b3b7bc86b6a3a9f742183be405b9382c6a79b790de73f271f6478a811",
|
|
19
|
+
"src/invisible-watermark.ts": "f00342e7c19b4af763eb666e2997bb7653ccb653d7996013b073e287236ebc87",
|
|
20
|
+
"src/job.ts": "7e88640d435734b51d17a9497db24b9870b96fa2b00f134f0bd4573304ca3a29",
|
|
21
|
+
"src/layout.ts": "08b1c72d595cb038ba14b31b2d47bbfb1ec28fa208ac4aee94e8f46bff2ce30c",
|
|
22
|
+
"src/marker.ts": "e6dea90ed0dac568e64716b56e076e4af68b55c85997719258fad19c6d919a29",
|
|
23
|
+
"src/recipe.ts": "930c6d7fc42eb83b2c4d3d4e220e9926c96f3999930fcb8c04b890c494564321",
|
|
24
|
+
"src/result.ts": "0c76a449979925c8c867fddb56ec784d2b67f4bf0d0a5121bd14c0eb2b707355",
|
|
25
|
+
"src/trace-runtime.ts": "d1e738cf0c1da5c34e914fbae01d1a1d99c2e8b5f1742e36bb9db94d0b48588c",
|
|
26
|
+
"src/validate.ts": "f42fc668ea19bd4c53e8a83aa2fecf842124166adb4bf88b0c393f856779a6a0",
|
|
27
|
+
"src/web/browser.ts": "336ee315b2b38fee7362d9dbd0d3ec8105f3519ede9e7d15b0855365d8c5ef9b",
|
|
28
|
+
"src/web/helpers.ts": "04fd36771ef9820fa8901a9f3f258f43d8f9dc80adae1e630c97aca691434b6c",
|
|
29
|
+
"src/web/index.ts": "e3f819f8c9a122358ce77f680a3a3fe6880d89bb8fff17c2f026270b7c7a7c28",
|
|
30
|
+
"src/web/invisible-worker-client.ts": "1e1d341306eeb35120c8e145594e0431f10b5d7f45a61d2d87573211af6e4b58",
|
|
31
|
+
"src/web/renderer.ts": "2d142c1aae0fd2fa5d4737560cf74ec644bc8f4fed1ed2f57896d178cc7967b7",
|
|
32
|
+
"src/web/runtime.ts": "0a3c87c0aab050331bc71e03d8babb627d85f1200ab45be14aca7c4f4b1e121d"
|
|
33
|
+
}
|
|
34
|
+
}
|