@overtone-art/canvas-editor-core 0.2.1 → 0.2.3
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/index.d.mts +34 -1
- package/dist/index.d.ts +34 -1
- package/dist/index.js +45 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +42 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -81,6 +81,12 @@ interface PrintifyPositioning {
|
|
|
81
81
|
type TileMode = 'grid' | 'brick-horizontal' | 'brick-vertical';
|
|
82
82
|
interface PatternConfig {
|
|
83
83
|
mode: TileMode;
|
|
84
|
+
/**
|
|
85
|
+
* Tile size as a % of the source image's on-canvas size (100 = natural).
|
|
86
|
+
* Smaller values pack more repeats across the print area; larger values make
|
|
87
|
+
* fewer, bigger tiles. Independent of how the source layer was placed.
|
|
88
|
+
*/
|
|
89
|
+
scale: number;
|
|
84
90
|
/** Extra horizontal gap between tiles, as % of tile width. */
|
|
85
91
|
horizontalSpacing: number;
|
|
86
92
|
/** Extra vertical gap between tiles, as % of tile height. */
|
|
@@ -99,6 +105,14 @@ interface PatternState {
|
|
|
99
105
|
config: PatternConfig;
|
|
100
106
|
/** Original (pre-pattern) image source, as a durable data URL when possible. */
|
|
101
107
|
originalSrc: string;
|
|
108
|
+
/**
|
|
109
|
+
* Serialized `clipPath` the image carried before it became a pattern, or null
|
|
110
|
+
* if it had none. A pattern must fill the whole print area, so any pre-pattern
|
|
111
|
+
* clip (e.g. a template crop sized to the original box) is stripped while the
|
|
112
|
+
* pattern is on and re-applied when it is turned off. Persisted so it survives
|
|
113
|
+
* serialization / reload / undo.
|
|
114
|
+
*/
|
|
115
|
+
originalClip?: Record<string, unknown> | null;
|
|
102
116
|
/** Original fabric transform, restored when the pattern is turned off. */
|
|
103
117
|
original: {
|
|
104
118
|
left: number;
|
|
@@ -330,6 +344,25 @@ declare class PatternManager {
|
|
|
330
344
|
private enqueue;
|
|
331
345
|
private renderLayer;
|
|
332
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* Object placement for a pattern re-render.
|
|
349
|
+
*
|
|
350
|
+
* The FIRST enable pins the pattern to the print-area origin (0,0, unrotated) so
|
|
351
|
+
* it fills the whole area. A later config update (scale/angle/spacing slider)
|
|
352
|
+
* preserves the object's current placement — otherwise tweaking a slider would
|
|
353
|
+
* snap a pattern the user had dragged/rotated back to the top-left corner.
|
|
354
|
+
*
|
|
355
|
+
* Exported for unit testing.
|
|
356
|
+
*/
|
|
357
|
+
declare function resolvePatternPlacement(pin: boolean, current: {
|
|
358
|
+
left?: number;
|
|
359
|
+
top?: number;
|
|
360
|
+
angle?: number;
|
|
361
|
+
}): {
|
|
362
|
+
left: number;
|
|
363
|
+
top: number;
|
|
364
|
+
angle: number;
|
|
365
|
+
};
|
|
333
366
|
/** Clear the decoded-image cache (e.g. on teardown). Exported for completeness. */
|
|
334
367
|
declare function clearPatternImageCache(): void;
|
|
335
368
|
/**
|
|
@@ -460,4 +493,4 @@ declare function round2(v: number): number;
|
|
|
460
493
|
declare function serializeEditor(editor: CanvasEditor): EditorState;
|
|
461
494
|
declare function deserializeEditor(editor: CanvasEditor, state: EditorState): Promise<void>;
|
|
462
495
|
|
|
463
|
-
export { CanvasEditor, CropController, DEFAULT_PATTERN_CONFIG, type EditorConfig, type EditorEvents, type EditorState, EventEmitter, type ExportFormat, type FileAdapter, HistoryManager, type ImageProvider, type ImageProviderResult, Layer, type LayerData, LayerManager, type LayerMeta, type LayerType, type MockupConfig, type MockupPrintArea, type PatternConfig, PatternManager, type PatternState, type PngExportOptions, type PrintifyPositioning, type SerializedLayer, type ShapePlugin, SnapManager, type TemplateDefinition, type TemplateParameter, type TileMode, type TilePlacement, type Unit, UnitConverter, buildPatternDataURL, clamp, clearPatternImageCache, computeTilePositions, deserializeEditor, drawTiles, exportDataURL, exportPNG, exportSVG, generateId, round2, serializeEditor };
|
|
496
|
+
export { CanvasEditor, CropController, DEFAULT_PATTERN_CONFIG, type EditorConfig, type EditorEvents, type EditorState, EventEmitter, type ExportFormat, type FileAdapter, HistoryManager, type ImageProvider, type ImageProviderResult, Layer, type LayerData, LayerManager, type LayerMeta, type LayerType, type MockupConfig, type MockupPrintArea, type PatternConfig, PatternManager, type PatternState, type PngExportOptions, type PrintifyPositioning, type SerializedLayer, type ShapePlugin, SnapManager, type TemplateDefinition, type TemplateParameter, type TileMode, type TilePlacement, type Unit, UnitConverter, buildPatternDataURL, clamp, clearPatternImageCache, computeTilePositions, deserializeEditor, drawTiles, exportDataURL, exportPNG, exportSVG, generateId, resolvePatternPlacement, round2, serializeEditor };
|
package/dist/index.d.ts
CHANGED
|
@@ -81,6 +81,12 @@ interface PrintifyPositioning {
|
|
|
81
81
|
type TileMode = 'grid' | 'brick-horizontal' | 'brick-vertical';
|
|
82
82
|
interface PatternConfig {
|
|
83
83
|
mode: TileMode;
|
|
84
|
+
/**
|
|
85
|
+
* Tile size as a % of the source image's on-canvas size (100 = natural).
|
|
86
|
+
* Smaller values pack more repeats across the print area; larger values make
|
|
87
|
+
* fewer, bigger tiles. Independent of how the source layer was placed.
|
|
88
|
+
*/
|
|
89
|
+
scale: number;
|
|
84
90
|
/** Extra horizontal gap between tiles, as % of tile width. */
|
|
85
91
|
horizontalSpacing: number;
|
|
86
92
|
/** Extra vertical gap between tiles, as % of tile height. */
|
|
@@ -99,6 +105,14 @@ interface PatternState {
|
|
|
99
105
|
config: PatternConfig;
|
|
100
106
|
/** Original (pre-pattern) image source, as a durable data URL when possible. */
|
|
101
107
|
originalSrc: string;
|
|
108
|
+
/**
|
|
109
|
+
* Serialized `clipPath` the image carried before it became a pattern, or null
|
|
110
|
+
* if it had none. A pattern must fill the whole print area, so any pre-pattern
|
|
111
|
+
* clip (e.g. a template crop sized to the original box) is stripped while the
|
|
112
|
+
* pattern is on and re-applied when it is turned off. Persisted so it survives
|
|
113
|
+
* serialization / reload / undo.
|
|
114
|
+
*/
|
|
115
|
+
originalClip?: Record<string, unknown> | null;
|
|
102
116
|
/** Original fabric transform, restored when the pattern is turned off. */
|
|
103
117
|
original: {
|
|
104
118
|
left: number;
|
|
@@ -330,6 +344,25 @@ declare class PatternManager {
|
|
|
330
344
|
private enqueue;
|
|
331
345
|
private renderLayer;
|
|
332
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* Object placement for a pattern re-render.
|
|
349
|
+
*
|
|
350
|
+
* The FIRST enable pins the pattern to the print-area origin (0,0, unrotated) so
|
|
351
|
+
* it fills the whole area. A later config update (scale/angle/spacing slider)
|
|
352
|
+
* preserves the object's current placement — otherwise tweaking a slider would
|
|
353
|
+
* snap a pattern the user had dragged/rotated back to the top-left corner.
|
|
354
|
+
*
|
|
355
|
+
* Exported for unit testing.
|
|
356
|
+
*/
|
|
357
|
+
declare function resolvePatternPlacement(pin: boolean, current: {
|
|
358
|
+
left?: number;
|
|
359
|
+
top?: number;
|
|
360
|
+
angle?: number;
|
|
361
|
+
}): {
|
|
362
|
+
left: number;
|
|
363
|
+
top: number;
|
|
364
|
+
angle: number;
|
|
365
|
+
};
|
|
333
366
|
/** Clear the decoded-image cache (e.g. on teardown). Exported for completeness. */
|
|
334
367
|
declare function clearPatternImageCache(): void;
|
|
335
368
|
/**
|
|
@@ -460,4 +493,4 @@ declare function round2(v: number): number;
|
|
|
460
493
|
declare function serializeEditor(editor: CanvasEditor): EditorState;
|
|
461
494
|
declare function deserializeEditor(editor: CanvasEditor, state: EditorState): Promise<void>;
|
|
462
495
|
|
|
463
|
-
export { CanvasEditor, CropController, DEFAULT_PATTERN_CONFIG, type EditorConfig, type EditorEvents, type EditorState, EventEmitter, type ExportFormat, type FileAdapter, HistoryManager, type ImageProvider, type ImageProviderResult, Layer, type LayerData, LayerManager, type LayerMeta, type LayerType, type MockupConfig, type MockupPrintArea, type PatternConfig, PatternManager, type PatternState, type PngExportOptions, type PrintifyPositioning, type SerializedLayer, type ShapePlugin, SnapManager, type TemplateDefinition, type TemplateParameter, type TileMode, type TilePlacement, type Unit, UnitConverter, buildPatternDataURL, clamp, clearPatternImageCache, computeTilePositions, deserializeEditor, drawTiles, exportDataURL, exportPNG, exportSVG, generateId, round2, serializeEditor };
|
|
496
|
+
export { CanvasEditor, CropController, DEFAULT_PATTERN_CONFIG, type EditorConfig, type EditorEvents, type EditorState, EventEmitter, type ExportFormat, type FileAdapter, HistoryManager, type ImageProvider, type ImageProviderResult, Layer, type LayerData, LayerManager, type LayerMeta, type LayerType, type MockupConfig, type MockupPrintArea, type PatternConfig, PatternManager, type PatternState, type PngExportOptions, type PrintifyPositioning, type SerializedLayer, type ShapePlugin, SnapManager, type TemplateDefinition, type TemplateParameter, type TileMode, type TilePlacement, type Unit, UnitConverter, buildPatternDataURL, clamp, clearPatternImageCache, computeTilePositions, deserializeEditor, drawTiles, exportDataURL, exportPNG, exportSVG, generateId, resolvePatternPlacement, round2, serializeEditor };
|
package/dist/index.js
CHANGED
|
@@ -50,13 +50,14 @@ __export(index_exports, {
|
|
|
50
50
|
exportPNG: () => exportPNG,
|
|
51
51
|
exportSVG: () => exportSVG,
|
|
52
52
|
generateId: () => generateId,
|
|
53
|
+
resolvePatternPlacement: () => resolvePatternPlacement,
|
|
53
54
|
round2: () => round2,
|
|
54
55
|
serializeEditor: () => serializeEditor
|
|
55
56
|
});
|
|
56
57
|
module.exports = __toCommonJS(index_exports);
|
|
57
58
|
|
|
58
59
|
// src/editor.ts
|
|
59
|
-
var
|
|
60
|
+
var import_fabric4 = require("fabric");
|
|
60
61
|
|
|
61
62
|
// src/events.ts
|
|
62
63
|
var EventEmitter = class {
|
|
@@ -598,6 +599,8 @@ var CropController = class {
|
|
|
598
599
|
var STROKE2 = "#22c55e";
|
|
599
600
|
|
|
600
601
|
// src/pattern.ts
|
|
602
|
+
var import_fabric2 = require("fabric");
|
|
603
|
+
var MAX_TILES_PER_AXIS = 200;
|
|
601
604
|
var PatternManager = class {
|
|
602
605
|
constructor(canvas, layers, history) {
|
|
603
606
|
this.canvas = canvas;
|
|
@@ -626,9 +629,11 @@ var PatternManager = class {
|
|
|
626
629
|
const image = layer.fabricObject;
|
|
627
630
|
const firstEnable = !layer.meta.pattern;
|
|
628
631
|
if (!layer.meta.pattern) {
|
|
632
|
+
const clip = image.clipPath;
|
|
629
633
|
layer.meta.pattern = {
|
|
630
634
|
config,
|
|
631
635
|
originalSrc: elementToDataURL(image) ?? image.getSrc(),
|
|
636
|
+
originalClip: clip ? clip.toObject() : null,
|
|
632
637
|
original: {
|
|
633
638
|
left: image.left ?? 0,
|
|
634
639
|
top: image.top ?? 0,
|
|
@@ -645,7 +650,7 @@ var PatternManager = class {
|
|
|
645
650
|
layer.meta.pattern.config = config;
|
|
646
651
|
}
|
|
647
652
|
try {
|
|
648
|
-
await this.renderLayer(layer);
|
|
653
|
+
await this.renderLayer(layer, firstEnable);
|
|
649
654
|
} catch (err) {
|
|
650
655
|
if (firstEnable) delete layer.meta.pattern;
|
|
651
656
|
throw err;
|
|
@@ -672,6 +677,7 @@ var PatternManager = class {
|
|
|
672
677
|
cropY: state.original.cropY,
|
|
673
678
|
angle: state.original.angle
|
|
674
679
|
});
|
|
680
|
+
image.clipPath = state.originalClip ? (await import_fabric2.util.enlivenObjects([state.originalClip]))[0] : void 0;
|
|
675
681
|
image.setCoords();
|
|
676
682
|
delete layer.meta.pattern;
|
|
677
683
|
this.canvas.requestRenderAll();
|
|
@@ -688,14 +694,18 @@ var PatternManager = class {
|
|
|
688
694
|
);
|
|
689
695
|
return next;
|
|
690
696
|
}
|
|
691
|
-
async renderLayer(layer) {
|
|
697
|
+
async renderLayer(layer, pin) {
|
|
692
698
|
const state = layer.meta.pattern;
|
|
693
699
|
if (!state) return;
|
|
694
700
|
const image = layer.fabricObject;
|
|
695
701
|
const cw = this.canvas.getWidth();
|
|
696
702
|
const ch = this.canvas.getHeight();
|
|
697
|
-
const
|
|
698
|
-
const
|
|
703
|
+
const { left, top, angle } = resolvePatternPlacement(pin, image);
|
|
704
|
+
const scale = Math.max(1, state.config.scale ?? 100) / 100;
|
|
705
|
+
const diag = Math.sqrt(cw * cw + ch * ch);
|
|
706
|
+
const minTile = Math.max(2, diag / MAX_TILES_PER_AXIS);
|
|
707
|
+
const tileW = Math.max(minTile, state.original.width * state.original.scaleX * scale);
|
|
708
|
+
const tileH = Math.max(minTile, state.original.height * state.original.scaleY * scale);
|
|
699
709
|
const dataUrl = await buildPatternDataURL(
|
|
700
710
|
state.originalSrc,
|
|
701
711
|
state.config,
|
|
@@ -705,11 +715,30 @@ var PatternManager = class {
|
|
|
705
715
|
tileH
|
|
706
716
|
);
|
|
707
717
|
await image.setSrc(dataUrl);
|
|
708
|
-
image.set({
|
|
718
|
+
image.set({
|
|
719
|
+
left,
|
|
720
|
+
top,
|
|
721
|
+
scaleX: 1,
|
|
722
|
+
scaleY: 1,
|
|
723
|
+
width: cw,
|
|
724
|
+
height: ch,
|
|
725
|
+
cropX: 0,
|
|
726
|
+
cropY: 0,
|
|
727
|
+
angle
|
|
728
|
+
});
|
|
729
|
+
image.clipPath = void 0;
|
|
709
730
|
image.setCoords();
|
|
710
731
|
this.canvas.requestRenderAll();
|
|
711
732
|
}
|
|
712
733
|
};
|
|
734
|
+
function resolvePatternPlacement(pin, current) {
|
|
735
|
+
if (pin) return { left: 0, top: 0, angle: 0 };
|
|
736
|
+
return {
|
|
737
|
+
left: current.left ?? 0,
|
|
738
|
+
top: current.top ?? 0,
|
|
739
|
+
angle: current.angle ?? 0
|
|
740
|
+
};
|
|
741
|
+
}
|
|
713
742
|
function elementToDataURL(image) {
|
|
714
743
|
try {
|
|
715
744
|
const el = image.getElement();
|
|
@@ -770,9 +799,10 @@ async function buildPatternDataURL(src, config, targetW, targetH, baseW, baseH)
|
|
|
770
799
|
return off.toDataURL("image/png");
|
|
771
800
|
}
|
|
772
801
|
function computeTilePositions(config, targetW, targetH, baseW, baseH) {
|
|
773
|
-
const tileW = Math.max(1, baseW * (1 + config.horizontalSpacing / 100));
|
|
774
|
-
const tileH = Math.max(1, baseH * (1 + config.verticalSpacing / 100));
|
|
775
802
|
const diag = Math.sqrt(targetW * targetW + targetH * targetH);
|
|
803
|
+
const minTile = Math.max(1, diag / MAX_TILES_PER_AXIS);
|
|
804
|
+
const tileW = Math.max(minTile, baseW * (1 + config.horizontalSpacing / 100));
|
|
805
|
+
const tileH = Math.max(minTile, baseH * (1 + config.verticalSpacing / 100));
|
|
776
806
|
const cols = Math.ceil(diag / tileW) + 2;
|
|
777
807
|
const rows = Math.ceil(diag / tileH) + 2;
|
|
778
808
|
const halfCols = Math.ceil(cols / 2);
|
|
@@ -856,7 +886,7 @@ var UnitConverter = class {
|
|
|
856
886
|
};
|
|
857
887
|
|
|
858
888
|
// src/serialization.ts
|
|
859
|
-
var
|
|
889
|
+
var import_fabric3 = require("fabric");
|
|
860
890
|
var VERSION = "1.0.0";
|
|
861
891
|
function serializeEditor(editor) {
|
|
862
892
|
return {
|
|
@@ -887,7 +917,7 @@ async function deserializeEditor(editor, state) {
|
|
|
887
917
|
editor.canvas.requestRenderAll();
|
|
888
918
|
}
|
|
889
919
|
async function restoreLayer(editor, serialized) {
|
|
890
|
-
const objects = await
|
|
920
|
+
const objects = await import_fabric3.util.enlivenObjects([serialized.fabricObject]);
|
|
891
921
|
const fabricObject = objects[0];
|
|
892
922
|
const layer = editor.layers.add(serialized.type, fabricObject, serialized.name, serialized.id);
|
|
893
923
|
if (serialized.meta) {
|
|
@@ -949,7 +979,7 @@ var CanvasEditor = class {
|
|
|
949
979
|
const widthPx = this.units.toPixels(config.width);
|
|
950
980
|
const heightPx = this.units.toPixels(config.height);
|
|
951
981
|
this.designBackground = config.backgroundColor ?? "#ffffff";
|
|
952
|
-
this.canvas = new
|
|
982
|
+
this.canvas = new import_fabric4.Canvas(canvasElement, {
|
|
953
983
|
width: widthPx,
|
|
954
984
|
height: heightPx,
|
|
955
985
|
backgroundColor: this.designBackground,
|
|
@@ -975,13 +1005,13 @@ var CanvasEditor = class {
|
|
|
975
1005
|
}
|
|
976
1006
|
// ─── Layer Operations ────────────────────────────────
|
|
977
1007
|
async addImage(url, options) {
|
|
978
|
-
const img = await
|
|
1008
|
+
const img = await import_fabric4.FabricImage.fromURL(url, {}, { originX: "left", originY: "top", ...options });
|
|
979
1009
|
const layer = this.layers.add("image", img);
|
|
980
1010
|
this.history.save();
|
|
981
1011
|
return layer;
|
|
982
1012
|
}
|
|
983
1013
|
addText(text, options) {
|
|
984
|
-
const textbox = new
|
|
1014
|
+
const textbox = new import_fabric4.Textbox(text, {
|
|
985
1015
|
fontSize: 32,
|
|
986
1016
|
fontFamily: "Arial",
|
|
987
1017
|
fill: "#000000",
|
|
@@ -1256,6 +1286,7 @@ var CanvasEditor = class {
|
|
|
1256
1286
|
// src/types.ts
|
|
1257
1287
|
var DEFAULT_PATTERN_CONFIG = {
|
|
1258
1288
|
mode: "grid",
|
|
1289
|
+
scale: 100,
|
|
1259
1290
|
horizontalSpacing: 0,
|
|
1260
1291
|
verticalSpacing: 0,
|
|
1261
1292
|
angle: 0,
|
|
@@ -1285,6 +1316,7 @@ var DEFAULT_PATTERN_CONFIG = {
|
|
|
1285
1316
|
exportPNG,
|
|
1286
1317
|
exportSVG,
|
|
1287
1318
|
generateId,
|
|
1319
|
+
resolvePatternPlacement,
|
|
1288
1320
|
round2,
|
|
1289
1321
|
serializeEditor
|
|
1290
1322
|
});
|