@overtone-art/canvas-editor-core 0.2.7 → 0.3.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/dist/chunk-MCBRZQ4M.mjs +261 -0
- package/dist/chunk-MCBRZQ4M.mjs.map +1 -0
- package/dist/index.d.mts +326 -5
- package/dist/index.d.ts +326 -5
- package/dist/index.global.js +71 -71
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1350 -72
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1136 -35
- 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-Dh1unrzT.d.mts} +107 -2
- package/dist/{types-bOY7oYVq.d.ts → types-Dh1unrzT.d.ts} +107 -2
- package/package.json +42 -5
- 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,8 +14,40 @@ interface LayerData {
|
|
|
14
14
|
}
|
|
15
15
|
interface LayerMeta {
|
|
16
16
|
pattern?: PatternState;
|
|
17
|
+
mask?: {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
revision: number;
|
|
21
|
+
};
|
|
22
|
+
/** Preset silhouette or texture the layer is clipped to. */
|
|
23
|
+
maskPreset?: MaskPresetId;
|
|
24
|
+
/** Baseline curve applied to a text layer. */
|
|
25
|
+
curve?: TextCurveConfig;
|
|
26
|
+
/** Text box width authored before a curve widened it, in px. */
|
|
27
|
+
curveWidth?: number;
|
|
28
|
+
/** Layer resizes uniformly: single-axis handles are hidden. */
|
|
29
|
+
lockAspect?: boolean;
|
|
17
30
|
[key: string]: unknown;
|
|
18
31
|
}
|
|
32
|
+
interface TextCurveConfig {
|
|
33
|
+
/**
|
|
34
|
+
* Baseline bend, −100…100. Positive arcs the run upward (a rainbow),
|
|
35
|
+
* negative into a valley; ±100 closes it into a full ring. 0 is straight.
|
|
36
|
+
*/
|
|
37
|
+
arc: number;
|
|
38
|
+
/** Sine-wave amplitude, 0…100, as a share of the font size. Ignored when arced. */
|
|
39
|
+
wave: number;
|
|
40
|
+
}
|
|
41
|
+
type ShapeMaskPresetId = 'circle' | 'square' | 'triangle' | 'star' | 'heart' | 'octagram' | 'arch' | 'zigzag';
|
|
42
|
+
type TextureMaskPresetId = 'vignette' | 'halftone' | 'spray' | 'grunge' | 'torn' | 'band';
|
|
43
|
+
type MaskPresetId = ShapeMaskPresetId | TextureMaskPresetId;
|
|
44
|
+
interface LayerShadowConfig {
|
|
45
|
+
enabled: boolean;
|
|
46
|
+
color: string;
|
|
47
|
+
blur: number;
|
|
48
|
+
offsetX: number;
|
|
49
|
+
offsetY: number;
|
|
50
|
+
}
|
|
19
51
|
interface SerializedLayer extends LayerData {
|
|
20
52
|
fabricObject: Record<string, unknown>;
|
|
21
53
|
}
|
|
@@ -30,6 +62,7 @@ interface EditorState {
|
|
|
30
62
|
layers: SerializedLayer[];
|
|
31
63
|
background?: string;
|
|
32
64
|
backgroundImage?: Record<string, unknown> | null;
|
|
65
|
+
backgroundImageOptions?: SerializedBackgroundImageOptions | null;
|
|
33
66
|
mockup?: MockupConfig | null;
|
|
34
67
|
}
|
|
35
68
|
interface EditorConfig {
|
|
@@ -54,6 +87,63 @@ interface ResizeOptions {
|
|
|
54
87
|
interface BackgroundImageOptions {
|
|
55
88
|
fit?: 'cover' | 'contain' | 'stretch';
|
|
56
89
|
opacity?: number;
|
|
90
|
+
/** Fabric/HTML image loading mode for remote assets. */
|
|
91
|
+
crossOrigin?: '' | 'anonymous' | 'use-credentials' | null;
|
|
92
|
+
/** Cancels the current load; never serialized. */
|
|
93
|
+
signal?: AbortSignal;
|
|
94
|
+
}
|
|
95
|
+
type SerializedBackgroundImageOptions = Omit<BackgroundImageOptions, 'signal'>;
|
|
96
|
+
interface SemanticExportOptions extends PngLikeExportOptions {
|
|
97
|
+
/** Document keeps full-canvas coordinates; source returns native image pixels. */
|
|
98
|
+
resolution?: 'document' | 'source';
|
|
99
|
+
}
|
|
100
|
+
interface MaskPoint {
|
|
101
|
+
x: number;
|
|
102
|
+
y: number;
|
|
103
|
+
}
|
|
104
|
+
interface MaskBrushOptions {
|
|
105
|
+
mode: 'add' | 'subtract';
|
|
106
|
+
size: number;
|
|
107
|
+
/** Soft-edge inner radius ratio, from 0 (soft) to 1 (hard). */
|
|
108
|
+
hardness: number;
|
|
109
|
+
}
|
|
110
|
+
type MaskRefinementPrompt = {
|
|
111
|
+
type: 'point';
|
|
112
|
+
x: number;
|
|
113
|
+
y: number;
|
|
114
|
+
label: 'foreground' | 'background';
|
|
115
|
+
} | {
|
|
116
|
+
type: 'rectangle';
|
|
117
|
+
left: number;
|
|
118
|
+
top: number;
|
|
119
|
+
width: number;
|
|
120
|
+
height: number;
|
|
121
|
+
} | {
|
|
122
|
+
type: 'text';
|
|
123
|
+
value: string;
|
|
124
|
+
};
|
|
125
|
+
interface MaskRefinementRequest {
|
|
126
|
+
mask: string;
|
|
127
|
+
width: number;
|
|
128
|
+
height: number;
|
|
129
|
+
prompts: MaskRefinementPrompt[];
|
|
130
|
+
}
|
|
131
|
+
interface MaskRefinementResult {
|
|
132
|
+
dataUrl: string;
|
|
133
|
+
width?: number;
|
|
134
|
+
height?: number;
|
|
135
|
+
metadata?: Record<string, unknown>;
|
|
136
|
+
}
|
|
137
|
+
interface MaskRefinementProvider {
|
|
138
|
+
refine(request: MaskRefinementRequest, options?: {
|
|
139
|
+
signal?: AbortSignal;
|
|
140
|
+
onProgress?: (progress: number) => void;
|
|
141
|
+
}): Promise<MaskRefinementResult>;
|
|
142
|
+
}
|
|
143
|
+
/** Kept structural to avoid coupling the public types module to export.ts. */
|
|
144
|
+
interface PngLikeExportOptions {
|
|
145
|
+
multiplier?: number;
|
|
146
|
+
quality?: number;
|
|
57
147
|
}
|
|
58
148
|
interface DpiIssue {
|
|
59
149
|
layerId: string;
|
|
@@ -303,6 +393,19 @@ interface MockupOverlay {
|
|
|
303
393
|
blendMode?: MockupBlendMode;
|
|
304
394
|
opacity?: number;
|
|
305
395
|
}
|
|
396
|
+
type MockupDisplacementChannel = 'red' | 'green' | 'blue' | 'alpha';
|
|
397
|
+
interface MockupDisplacement {
|
|
398
|
+
/** Channel-map image cover-fitted to the mockup canvas. A value of 128 is neutral. */
|
|
399
|
+
image: string;
|
|
400
|
+
/** Maximum horizontal displacement in document pixels. Defaults to 10. */
|
|
401
|
+
scaleX?: number;
|
|
402
|
+
/** Maximum vertical displacement in document pixels. Defaults to 10. */
|
|
403
|
+
scaleY?: number;
|
|
404
|
+
/** Map channel controlling horizontal displacement. Defaults to red. */
|
|
405
|
+
channelX?: MockupDisplacementChannel;
|
|
406
|
+
/** Map channel controlling vertical displacement. Defaults to green. */
|
|
407
|
+
channelY?: MockupDisplacementChannel;
|
|
408
|
+
}
|
|
306
409
|
interface MockupConfig {
|
|
307
410
|
/** Garment/product image rendered behind the design. */
|
|
308
411
|
image: string;
|
|
@@ -312,6 +415,8 @@ interface MockupConfig {
|
|
|
312
415
|
clipToPrintArea?: boolean;
|
|
313
416
|
designBlendMode?: MockupBlendMode;
|
|
314
417
|
designOpacity?: number;
|
|
418
|
+
/** Optional channel-map warp applied to the design before compositing. */
|
|
419
|
+
displacement?: MockupDisplacement;
|
|
315
420
|
/** Optional product lighting/shadow pass rendered after the design. */
|
|
316
421
|
overlay?: MockupOverlay;
|
|
317
422
|
}
|
|
@@ -380,4 +485,4 @@ interface EditorEvents {
|
|
|
380
485
|
};
|
|
381
486
|
}
|
|
382
487
|
|
|
383
|
-
export { type
|
|
488
|
+
export { type TemplateParameter as $, type MockupOverlay as A, type BackgroundImageOptions as B, type CanvasSizePreset as C, DEFAULT_PATTERN_CONFIG as D, type EditorConfig as E, type FileAdapter as F, type MockupPrintArea as G, type PatternLocks as H, type ImageAdjustments as I, type PatternSourceResolver as J, type PatternState as K, type LayerData as L, type MaskBrushOptions as M, type NormalizedLayerPosition as N, type PositioningAdapter as O, type PatternConfig as P, type PrintifyPositioning as Q, type ProjectPage as R, type ProjectState as S, type ResizeOptions as T, type SemanticExportOptions as U, type SerializedBackgroundImageOptions as V, type SerializedLayer as W, type ShapeMaskPresetId as X, type ShapePlugin as Y, type SvgExportOptions as Z, type TemplateDefinition as _, type DpiIssue as a, type TextCurveConfig as a0, type TextureMaskPresetId as a1, type TileMode as a2, type Unit as a3, 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 LayerShadowConfig as l, type LayerType as m, type LicenseConfig as n, type LicensePayload as o, type LicenseStatus as p, type MaskPoint as q, type MaskPresetId as r, type MaskRefinementPrompt as s, type MaskRefinementProvider as t, type MaskRefinementRequest as u, type MaskRefinementResult as v, type MockupBlendMode as w, type MockupConfig as x, type MockupDisplacement as y, type MockupDisplacementChannel 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,8 +14,40 @@ interface LayerData {
|
|
|
14
14
|
}
|
|
15
15
|
interface LayerMeta {
|
|
16
16
|
pattern?: PatternState;
|
|
17
|
+
mask?: {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
revision: number;
|
|
21
|
+
};
|
|
22
|
+
/** Preset silhouette or texture the layer is clipped to. */
|
|
23
|
+
maskPreset?: MaskPresetId;
|
|
24
|
+
/** Baseline curve applied to a text layer. */
|
|
25
|
+
curve?: TextCurveConfig;
|
|
26
|
+
/** Text box width authored before a curve widened it, in px. */
|
|
27
|
+
curveWidth?: number;
|
|
28
|
+
/** Layer resizes uniformly: single-axis handles are hidden. */
|
|
29
|
+
lockAspect?: boolean;
|
|
17
30
|
[key: string]: unknown;
|
|
18
31
|
}
|
|
32
|
+
interface TextCurveConfig {
|
|
33
|
+
/**
|
|
34
|
+
* Baseline bend, −100…100. Positive arcs the run upward (a rainbow),
|
|
35
|
+
* negative into a valley; ±100 closes it into a full ring. 0 is straight.
|
|
36
|
+
*/
|
|
37
|
+
arc: number;
|
|
38
|
+
/** Sine-wave amplitude, 0…100, as a share of the font size. Ignored when arced. */
|
|
39
|
+
wave: number;
|
|
40
|
+
}
|
|
41
|
+
type ShapeMaskPresetId = 'circle' | 'square' | 'triangle' | 'star' | 'heart' | 'octagram' | 'arch' | 'zigzag';
|
|
42
|
+
type TextureMaskPresetId = 'vignette' | 'halftone' | 'spray' | 'grunge' | 'torn' | 'band';
|
|
43
|
+
type MaskPresetId = ShapeMaskPresetId | TextureMaskPresetId;
|
|
44
|
+
interface LayerShadowConfig {
|
|
45
|
+
enabled: boolean;
|
|
46
|
+
color: string;
|
|
47
|
+
blur: number;
|
|
48
|
+
offsetX: number;
|
|
49
|
+
offsetY: number;
|
|
50
|
+
}
|
|
19
51
|
interface SerializedLayer extends LayerData {
|
|
20
52
|
fabricObject: Record<string, unknown>;
|
|
21
53
|
}
|
|
@@ -30,6 +62,7 @@ interface EditorState {
|
|
|
30
62
|
layers: SerializedLayer[];
|
|
31
63
|
background?: string;
|
|
32
64
|
backgroundImage?: Record<string, unknown> | null;
|
|
65
|
+
backgroundImageOptions?: SerializedBackgroundImageOptions | null;
|
|
33
66
|
mockup?: MockupConfig | null;
|
|
34
67
|
}
|
|
35
68
|
interface EditorConfig {
|
|
@@ -54,6 +87,63 @@ interface ResizeOptions {
|
|
|
54
87
|
interface BackgroundImageOptions {
|
|
55
88
|
fit?: 'cover' | 'contain' | 'stretch';
|
|
56
89
|
opacity?: number;
|
|
90
|
+
/** Fabric/HTML image loading mode for remote assets. */
|
|
91
|
+
crossOrigin?: '' | 'anonymous' | 'use-credentials' | null;
|
|
92
|
+
/** Cancels the current load; never serialized. */
|
|
93
|
+
signal?: AbortSignal;
|
|
94
|
+
}
|
|
95
|
+
type SerializedBackgroundImageOptions = Omit<BackgroundImageOptions, 'signal'>;
|
|
96
|
+
interface SemanticExportOptions extends PngLikeExportOptions {
|
|
97
|
+
/** Document keeps full-canvas coordinates; source returns native image pixels. */
|
|
98
|
+
resolution?: 'document' | 'source';
|
|
99
|
+
}
|
|
100
|
+
interface MaskPoint {
|
|
101
|
+
x: number;
|
|
102
|
+
y: number;
|
|
103
|
+
}
|
|
104
|
+
interface MaskBrushOptions {
|
|
105
|
+
mode: 'add' | 'subtract';
|
|
106
|
+
size: number;
|
|
107
|
+
/** Soft-edge inner radius ratio, from 0 (soft) to 1 (hard). */
|
|
108
|
+
hardness: number;
|
|
109
|
+
}
|
|
110
|
+
type MaskRefinementPrompt = {
|
|
111
|
+
type: 'point';
|
|
112
|
+
x: number;
|
|
113
|
+
y: number;
|
|
114
|
+
label: 'foreground' | 'background';
|
|
115
|
+
} | {
|
|
116
|
+
type: 'rectangle';
|
|
117
|
+
left: number;
|
|
118
|
+
top: number;
|
|
119
|
+
width: number;
|
|
120
|
+
height: number;
|
|
121
|
+
} | {
|
|
122
|
+
type: 'text';
|
|
123
|
+
value: string;
|
|
124
|
+
};
|
|
125
|
+
interface MaskRefinementRequest {
|
|
126
|
+
mask: string;
|
|
127
|
+
width: number;
|
|
128
|
+
height: number;
|
|
129
|
+
prompts: MaskRefinementPrompt[];
|
|
130
|
+
}
|
|
131
|
+
interface MaskRefinementResult {
|
|
132
|
+
dataUrl: string;
|
|
133
|
+
width?: number;
|
|
134
|
+
height?: number;
|
|
135
|
+
metadata?: Record<string, unknown>;
|
|
136
|
+
}
|
|
137
|
+
interface MaskRefinementProvider {
|
|
138
|
+
refine(request: MaskRefinementRequest, options?: {
|
|
139
|
+
signal?: AbortSignal;
|
|
140
|
+
onProgress?: (progress: number) => void;
|
|
141
|
+
}): Promise<MaskRefinementResult>;
|
|
142
|
+
}
|
|
143
|
+
/** Kept structural to avoid coupling the public types module to export.ts. */
|
|
144
|
+
interface PngLikeExportOptions {
|
|
145
|
+
multiplier?: number;
|
|
146
|
+
quality?: number;
|
|
57
147
|
}
|
|
58
148
|
interface DpiIssue {
|
|
59
149
|
layerId: string;
|
|
@@ -303,6 +393,19 @@ interface MockupOverlay {
|
|
|
303
393
|
blendMode?: MockupBlendMode;
|
|
304
394
|
opacity?: number;
|
|
305
395
|
}
|
|
396
|
+
type MockupDisplacementChannel = 'red' | 'green' | 'blue' | 'alpha';
|
|
397
|
+
interface MockupDisplacement {
|
|
398
|
+
/** Channel-map image cover-fitted to the mockup canvas. A value of 128 is neutral. */
|
|
399
|
+
image: string;
|
|
400
|
+
/** Maximum horizontal displacement in document pixels. Defaults to 10. */
|
|
401
|
+
scaleX?: number;
|
|
402
|
+
/** Maximum vertical displacement in document pixels. Defaults to 10. */
|
|
403
|
+
scaleY?: number;
|
|
404
|
+
/** Map channel controlling horizontal displacement. Defaults to red. */
|
|
405
|
+
channelX?: MockupDisplacementChannel;
|
|
406
|
+
/** Map channel controlling vertical displacement. Defaults to green. */
|
|
407
|
+
channelY?: MockupDisplacementChannel;
|
|
408
|
+
}
|
|
306
409
|
interface MockupConfig {
|
|
307
410
|
/** Garment/product image rendered behind the design. */
|
|
308
411
|
image: string;
|
|
@@ -312,6 +415,8 @@ interface MockupConfig {
|
|
|
312
415
|
clipToPrintArea?: boolean;
|
|
313
416
|
designBlendMode?: MockupBlendMode;
|
|
314
417
|
designOpacity?: number;
|
|
418
|
+
/** Optional channel-map warp applied to the design before compositing. */
|
|
419
|
+
displacement?: MockupDisplacement;
|
|
315
420
|
/** Optional product lighting/shadow pass rendered after the design. */
|
|
316
421
|
overlay?: MockupOverlay;
|
|
317
422
|
}
|
|
@@ -380,4 +485,4 @@ interface EditorEvents {
|
|
|
380
485
|
};
|
|
381
486
|
}
|
|
382
487
|
|
|
383
|
-
export { type
|
|
488
|
+
export { type TemplateParameter as $, type MockupOverlay as A, type BackgroundImageOptions as B, type CanvasSizePreset as C, DEFAULT_PATTERN_CONFIG as D, type EditorConfig as E, type FileAdapter as F, type MockupPrintArea as G, type PatternLocks as H, type ImageAdjustments as I, type PatternSourceResolver as J, type PatternState as K, type LayerData as L, type MaskBrushOptions as M, type NormalizedLayerPosition as N, type PositioningAdapter as O, type PatternConfig as P, type PrintifyPositioning as Q, type ProjectPage as R, type ProjectState as S, type ResizeOptions as T, type SemanticExportOptions as U, type SerializedBackgroundImageOptions as V, type SerializedLayer as W, type ShapeMaskPresetId as X, type ShapePlugin as Y, type SvgExportOptions as Z, type TemplateDefinition as _, type DpiIssue as a, type TextCurveConfig as a0, type TextureMaskPresetId as a1, type TileMode as a2, type Unit as a3, 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 LayerShadowConfig as l, type LayerType as m, type LicenseConfig as n, type LicensePayload as o, type LicenseStatus as p, type MaskPoint as q, type MaskPresetId as r, type MaskRefinementPrompt as s, type MaskRefinementProvider as t, type MaskRefinementRequest as u, type MaskRefinementResult as v, type MockupBlendMode as w, type MockupConfig as x, type MockupDisplacement as y, type MockupDisplacementChannel as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@overtone-art/canvas-editor-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
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,44 @@
|
|
|
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/node": "^22.12.0",
|
|
68
|
+
"@types/fontkit": "^2.0.9",
|
|
69
|
+
"@types/opentype.js": "^1.3.10",
|
|
70
|
+
"@types/pdfkit": "^0.17.6",
|
|
71
|
+
"@types/svg-to-pdfkit": "^0.1.4",
|
|
72
|
+
"fontkit": "^2.0.4",
|
|
73
|
+
"opentype.js": "^1.3.4",
|
|
74
|
+
"pdf-lib": "^1.17.1",
|
|
75
|
+
"pdfkit": "^0.19.1",
|
|
76
|
+
"sharp": "^0.35.0",
|
|
77
|
+
"svg-to-pdfkit": "^0.1.8",
|
|
43
78
|
"tsup": "^8.4.0",
|
|
44
79
|
"typescript": "^5.8.2",
|
|
45
80
|
"vitest": "^3.2.1",
|
|
@@ -49,9 +84,11 @@
|
|
|
49
84
|
"scripts": {
|
|
50
85
|
"build": "tsup",
|
|
51
86
|
"dev": "tsup --watch",
|
|
52
|
-
"lint": "eslint src/",
|
|
53
|
-
"type-check": "tsc --noEmit",
|
|
87
|
+
"lint": "eslint src/ acceptance/ vitest.config.ts vitest.acceptance.config.ts",
|
|
88
|
+
"type-check": "tsc --noEmit && tsc --noEmit -p tsconfig.acceptance.json",
|
|
54
89
|
"test": "vitest run",
|
|
55
|
-
"test:
|
|
90
|
+
"test:coverage": "vitest run --coverage",
|
|
91
|
+
"test:watch": "vitest",
|
|
92
|
+
"acceptance:print": "vitest run --config vitest.acceptance.config.ts acceptance/generate-print.test.ts"
|
|
56
93
|
}
|
|
57
94
|
}
|
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":[]}
|