@overtone-art/canvas-editor-core 0.2.7 → 0.2.8
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/dist/chunk-ORZZ6MGQ.mjs +228 -0
- package/dist/chunk-ORZZ6MGQ.mjs.map +1 -0
- package/dist/index.d.mts +157 -4
- package/dist/index.d.ts +157 -4
- package/dist/index.global.js +59 -59
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +660 -55
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +500 -21
- package/dist/index.mjs.map +1 -1
- package/dist/node.d.mts +51 -2
- package/dist/node.d.ts +51 -2
- package/dist/node.js +546 -13
- package/dist/node.js.map +1 -1
- package/dist/node.mjs +446 -4
- package/dist/node.mjs.map +1 -1
- package/dist/{types-bOY7oYVq.d.mts → types-D60CfxL9.d.mts} +80 -2
- package/dist/{types-bOY7oYVq.d.ts → types-D60CfxL9.d.ts} +80 -2
- package/package.json +37 -2
- package/dist/chunk-NINRTPOJ.mjs +0 -104
- package/dist/chunk-NINRTPOJ.mjs.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FabricObject } from 'fabric';
|
|
2
2
|
|
|
3
3
|
type Unit = 'px' | 'mm' | 'in';
|
|
4
|
-
type LayerType = 'image' | 'text' | 'shape' | 'template' | 'group';
|
|
4
|
+
type LayerType = 'image' | 'mask' | 'text' | 'shape' | 'template' | 'group';
|
|
5
5
|
interface LayerData {
|
|
6
6
|
id: string;
|
|
7
7
|
type: LayerType;
|
|
@@ -14,6 +14,11 @@ interface LayerData {
|
|
|
14
14
|
}
|
|
15
15
|
interface LayerMeta {
|
|
16
16
|
pattern?: PatternState;
|
|
17
|
+
mask?: {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
revision: number;
|
|
21
|
+
};
|
|
17
22
|
[key: string]: unknown;
|
|
18
23
|
}
|
|
19
24
|
interface SerializedLayer extends LayerData {
|
|
@@ -30,6 +35,7 @@ interface EditorState {
|
|
|
30
35
|
layers: SerializedLayer[];
|
|
31
36
|
background?: string;
|
|
32
37
|
backgroundImage?: Record<string, unknown> | null;
|
|
38
|
+
backgroundImageOptions?: SerializedBackgroundImageOptions | null;
|
|
33
39
|
mockup?: MockupConfig | null;
|
|
34
40
|
}
|
|
35
41
|
interface EditorConfig {
|
|
@@ -54,6 +60,63 @@ interface ResizeOptions {
|
|
|
54
60
|
interface BackgroundImageOptions {
|
|
55
61
|
fit?: 'cover' | 'contain' | 'stretch';
|
|
56
62
|
opacity?: number;
|
|
63
|
+
/** Fabric/HTML image loading mode for remote assets. */
|
|
64
|
+
crossOrigin?: '' | 'anonymous' | 'use-credentials' | null;
|
|
65
|
+
/** Cancels the current load; never serialized. */
|
|
66
|
+
signal?: AbortSignal;
|
|
67
|
+
}
|
|
68
|
+
type SerializedBackgroundImageOptions = Omit<BackgroundImageOptions, 'signal'>;
|
|
69
|
+
interface SemanticExportOptions extends PngLikeExportOptions {
|
|
70
|
+
/** Document keeps full-canvas coordinates; source returns native image pixels. */
|
|
71
|
+
resolution?: 'document' | 'source';
|
|
72
|
+
}
|
|
73
|
+
interface MaskPoint {
|
|
74
|
+
x: number;
|
|
75
|
+
y: number;
|
|
76
|
+
}
|
|
77
|
+
interface MaskBrushOptions {
|
|
78
|
+
mode: 'add' | 'subtract';
|
|
79
|
+
size: number;
|
|
80
|
+
/** Soft-edge inner radius ratio, from 0 (soft) to 1 (hard). */
|
|
81
|
+
hardness: number;
|
|
82
|
+
}
|
|
83
|
+
type MaskRefinementPrompt = {
|
|
84
|
+
type: 'point';
|
|
85
|
+
x: number;
|
|
86
|
+
y: number;
|
|
87
|
+
label: 'foreground' | 'background';
|
|
88
|
+
} | {
|
|
89
|
+
type: 'rectangle';
|
|
90
|
+
left: number;
|
|
91
|
+
top: number;
|
|
92
|
+
width: number;
|
|
93
|
+
height: number;
|
|
94
|
+
} | {
|
|
95
|
+
type: 'text';
|
|
96
|
+
value: string;
|
|
97
|
+
};
|
|
98
|
+
interface MaskRefinementRequest {
|
|
99
|
+
mask: string;
|
|
100
|
+
width: number;
|
|
101
|
+
height: number;
|
|
102
|
+
prompts: MaskRefinementPrompt[];
|
|
103
|
+
}
|
|
104
|
+
interface MaskRefinementResult {
|
|
105
|
+
dataUrl: string;
|
|
106
|
+
width?: number;
|
|
107
|
+
height?: number;
|
|
108
|
+
metadata?: Record<string, unknown>;
|
|
109
|
+
}
|
|
110
|
+
interface MaskRefinementProvider {
|
|
111
|
+
refine(request: MaskRefinementRequest, options?: {
|
|
112
|
+
signal?: AbortSignal;
|
|
113
|
+
onProgress?: (progress: number) => void;
|
|
114
|
+
}): Promise<MaskRefinementResult>;
|
|
115
|
+
}
|
|
116
|
+
/** Kept structural to avoid coupling the public types module to export.ts. */
|
|
117
|
+
interface PngLikeExportOptions {
|
|
118
|
+
multiplier?: number;
|
|
119
|
+
quality?: number;
|
|
57
120
|
}
|
|
58
121
|
interface DpiIssue {
|
|
59
122
|
layerId: string;
|
|
@@ -303,6 +366,19 @@ interface MockupOverlay {
|
|
|
303
366
|
blendMode?: MockupBlendMode;
|
|
304
367
|
opacity?: number;
|
|
305
368
|
}
|
|
369
|
+
type MockupDisplacementChannel = 'red' | 'green' | 'blue' | 'alpha';
|
|
370
|
+
interface MockupDisplacement {
|
|
371
|
+
/** Channel-map image cover-fitted to the mockup canvas. A value of 128 is neutral. */
|
|
372
|
+
image: string;
|
|
373
|
+
/** Maximum horizontal displacement in document pixels. Defaults to 10. */
|
|
374
|
+
scaleX?: number;
|
|
375
|
+
/** Maximum vertical displacement in document pixels. Defaults to 10. */
|
|
376
|
+
scaleY?: number;
|
|
377
|
+
/** Map channel controlling horizontal displacement. Defaults to red. */
|
|
378
|
+
channelX?: MockupDisplacementChannel;
|
|
379
|
+
/** Map channel controlling vertical displacement. Defaults to green. */
|
|
380
|
+
channelY?: MockupDisplacementChannel;
|
|
381
|
+
}
|
|
306
382
|
interface MockupConfig {
|
|
307
383
|
/** Garment/product image rendered behind the design. */
|
|
308
384
|
image: string;
|
|
@@ -312,6 +388,8 @@ interface MockupConfig {
|
|
|
312
388
|
clipToPrintArea?: boolean;
|
|
313
389
|
designBlendMode?: MockupBlendMode;
|
|
314
390
|
designOpacity?: number;
|
|
391
|
+
/** Optional channel-map warp applied to the design before compositing. */
|
|
392
|
+
displacement?: MockupDisplacement;
|
|
315
393
|
/** Optional product lighting/shadow pass rendered after the design. */
|
|
316
394
|
overlay?: MockupOverlay;
|
|
317
395
|
}
|
|
@@ -380,4 +458,4 @@ interface EditorEvents {
|
|
|
380
458
|
};
|
|
381
459
|
}
|
|
382
460
|
|
|
383
|
-
export { type
|
|
461
|
+
export { type PatternLocks as A, type BackgroundImageOptions as B, type CanvasSizePreset as C, DEFAULT_PATTERN_CONFIG as D, type EditorConfig as E, type FileAdapter as F, type PatternSourceResolver as G, type PatternState as H, type ImageAdjustments as I, type PositioningAdapter as J, type PrintifyPositioning as K, type LayerData as L, type MaskBrushOptions as M, type NormalizedLayerPosition as N, type ProjectPage as O, type PatternConfig as P, type ProjectState as Q, type ResizeOptions as R, type SemanticExportOptions as S, type SerializedBackgroundImageOptions as T, type SerializedLayer as U, type ShapePlugin as V, type SvgExportOptions as W, type TemplateDefinition as X, type TemplateParameter as Y, type TileMode as Z, type Unit as _, type DpiIssue as a, type EditorEvents as b, type EditorState as c, type ExportFormat as d, type FontDefinition as e, type ImageAttribution as f, type ImageProvider as g, type ImageProviderResult as h, type ImageSearchOptions as i, type ImageSearchResult as j, type LayerMeta as k, type LayerType as l, type LicenseConfig as m, type LicensePayload as n, type LicenseStatus as o, type MaskPoint as p, type MaskRefinementPrompt as q, type MaskRefinementProvider as r, type MaskRefinementRequest as s, type MaskRefinementResult as t, type MockupBlendMode as u, type MockupConfig as v, type MockupDisplacement as w, type MockupDisplacementChannel as x, type MockupOverlay as y, type MockupPrintArea as z };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FabricObject } from 'fabric';
|
|
2
2
|
|
|
3
3
|
type Unit = 'px' | 'mm' | 'in';
|
|
4
|
-
type LayerType = 'image' | 'text' | 'shape' | 'template' | 'group';
|
|
4
|
+
type LayerType = 'image' | 'mask' | 'text' | 'shape' | 'template' | 'group';
|
|
5
5
|
interface LayerData {
|
|
6
6
|
id: string;
|
|
7
7
|
type: LayerType;
|
|
@@ -14,6 +14,11 @@ interface LayerData {
|
|
|
14
14
|
}
|
|
15
15
|
interface LayerMeta {
|
|
16
16
|
pattern?: PatternState;
|
|
17
|
+
mask?: {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
revision: number;
|
|
21
|
+
};
|
|
17
22
|
[key: string]: unknown;
|
|
18
23
|
}
|
|
19
24
|
interface SerializedLayer extends LayerData {
|
|
@@ -30,6 +35,7 @@ interface EditorState {
|
|
|
30
35
|
layers: SerializedLayer[];
|
|
31
36
|
background?: string;
|
|
32
37
|
backgroundImage?: Record<string, unknown> | null;
|
|
38
|
+
backgroundImageOptions?: SerializedBackgroundImageOptions | null;
|
|
33
39
|
mockup?: MockupConfig | null;
|
|
34
40
|
}
|
|
35
41
|
interface EditorConfig {
|
|
@@ -54,6 +60,63 @@ interface ResizeOptions {
|
|
|
54
60
|
interface BackgroundImageOptions {
|
|
55
61
|
fit?: 'cover' | 'contain' | 'stretch';
|
|
56
62
|
opacity?: number;
|
|
63
|
+
/** Fabric/HTML image loading mode for remote assets. */
|
|
64
|
+
crossOrigin?: '' | 'anonymous' | 'use-credentials' | null;
|
|
65
|
+
/** Cancels the current load; never serialized. */
|
|
66
|
+
signal?: AbortSignal;
|
|
67
|
+
}
|
|
68
|
+
type SerializedBackgroundImageOptions = Omit<BackgroundImageOptions, 'signal'>;
|
|
69
|
+
interface SemanticExportOptions extends PngLikeExportOptions {
|
|
70
|
+
/** Document keeps full-canvas coordinates; source returns native image pixels. */
|
|
71
|
+
resolution?: 'document' | 'source';
|
|
72
|
+
}
|
|
73
|
+
interface MaskPoint {
|
|
74
|
+
x: number;
|
|
75
|
+
y: number;
|
|
76
|
+
}
|
|
77
|
+
interface MaskBrushOptions {
|
|
78
|
+
mode: 'add' | 'subtract';
|
|
79
|
+
size: number;
|
|
80
|
+
/** Soft-edge inner radius ratio, from 0 (soft) to 1 (hard). */
|
|
81
|
+
hardness: number;
|
|
82
|
+
}
|
|
83
|
+
type MaskRefinementPrompt = {
|
|
84
|
+
type: 'point';
|
|
85
|
+
x: number;
|
|
86
|
+
y: number;
|
|
87
|
+
label: 'foreground' | 'background';
|
|
88
|
+
} | {
|
|
89
|
+
type: 'rectangle';
|
|
90
|
+
left: number;
|
|
91
|
+
top: number;
|
|
92
|
+
width: number;
|
|
93
|
+
height: number;
|
|
94
|
+
} | {
|
|
95
|
+
type: 'text';
|
|
96
|
+
value: string;
|
|
97
|
+
};
|
|
98
|
+
interface MaskRefinementRequest {
|
|
99
|
+
mask: string;
|
|
100
|
+
width: number;
|
|
101
|
+
height: number;
|
|
102
|
+
prompts: MaskRefinementPrompt[];
|
|
103
|
+
}
|
|
104
|
+
interface MaskRefinementResult {
|
|
105
|
+
dataUrl: string;
|
|
106
|
+
width?: number;
|
|
107
|
+
height?: number;
|
|
108
|
+
metadata?: Record<string, unknown>;
|
|
109
|
+
}
|
|
110
|
+
interface MaskRefinementProvider {
|
|
111
|
+
refine(request: MaskRefinementRequest, options?: {
|
|
112
|
+
signal?: AbortSignal;
|
|
113
|
+
onProgress?: (progress: number) => void;
|
|
114
|
+
}): Promise<MaskRefinementResult>;
|
|
115
|
+
}
|
|
116
|
+
/** Kept structural to avoid coupling the public types module to export.ts. */
|
|
117
|
+
interface PngLikeExportOptions {
|
|
118
|
+
multiplier?: number;
|
|
119
|
+
quality?: number;
|
|
57
120
|
}
|
|
58
121
|
interface DpiIssue {
|
|
59
122
|
layerId: string;
|
|
@@ -303,6 +366,19 @@ interface MockupOverlay {
|
|
|
303
366
|
blendMode?: MockupBlendMode;
|
|
304
367
|
opacity?: number;
|
|
305
368
|
}
|
|
369
|
+
type MockupDisplacementChannel = 'red' | 'green' | 'blue' | 'alpha';
|
|
370
|
+
interface MockupDisplacement {
|
|
371
|
+
/** Channel-map image cover-fitted to the mockup canvas. A value of 128 is neutral. */
|
|
372
|
+
image: string;
|
|
373
|
+
/** Maximum horizontal displacement in document pixels. Defaults to 10. */
|
|
374
|
+
scaleX?: number;
|
|
375
|
+
/** Maximum vertical displacement in document pixels. Defaults to 10. */
|
|
376
|
+
scaleY?: number;
|
|
377
|
+
/** Map channel controlling horizontal displacement. Defaults to red. */
|
|
378
|
+
channelX?: MockupDisplacementChannel;
|
|
379
|
+
/** Map channel controlling vertical displacement. Defaults to green. */
|
|
380
|
+
channelY?: MockupDisplacementChannel;
|
|
381
|
+
}
|
|
306
382
|
interface MockupConfig {
|
|
307
383
|
/** Garment/product image rendered behind the design. */
|
|
308
384
|
image: string;
|
|
@@ -312,6 +388,8 @@ interface MockupConfig {
|
|
|
312
388
|
clipToPrintArea?: boolean;
|
|
313
389
|
designBlendMode?: MockupBlendMode;
|
|
314
390
|
designOpacity?: number;
|
|
391
|
+
/** Optional channel-map warp applied to the design before compositing. */
|
|
392
|
+
displacement?: MockupDisplacement;
|
|
315
393
|
/** Optional product lighting/shadow pass rendered after the design. */
|
|
316
394
|
overlay?: MockupOverlay;
|
|
317
395
|
}
|
|
@@ -380,4 +458,4 @@ interface EditorEvents {
|
|
|
380
458
|
};
|
|
381
459
|
}
|
|
382
460
|
|
|
383
|
-
export { type
|
|
461
|
+
export { type PatternLocks as A, type BackgroundImageOptions as B, type CanvasSizePreset as C, DEFAULT_PATTERN_CONFIG as D, type EditorConfig as E, type FileAdapter as F, type PatternSourceResolver as G, type PatternState as H, type ImageAdjustments as I, type PositioningAdapter as J, type PrintifyPositioning as K, type LayerData as L, type MaskBrushOptions as M, type NormalizedLayerPosition as N, type ProjectPage as O, type PatternConfig as P, type ProjectState as Q, type ResizeOptions as R, type SemanticExportOptions as S, type SerializedBackgroundImageOptions as T, type SerializedLayer as U, type ShapePlugin as V, type SvgExportOptions as W, type TemplateDefinition as X, type TemplateParameter as Y, type TileMode as Z, type Unit as _, type DpiIssue as a, type EditorEvents as b, type EditorState as c, type ExportFormat as d, type FontDefinition as e, type ImageAttribution as f, type ImageProvider as g, type ImageProviderResult as h, type ImageSearchOptions as i, type ImageSearchResult as j, type LayerMeta as k, type LayerType as l, type LicenseConfig as m, type LicensePayload as n, type LicenseStatus as o, type MaskPoint as p, type MaskRefinementPrompt as q, type MaskRefinementProvider as r, type MaskRefinementRequest as s, type MaskRefinementResult as t, type MockupBlendMode as u, type MockupConfig as v, type MockupDisplacement as w, type MockupDisplacementChannel as x, type MockupOverlay as y, type MockupPrintArea as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@overtone-art/canvas-editor-core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "Abstract pluggable canvas editor built on Fabric.js",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -37,9 +37,43 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"fabric": "^7.4.0",
|
|
40
|
-
"nanoid": "^5.1.
|
|
40
|
+
"nanoid": "^5.1.16"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"fontkit": "^2.0.4",
|
|
44
|
+
"pdf-lib": "^1.17.1",
|
|
45
|
+
"pdfkit": "^0.19.1",
|
|
46
|
+
"sharp": "^0.35.0",
|
|
47
|
+
"svg-to-pdfkit": "^0.1.8"
|
|
48
|
+
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"fontkit": {
|
|
51
|
+
"optional": true
|
|
52
|
+
},
|
|
53
|
+
"pdf-lib": {
|
|
54
|
+
"optional": true
|
|
55
|
+
},
|
|
56
|
+
"pdfkit": {
|
|
57
|
+
"optional": true
|
|
58
|
+
},
|
|
59
|
+
"sharp": {
|
|
60
|
+
"optional": true
|
|
61
|
+
},
|
|
62
|
+
"svg-to-pdfkit": {
|
|
63
|
+
"optional": true
|
|
64
|
+
}
|
|
41
65
|
},
|
|
42
66
|
"devDependencies": {
|
|
67
|
+
"@types/fontkit": "^2.0.9",
|
|
68
|
+
"@types/opentype.js": "^1.3.10",
|
|
69
|
+
"@types/pdfkit": "^0.17.6",
|
|
70
|
+
"@types/svg-to-pdfkit": "^0.1.4",
|
|
71
|
+
"fontkit": "^2.0.4",
|
|
72
|
+
"opentype.js": "^1.3.4",
|
|
73
|
+
"pdf-lib": "^1.17.1",
|
|
74
|
+
"pdfkit": "^0.19.1",
|
|
75
|
+
"sharp": "^0.35.0",
|
|
76
|
+
"svg-to-pdfkit": "^0.1.8",
|
|
43
77
|
"tsup": "^8.4.0",
|
|
44
78
|
"typescript": "^5.8.2",
|
|
45
79
|
"vitest": "^3.2.1",
|
|
@@ -52,6 +86,7 @@
|
|
|
52
86
|
"lint": "eslint src/",
|
|
53
87
|
"type-check": "tsc --noEmit",
|
|
54
88
|
"test": "vitest run",
|
|
89
|
+
"test:coverage": "vitest run --coverage",
|
|
55
90
|
"test:watch": "vitest"
|
|
56
91
|
}
|
|
57
92
|
}
|
package/dist/chunk-NINRTPOJ.mjs
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
// src/export.ts
|
|
2
|
-
function computePrintAreaClip(area, scaleX, scaleY, targetWidth, targetHeight) {
|
|
3
|
-
const left = Math.max(0, Math.min(targetWidth, area.left * scaleX));
|
|
4
|
-
const top = Math.max(0, Math.min(targetHeight, area.top * scaleY));
|
|
5
|
-
const right = Math.max(left, Math.min(targetWidth, (area.left + area.width) * scaleX));
|
|
6
|
-
const bottom = Math.max(top, Math.min(targetHeight, (area.top + area.height) * scaleY));
|
|
7
|
-
return { left, top, width: right - left, height: bottom - top };
|
|
8
|
-
}
|
|
9
|
-
function computeCoverPlacement(sourceWidth, sourceHeight, targetWidth, targetHeight) {
|
|
10
|
-
if (sourceWidth <= 0 || sourceHeight <= 0 || targetWidth <= 0 || targetHeight <= 0) {
|
|
11
|
-
throw new Error("Cover dimensions must be positive");
|
|
12
|
-
}
|
|
13
|
-
const scale = Math.max(targetWidth / sourceWidth, targetHeight / sourceHeight);
|
|
14
|
-
const width = sourceWidth * scale;
|
|
15
|
-
const height = sourceHeight * scale;
|
|
16
|
-
return {
|
|
17
|
-
left: (targetWidth - width) / 2,
|
|
18
|
-
top: (targetHeight - height) / 2,
|
|
19
|
-
width,
|
|
20
|
-
height
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
function canvasElementToBlob(output, format, quality) {
|
|
24
|
-
const mime = format === "jpeg" ? "image/jpeg" : `image/${format}`;
|
|
25
|
-
return new Promise((resolve, reject) => {
|
|
26
|
-
output.toBlob(
|
|
27
|
-
(blob) => blob ? resolve(blob) : reject(new Error(`Failed to export ${format}`)),
|
|
28
|
-
mime,
|
|
29
|
-
quality
|
|
30
|
-
);
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
async function exportPNG(canvas, options = {}) {
|
|
34
|
-
const { multiplier = 1, format = "png", quality = 1 } = options;
|
|
35
|
-
const output = canvas.toCanvasElement(multiplier);
|
|
36
|
-
return canvasElementToBlob(output, format, quality);
|
|
37
|
-
}
|
|
38
|
-
async function exportMockup(canvas, mockup, options = {}) {
|
|
39
|
-
const { multiplier = 1, format = "png", quality = 1 } = options;
|
|
40
|
-
const design = canvas.toCanvasElement(multiplier);
|
|
41
|
-
const output = design.ownerDocument.createElement("canvas");
|
|
42
|
-
output.width = design.width;
|
|
43
|
-
output.height = design.height;
|
|
44
|
-
const context = output.getContext("2d");
|
|
45
|
-
if (!context) throw new Error("2D canvas context is unavailable");
|
|
46
|
-
const loadImage = (url) => new Promise((resolve, reject) => {
|
|
47
|
-
const element = new Image();
|
|
48
|
-
element.crossOrigin = "anonymous";
|
|
49
|
-
element.onload = () => resolve(element);
|
|
50
|
-
element.onerror = () => reject(new Error(`Failed to load mockup image: ${url}`));
|
|
51
|
-
element.src = url;
|
|
52
|
-
});
|
|
53
|
-
const drawCover = (image) => {
|
|
54
|
-
const placement = computeCoverPlacement(
|
|
55
|
-
image.naturalWidth || image.width,
|
|
56
|
-
image.naturalHeight || image.height,
|
|
57
|
-
output.width,
|
|
58
|
-
output.height
|
|
59
|
-
);
|
|
60
|
-
context.drawImage(image, placement.left, placement.top, placement.width, placement.height);
|
|
61
|
-
};
|
|
62
|
-
drawCover(await loadImage(mockup.image));
|
|
63
|
-
context.save();
|
|
64
|
-
if (mockup.printArea && mockup.clipToPrintArea !== false) {
|
|
65
|
-
const clip = computePrintAreaClip(
|
|
66
|
-
mockup.printArea,
|
|
67
|
-
output.width / canvas.getWidth(),
|
|
68
|
-
output.height / canvas.getHeight(),
|
|
69
|
-
output.width,
|
|
70
|
-
output.height
|
|
71
|
-
);
|
|
72
|
-
context.beginPath();
|
|
73
|
-
context.rect(clip.left, clip.top, clip.width, clip.height);
|
|
74
|
-
context.clip();
|
|
75
|
-
}
|
|
76
|
-
context.globalAlpha = Math.max(0, Math.min(1, mockup.designOpacity ?? 1));
|
|
77
|
-
context.globalCompositeOperation = !mockup.designBlendMode || mockup.designBlendMode === "normal" ? "source-over" : mockup.designBlendMode;
|
|
78
|
-
context.drawImage(design, 0, 0);
|
|
79
|
-
context.restore();
|
|
80
|
-
if (mockup.overlay) {
|
|
81
|
-
context.save();
|
|
82
|
-
context.globalAlpha = Math.max(0, Math.min(1, mockup.overlay.opacity ?? 1));
|
|
83
|
-
context.globalCompositeOperation = mockup.overlay.blendMode === "normal" ? "source-over" : mockup.overlay.blendMode ?? "multiply";
|
|
84
|
-
drawCover(await loadImage(mockup.overlay.image));
|
|
85
|
-
context.restore();
|
|
86
|
-
}
|
|
87
|
-
return canvasElementToBlob(output, format, quality);
|
|
88
|
-
}
|
|
89
|
-
function exportSVG(canvas) {
|
|
90
|
-
return canvas.toSVG();
|
|
91
|
-
}
|
|
92
|
-
function exportDataURL(canvas, format = "png", multiplier = 1) {
|
|
93
|
-
return canvas.toDataURL({ format, multiplier });
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export {
|
|
97
|
-
computePrintAreaClip,
|
|
98
|
-
computeCoverPlacement,
|
|
99
|
-
exportPNG,
|
|
100
|
-
exportMockup,
|
|
101
|
-
exportSVG,
|
|
102
|
-
exportDataURL
|
|
103
|
-
};
|
|
104
|
-
//# sourceMappingURL=chunk-NINRTPOJ.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/export.ts"],"sourcesContent":["import type { Canvas, ImageFormat } from 'fabric';\nimport type { MockupConfig, MockupPrintArea } from './types';\n\nexport interface PngExportOptions {\n multiplier?: number;\n format?: ImageFormat;\n quality?: number;\n}\n\nexport interface CoverPlacement {\n left: number;\n top: number;\n width: number;\n height: number;\n}\n\nexport function computePrintAreaClip(\n area: MockupPrintArea,\n scaleX: number,\n scaleY: number,\n targetWidth: number,\n targetHeight: number,\n): MockupPrintArea {\n const left = Math.max(0, Math.min(targetWidth, area.left * scaleX));\n const top = Math.max(0, Math.min(targetHeight, area.top * scaleY));\n const right = Math.max(left, Math.min(targetWidth, (area.left + area.width) * scaleX));\n const bottom = Math.max(top, Math.min(targetHeight, (area.top + area.height) * scaleY));\n return { left, top, width: right - left, height: bottom - top };\n}\n\n/** Object-fit: cover geometry, exported for deterministic preview/composite tests. */\nexport function computeCoverPlacement(\n sourceWidth: number,\n sourceHeight: number,\n targetWidth: number,\n targetHeight: number,\n): CoverPlacement {\n if (sourceWidth <= 0 || sourceHeight <= 0 || targetWidth <= 0 || targetHeight <= 0) {\n throw new Error('Cover dimensions must be positive');\n }\n const scale = Math.max(targetWidth / sourceWidth, targetHeight / sourceHeight);\n const width = sourceWidth * scale;\n const height = sourceHeight * scale;\n return {\n left: (targetWidth - width) / 2,\n top: (targetHeight - height) / 2,\n width,\n height,\n };\n}\n\nfunction canvasElementToBlob(\n output: HTMLCanvasElement,\n format: ImageFormat,\n quality: number,\n): Promise<Blob> {\n const mime = format === 'jpeg' ? 'image/jpeg' : `image/${format}`;\n return new Promise<Blob>((resolve, reject) => {\n output.toBlob(\n (blob) => (blob ? resolve(blob) : reject(new Error(`Failed to export ${format}`))),\n mime,\n quality,\n );\n });\n}\n\nexport async function exportPNG(canvas: Canvas, options: PngExportOptions = {}): Promise<Blob> {\n const { multiplier = 1, format = 'png' as ImageFormat, quality = 1 } = options;\n const output = canvas.toCanvasElement(multiplier);\n return canvasElementToBlob(output, format, quality);\n}\n\n/** Rasterize the browser mockup preview together with the transparent design. */\nexport async function exportMockup(\n canvas: Canvas,\n mockup: MockupConfig,\n options: PngExportOptions = {},\n): Promise<Blob> {\n const { multiplier = 1, format = 'png' as ImageFormat, quality = 1 } = options;\n const design = canvas.toCanvasElement(multiplier);\n const output = design.ownerDocument.createElement('canvas');\n output.width = design.width;\n output.height = design.height;\n const context = output.getContext('2d');\n if (!context) throw new Error('2D canvas context is unavailable');\n\n const loadImage = (url: string) =>\n new Promise<HTMLImageElement>((resolve, reject) => {\n const element = new Image();\n element.crossOrigin = 'anonymous';\n element.onload = () => resolve(element);\n element.onerror = () => reject(new Error(`Failed to load mockup image: ${url}`));\n element.src = url;\n });\n const drawCover = (image: HTMLImageElement) => {\n const placement = computeCoverPlacement(\n image.naturalWidth || image.width,\n image.naturalHeight || image.height,\n output.width,\n output.height,\n );\n context.drawImage(image, placement.left, placement.top, placement.width, placement.height);\n };\n\n drawCover(await loadImage(mockup.image));\n context.save();\n if (mockup.printArea && mockup.clipToPrintArea !== false) {\n const clip = computePrintAreaClip(\n mockup.printArea,\n output.width / canvas.getWidth(),\n output.height / canvas.getHeight(),\n output.width,\n output.height,\n );\n context.beginPath();\n context.rect(clip.left, clip.top, clip.width, clip.height);\n context.clip();\n }\n context.globalAlpha = Math.max(0, Math.min(1, mockup.designOpacity ?? 1));\n context.globalCompositeOperation =\n !mockup.designBlendMode || mockup.designBlendMode === 'normal'\n ? 'source-over'\n : mockup.designBlendMode;\n context.drawImage(design, 0, 0);\n context.restore();\n\n if (mockup.overlay) {\n context.save();\n context.globalAlpha = Math.max(0, Math.min(1, mockup.overlay.opacity ?? 1));\n context.globalCompositeOperation =\n mockup.overlay.blendMode === 'normal'\n ? 'source-over'\n : (mockup.overlay.blendMode ?? 'multiply');\n drawCover(await loadImage(mockup.overlay.image));\n context.restore();\n }\n return canvasElementToBlob(output, format, quality);\n}\n\nexport function exportSVG(canvas: Canvas): string {\n return canvas.toSVG();\n}\n\nexport function exportDataURL(canvas: Canvas, format: ImageFormat = 'png', multiplier = 1): string {\n return canvas.toDataURL({ format, multiplier });\n}\n"],"mappings":"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","names":[]}
|