@overtone-art/canvas-editor-core 0.2.6 → 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.
@@ -0,0 +1,461 @@
1
+ import { FabricObject } from 'fabric';
2
+
3
+ type Unit = 'px' | 'mm' | 'in';
4
+ type LayerType = 'image' | 'mask' | 'text' | 'shape' | 'template' | 'group';
5
+ interface LayerData {
6
+ id: string;
7
+ type: LayerType;
8
+ name: string;
9
+ visible: boolean;
10
+ locked: boolean;
11
+ opacity: number;
12
+ /** Non-fabric metadata persisted with the layer (pattern config, etc). */
13
+ meta?: LayerMeta;
14
+ }
15
+ interface LayerMeta {
16
+ pattern?: PatternState;
17
+ mask?: {
18
+ width: number;
19
+ height: number;
20
+ revision: number;
21
+ };
22
+ [key: string]: unknown;
23
+ }
24
+ interface SerializedLayer extends LayerData {
25
+ fabricObject: Record<string, unknown>;
26
+ }
27
+ interface EditorState {
28
+ version: string;
29
+ canvas: {
30
+ width: number;
31
+ height: number;
32
+ unit?: Unit;
33
+ dpi?: number;
34
+ };
35
+ layers: SerializedLayer[];
36
+ background?: string;
37
+ backgroundImage?: Record<string, unknown> | null;
38
+ backgroundImageOptions?: SerializedBackgroundImageOptions | null;
39
+ mockup?: MockupConfig | null;
40
+ }
41
+ interface EditorConfig {
42
+ width: number;
43
+ height: number;
44
+ backgroundColor?: string;
45
+ fileAdapter?: FileAdapter;
46
+ imageProvider?: ImageProvider;
47
+ preserveObjectStacking?: boolean;
48
+ unit?: Unit;
49
+ dpi?: number;
50
+ /** Resolve a non-CORS image through a trusted proxy or same-origin store for pattern rendering. */
51
+ patternSourceResolver?: PatternSourceResolver;
52
+ fonts?: FontDefinition[];
53
+ license?: LicenseConfig;
54
+ }
55
+ type ExportFormat = 'png' | 'jpeg' | 'webp' | 'svg' | 'json';
56
+ interface ResizeOptions {
57
+ /** Scale layer positions and dimensions with the canvas. Defaults to true. */
58
+ scaleContent?: boolean;
59
+ }
60
+ interface BackgroundImageOptions {
61
+ fit?: 'cover' | 'contain' | 'stretch';
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;
120
+ }
121
+ interface DpiIssue {
122
+ layerId: string;
123
+ layerName: string;
124
+ effectiveDpi: number;
125
+ minimumDpi: number;
126
+ }
127
+ interface ImageAdjustments {
128
+ /** Range -1 to 1. */
129
+ brightness?: number;
130
+ /** Range -1 to 1. */
131
+ contrast?: number;
132
+ /** Range -1 to 1. */
133
+ saturation?: number;
134
+ /** Range 0 to 1. */
135
+ blur?: number;
136
+ }
137
+ type PatternSourceResolver = (source: string) => Promise<string>;
138
+ interface FontDefinition {
139
+ family: string;
140
+ /** URL, data URL, or a complete CSS FontFace source such as `local(...)`. */
141
+ source: string;
142
+ weight?: string;
143
+ style?: string;
144
+ display?: FontDisplay;
145
+ }
146
+ interface SvgExportOptions {
147
+ /** Embed registered URL/data fonts into the SVG. Defaults to true. */
148
+ embedFonts?: boolean;
149
+ }
150
+ interface LicensePayload {
151
+ id: string;
152
+ domains: string[];
153
+ expiresAt?: string;
154
+ features?: string[];
155
+ }
156
+ type LicenseStatus = {
157
+ state: 'community' | 'exempt' | 'checking';
158
+ payload: null;
159
+ } | {
160
+ state: 'valid';
161
+ payload: LicensePayload;
162
+ } | {
163
+ state: 'invalid' | 'expired' | 'domain-mismatch';
164
+ payload: LicensePayload | null;
165
+ };
166
+ interface LicenseConfig {
167
+ key?: string;
168
+ /** Offline signature decoder/verifier supplied by the commercial distribution. */
169
+ verifyOffline?: (key: string) => Promise<LicensePayload | null> | LicensePayload | null;
170
+ hostname?: string;
171
+ environment?: 'development' | 'test' | 'production';
172
+ onUsage?: (event: {
173
+ name: string;
174
+ at: string;
175
+ licenseId?: string;
176
+ }) => void;
177
+ }
178
+ interface CanvasSizePreset {
179
+ id: string;
180
+ name: string;
181
+ width: number;
182
+ height: number;
183
+ unit: Unit;
184
+ dpi: number;
185
+ }
186
+ interface FileAdapter {
187
+ save(data: Blob | string, filename: string, format: ExportFormat): Promise<string>;
188
+ }
189
+ interface ImageProviderResult {
190
+ id?: string;
191
+ url: string;
192
+ width: number;
193
+ height: number;
194
+ previewUrl?: string;
195
+ thumbnailUrl?: string;
196
+ alt?: string;
197
+ sourceUrl?: string;
198
+ attribution?: ImageAttribution;
199
+ /** Provider event endpoint retained for use tracking; never render this as an image. */
200
+ trackingUrl?: string;
201
+ }
202
+ interface ImageAttribution {
203
+ name: string;
204
+ url: string;
205
+ provider?: string;
206
+ }
207
+ interface ImageSearchOptions {
208
+ page?: number;
209
+ perPage?: number;
210
+ orientation?: 'landscape' | 'portrait' | 'squarish';
211
+ orderBy?: 'relevant' | 'latest';
212
+ contentFilter?: 'low' | 'high';
213
+ }
214
+ interface ImageSearchResult {
215
+ items: ImageProviderResult[];
216
+ page: number;
217
+ total: number;
218
+ totalPages: number;
219
+ }
220
+ interface ImageProvider {
221
+ upload?(file: File): Promise<ImageProviderResult>;
222
+ browse?(): Promise<ImageProviderResult | null>;
223
+ search?(query: string, options?: ImageSearchOptions): Promise<ImageSearchResult>;
224
+ /** Called immediately before a provider asset is inserted into the design. */
225
+ trackUse?(image: ImageProviderResult): Promise<void>;
226
+ }
227
+ interface ShapePlugin {
228
+ name: string;
229
+ icon: string;
230
+ category?: string;
231
+ create(options?: Record<string, unknown>): FabricObject;
232
+ }
233
+ interface TemplateParameter {
234
+ key: string;
235
+ label: string;
236
+ type: 'text' | 'color' | 'number' | 'font';
237
+ default?: string | number;
238
+ }
239
+ interface TemplateDefinition {
240
+ id: string;
241
+ name: string;
242
+ category: string;
243
+ svg: string;
244
+ parameters: TemplateParameter[];
245
+ preview?: string;
246
+ }
247
+ interface PrintifyPositioning {
248
+ x: number;
249
+ y: number;
250
+ scale: number;
251
+ angle: number;
252
+ }
253
+ interface NormalizedLayerPosition {
254
+ layerId: string;
255
+ name: string;
256
+ type: LayerType;
257
+ centerX: number;
258
+ centerY: number;
259
+ width: number;
260
+ height: number;
261
+ scaleX: number;
262
+ scaleY: number;
263
+ angle: number;
264
+ }
265
+ interface PositioningAdapter<T> {
266
+ readonly provider: string;
267
+ map(positions: NormalizedLayerPosition[], canvas: {
268
+ width: number;
269
+ height: number;
270
+ }): T;
271
+ }
272
+ interface ProjectPage {
273
+ id: string;
274
+ name: string;
275
+ state: EditorState;
276
+ }
277
+ interface ProjectState {
278
+ version: '1.0.0';
279
+ activePageId: string;
280
+ pages: ProjectPage[];
281
+ }
282
+ type TileMode = 'grid' | 'brick-horizontal' | 'brick-vertical';
283
+ interface PatternConfig {
284
+ mode: TileMode;
285
+ /**
286
+ * Tile size as a % of the source image's on-canvas size (100 = natural).
287
+ * Smaller values pack more repeats across the print area; larger values make
288
+ * fewer, bigger tiles. Independent of how the source layer was placed.
289
+ */
290
+ scale: number;
291
+ /** Extra horizontal gap between tiles, as % of tile width. */
292
+ horizontalSpacing: number;
293
+ /** Extra vertical gap between tiles, as % of tile height. */
294
+ verticalSpacing: number;
295
+ /** Rotation of the whole pattern, in degrees. */
296
+ angle: number;
297
+ /** Brick row/column shift, as % of tile size. */
298
+ horizontalOffset: number;
299
+ /**
300
+ * Phase shift of the whole tile grid inside the print area, as % of tile
301
+ * width. The pattern object itself is pinned to the print area (it can't be
302
+ * dragged — that would expose bare edges), so this is how the tiling is
303
+ * nudged into alignment. Clamped to ±100% so coverage is never broken.
304
+ */
305
+ offsetX: number;
306
+ /** Phase shift of the tile grid, as % of tile height. See {@link offsetX}. */
307
+ offsetY: number;
308
+ /** Added rotation per horizontal step, in degrees. */
309
+ rotationStepH: number;
310
+ /** Added rotation per vertical step, in degrees. */
311
+ rotationStepV: number;
312
+ }
313
+ /** Fabric interaction flags frozen while a layer is rendered as a pattern. */
314
+ interface PatternLocks {
315
+ lockMovementX: boolean;
316
+ lockMovementY: boolean;
317
+ lockScalingX: boolean;
318
+ lockScalingY: boolean;
319
+ lockRotation: boolean;
320
+ hasControls: boolean;
321
+ }
322
+ /** Persisted on a layer's meta while it is rendered as a repeating pattern. */
323
+ interface PatternState {
324
+ config: PatternConfig;
325
+ /** Original (pre-pattern) image source, as a durable data URL when possible. */
326
+ originalSrc: string;
327
+ /**
328
+ * Serialized `clipPath` the image carried before it became a pattern, or null
329
+ * if it had none. A pattern must fill the whole print area, so any pre-pattern
330
+ * clip (e.g. a template crop sized to the original box) is stripped while the
331
+ * pattern is on and re-applied when it is turned off. Persisted so it survives
332
+ * serialization / reload / undo.
333
+ */
334
+ originalClip?: Record<string, unknown> | null;
335
+ /**
336
+ * Interaction flags the object carried before it became a pattern. A pattern
337
+ * fills the whole print area, so while it is on the object is locked in place
338
+ * (a drag/resize would slide the tiled bitmap off the area and leave a bare
339
+ * band); the captured flags are restored when the pattern is turned off.
340
+ */
341
+ originalLocks?: PatternLocks;
342
+ /** Original fabric transform, restored when the pattern is turned off. */
343
+ original: {
344
+ left: number;
345
+ top: number;
346
+ scaleX: number;
347
+ scaleY: number;
348
+ width: number;
349
+ height: number;
350
+ angle: number;
351
+ cropX: number;
352
+ cropY: number;
353
+ };
354
+ }
355
+ declare const DEFAULT_PATTERN_CONFIG: PatternConfig;
356
+ interface MockupPrintArea {
357
+ left: number;
358
+ top: number;
359
+ width: number;
360
+ height: number;
361
+ }
362
+ type MockupBlendMode = 'normal' | 'multiply' | 'screen' | 'overlay' | 'soft-light' | 'hard-light';
363
+ interface MockupOverlay {
364
+ /** Lighting, texture, or shadow image drawn above the design. */
365
+ image: string;
366
+ blendMode?: MockupBlendMode;
367
+ opacity?: number;
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
+ }
382
+ interface MockupConfig {
383
+ /** Garment/product image rendered behind the design. */
384
+ image: string;
385
+ /** Print-area rectangle, in canvas pixels. Drawn as a guide. */
386
+ printArea?: MockupPrintArea;
387
+ /** Clip the design to printArea. Defaults to true when printArea exists. */
388
+ clipToPrintArea?: boolean;
389
+ designBlendMode?: MockupBlendMode;
390
+ designOpacity?: number;
391
+ /** Optional channel-map warp applied to the design before compositing. */
392
+ displacement?: MockupDisplacement;
393
+ /** Optional product lighting/shadow pass rendered after the design. */
394
+ overlay?: MockupOverlay;
395
+ }
396
+ interface EditorEvents {
397
+ 'layer:added': {
398
+ layer: LayerData;
399
+ };
400
+ 'layer:removed': {
401
+ layerId: string;
402
+ };
403
+ 'layer:selected': {
404
+ layerId: string | null;
405
+ };
406
+ 'layer:modified': {
407
+ layerId: string;
408
+ };
409
+ 'layer:reordered': {
410
+ layerIds: string[];
411
+ };
412
+ 'layers:changed': {
413
+ layers: LayerData[];
414
+ };
415
+ 'selection:changed': {
416
+ selected: string[];
417
+ };
418
+ 'history:changed': {
419
+ canUndo: boolean;
420
+ canRedo: boolean;
421
+ };
422
+ 'history:snapshot': {
423
+ bytes: number;
424
+ totalBytes: number;
425
+ entries: number;
426
+ };
427
+ 'zoom:changed': {
428
+ zoom: number;
429
+ };
430
+ 'snap:changed': {
431
+ enabled: boolean;
432
+ };
433
+ 'crop:changed': {
434
+ active: boolean;
435
+ layerId: string | null;
436
+ };
437
+ 'mockup:changed': {
438
+ mockup: MockupConfig | null;
439
+ };
440
+ 'canvas:modified': Record<string, never>;
441
+ 'project:changed': {
442
+ activePageId: string;
443
+ pages: Array<{
444
+ id: string;
445
+ name: string;
446
+ }>;
447
+ };
448
+ 'export:start': {
449
+ format: string;
450
+ };
451
+ 'export:complete': {
452
+ format: string;
453
+ url?: string;
454
+ };
455
+ error: {
456
+ message: string;
457
+ error?: unknown;
458
+ };
459
+ }
460
+
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,9 +1,11 @@
1
1
  {
2
2
  "name": "@overtone-art/canvas-editor-core",
3
- "version": "0.2.6",
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",
7
+ "unpkg": "./dist/index.global.js",
8
+ "jsdelivr": "./dist/index.global.js",
7
9
  "types": "./dist/index.d.ts",
8
10
  "exports": {
9
11
  ".": {
@@ -15,6 +17,16 @@
15
17
  "types": "./dist/index.d.ts",
16
18
  "default": "./dist/index.js"
17
19
  }
20
+ },
21
+ "./node": {
22
+ "import": {
23
+ "types": "./dist/node.d.mts",
24
+ "default": "./dist/node.mjs"
25
+ },
26
+ "require": {
27
+ "types": "./dist/node.d.ts",
28
+ "default": "./dist/node.js"
29
+ }
18
30
  }
19
31
  },
20
32
  "files": [
@@ -25,9 +37,43 @@
25
37
  },
26
38
  "dependencies": {
27
39
  "fabric": "^7.4.0",
28
- "nanoid": "^5.1.5"
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
+ }
29
65
  },
30
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",
31
77
  "tsup": "^8.4.0",
32
78
  "typescript": "^5.8.2",
33
79
  "vitest": "^3.2.1",
@@ -40,6 +86,7 @@
40
86
  "lint": "eslint src/",
41
87
  "type-check": "tsc --noEmit",
42
88
  "test": "vitest run",
89
+ "test:coverage": "vitest run --coverage",
43
90
  "test:watch": "vitest"
44
91
  }
45
92
  }