web-to-print 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/LICENSE +21 -0
- package/dist/cjs/app-globals-V2Kpy_OQ.js +5 -0
- package/dist/cjs/canvas-helpers-A6rp5rPD.js +765 -0
- package/dist/cjs/index-IFGFRm-i.js +1649 -0
- package/dist/cjs/index.cjs.js +232 -0
- package/dist/cjs/loader.cjs.js +13 -0
- package/dist/cjs/logo-BUX-b45R.js +18 -0
- package/dist/cjs/web-to-print.cjs.js +25 -0
- package/dist/cjs/wtp-editor_2.cjs.entry.js +12386 -0
- package/dist/cjs/wtp-logo-renderer.cjs.entry.js +353 -0
- package/dist/cjs/wtp-print-area-editor.cjs.entry.js +431 -0
- package/dist/collection/collection-manifest.json +16 -0
- package/dist/collection/components/wtp-editor/wtp-editor.css +124 -0
- package/dist/collection/components/wtp-editor/wtp-editor.js +1114 -0
- package/dist/collection/components/wtp-logo-renderer/wtp-logo-renderer.css +30 -0
- package/dist/collection/components/wtp-logo-renderer/wtp-logo-renderer.js +455 -0
- package/dist/collection/components/wtp-logo-upload/wtp-logo-upload.css +428 -0
- package/dist/collection/components/wtp-logo-upload/wtp-logo-upload.js +573 -0
- package/dist/collection/components/wtp-print-area-editor/wtp-print-area-editor.css +20 -0
- package/dist/collection/components/wtp-print-area-editor/wtp-print-area-editor.js +600 -0
- package/dist/collection/examples/schaeffler--big.svg +1 -0
- package/dist/collection/index.js +8 -0
- package/dist/collection/types/editor.js +1 -0
- package/dist/collection/types/index.js +2 -0
- package/dist/collection/types/labels.js +30 -0
- package/dist/collection/types/logo.js +13 -0
- package/dist/collection/utils/background-removal.js +717 -0
- package/dist/collection/utils/canvas-helpers.js +380 -0
- package/dist/collection/utils/format-detection.js +48 -0
- package/dist/collection/utils/html-render-helpers.js +106 -0
- package/dist/collection/utils/image-preview.js +54 -0
- package/dist/collection/utils/logo-validation.js +141 -0
- package/dist/collection/utils/pdf-export.js +224 -0
- package/dist/components/index.d.ts +35 -0
- package/dist/components/index.js +1 -0
- package/dist/components/p-5qCsRzlt.js +1 -0
- package/dist/components/p-Bn9gR_8e.js +1 -0
- package/dist/components/p-D8pVJRuX.js +1 -0
- package/dist/components/wtp-editor.d.ts +11 -0
- package/dist/components/wtp-editor.js +1 -0
- package/dist/components/wtp-logo-renderer.d.ts +11 -0
- package/dist/components/wtp-logo-renderer.js +1 -0
- package/dist/components/wtp-logo-upload.d.ts +11 -0
- package/dist/components/wtp-logo-upload.js +1 -0
- package/dist/components/wtp-print-area-editor.d.ts +11 -0
- package/dist/components/wtp-print-area-editor.js +1 -0
- package/dist/esm/app-globals-DQuL1Twl.js +3 -0
- package/dist/esm/canvas-helpers-CK8OAq2J.js +748 -0
- package/dist/esm/index-CUetmLbL.js +1641 -0
- package/dist/esm/index.js +228 -0
- package/dist/esm/loader.js +11 -0
- package/dist/esm/logo-D8pVJRuX.js +15 -0
- package/dist/esm/web-to-print.js +21 -0
- package/dist/esm/wtp-editor_2.entry.js +12383 -0
- package/dist/esm/wtp-logo-renderer.entry.js +351 -0
- package/dist/esm/wtp-print-area-editor.entry.js +429 -0
- package/dist/index.cjs.js +1 -0
- package/dist/index.js +1 -0
- package/dist/types/components/wtp-editor/wtp-editor.d.ts +101 -0
- package/dist/types/components/wtp-logo-renderer/wtp-logo-renderer.d.ts +55 -0
- package/dist/types/components/wtp-logo-upload/wtp-logo-upload.d.ts +76 -0
- package/dist/types/components/wtp-print-area-editor/wtp-print-area-editor.d.ts +43 -0
- package/dist/types/components.d.ts +507 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/types/stencil-public-runtime.d.ts +1860 -0
- package/dist/types/types/editor.d.ts +79 -0
- package/dist/types/types/index.d.ts +5 -0
- package/dist/types/types/labels.d.ts +30 -0
- package/dist/types/types/logo.d.ts +47 -0
- package/dist/types/utils/background-removal.d.ts +95 -0
- package/dist/types/utils/canvas-helpers.d.ts +60 -0
- package/dist/types/utils/format-detection.d.ts +4 -0
- package/dist/types/utils/html-render-helpers.d.ts +44 -0
- package/dist/types/utils/image-preview.d.ts +13 -0
- package/dist/types/utils/logo-validation.d.ts +2 -0
- package/dist/types/utils/pdf-export.d.ts +32 -0
- package/dist/web-to-print/index.esm.js +1 -0
- package/dist/web-to-print/p-611ec561.entry.js +1 -0
- package/dist/web-to-print/p-703e4c52.entry.js +1 -0
- package/dist/web-to-print/p-CK8OAq2J.js +1 -0
- package/dist/web-to-print/p-CUetmLbL.js +2 -0
- package/dist/web-to-print/p-D8pVJRuX.js +1 -0
- package/dist/web-to-print/p-DQuL1Twl.js +1 -0
- package/dist/web-to-print/p-b532777b.entry.js +1 -0
- package/dist/web-to-print/web-to-print.esm.js +1 -0
- package/loader/cdn.js +1 -0
- package/loader/index.cjs.js +1 -0
- package/loader/index.d.ts +24 -0
- package/loader/index.es2017.js +1 -0
- package/loader/index.js +2 -0
- package/package.json +68 -0
- package/readme.md +490 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var index = require('./index-IFGFRm-i.js');
|
|
4
|
+
var canvasHelpers = require('./canvas-helpers-A6rp5rPD.js');
|
|
5
|
+
|
|
6
|
+
const wtpPrintAreaEditorCss = () => `*.sc-wtp-print-area-editor,*.sc-wtp-print-area-editor::before,*.sc-wtp-print-area-editor::after{box-sizing:border-box}.sc-wtp-print-area-editor-h{font-family:var(--wtp-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);color:var(--wtp-color-text, #1e293b);line-height:1.5}.wtp-print-area-editor.sc-wtp-print-area-editor{display:inline-block;line-height:0;border:1px solid var(--wtp-color-border, #e2e8f0);border-radius:8px;overflow:hidden;background:repeating-conic-gradient(#e5e7eb 0% 25%, transparent 0% 50%) 50%/20px 20px}`;
|
|
7
|
+
|
|
8
|
+
const CORNER_RADIUS = 7;
|
|
9
|
+
/**
|
|
10
|
+
* Custom Fabric.js object that renders a quadrilateral defined by 4 corner
|
|
11
|
+
* offsets from the bounding-box center. Top/bottom edges can be curved
|
|
12
|
+
* via `bulge` (-1 to 1) using quadratic Bezier curves.
|
|
13
|
+
*/
|
|
14
|
+
class PrintAreaQuad extends canvasHelpers.J {
|
|
15
|
+
constructor(options) {
|
|
16
|
+
super(options);
|
|
17
|
+
this.cornerOffsets = options?.cornerOffsets ?? [
|
|
18
|
+
new canvasHelpers.N(-50, -50), new canvasHelpers.N(50, -50),
|
|
19
|
+
new canvasHelpers.N(50, 50), new canvasHelpers.N(-50, 50),
|
|
20
|
+
];
|
|
21
|
+
this.bulge = options?.bulge ?? 0;
|
|
22
|
+
this.objectCaching = false;
|
|
23
|
+
}
|
|
24
|
+
_render(ctx) {
|
|
25
|
+
const [tl, tr, br, bl] = this.cornerOffsets;
|
|
26
|
+
ctx.beginPath();
|
|
27
|
+
ctx.moveTo(tl.x, tl.y);
|
|
28
|
+
if (this.bulge !== 0) {
|
|
29
|
+
// Curved top edge: control point at midpoint shifted by bulge
|
|
30
|
+
const topMidX = (tl.x + tr.x) / 2;
|
|
31
|
+
const topMidY = (tl.y + tr.y) / 2;
|
|
32
|
+
const bulgePixels = this.bulge * (this.height ?? 1);
|
|
33
|
+
ctx.quadraticCurveTo(topMidX, topMidY - bulgePixels, tr.x, tr.y);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
ctx.lineTo(tr.x, tr.y);
|
|
37
|
+
}
|
|
38
|
+
ctx.lineTo(br.x, br.y);
|
|
39
|
+
if (this.bulge !== 0) {
|
|
40
|
+
// Curved bottom edge: same direction as top for cylindrical appearance
|
|
41
|
+
const botMidX = (br.x + bl.x) / 2;
|
|
42
|
+
const botMidY = (br.y + bl.y) / 2;
|
|
43
|
+
const bulgePixels = this.bulge * (this.height ?? 1);
|
|
44
|
+
ctx.quadraticCurveTo(botMidX, botMidY - bulgePixels, bl.x, bl.y);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
ctx.lineTo(bl.x, bl.y);
|
|
48
|
+
}
|
|
49
|
+
ctx.closePath();
|
|
50
|
+
this._renderPaintInOrder(ctx);
|
|
51
|
+
}
|
|
52
|
+
/** Recompute left/top/width/height from absolute corner positions. */
|
|
53
|
+
recalcBounds() {
|
|
54
|
+
const center = this.getCenterPoint();
|
|
55
|
+
const abs = this.cornerOffsets.map(o => new canvasHelpers.N(center.x + o.x, center.y + o.y));
|
|
56
|
+
const xs = abs.map(p => p.x);
|
|
57
|
+
const ys = abs.map(p => p.y);
|
|
58
|
+
const minX = Math.min(...xs);
|
|
59
|
+
const maxX = Math.max(...xs);
|
|
60
|
+
const minY = Math.min(...ys);
|
|
61
|
+
const maxY = Math.max(...ys);
|
|
62
|
+
const newCenterX = (minX + maxX) / 2;
|
|
63
|
+
const newCenterY = (minY + maxY) / 2;
|
|
64
|
+
this.cornerOffsets = abs.map(p => new canvasHelpers.N(p.x - newCenterX, p.y - newCenterY));
|
|
65
|
+
this.set({
|
|
66
|
+
left: newCenterX,
|
|
67
|
+
top: newCenterY,
|
|
68
|
+
width: Math.max(maxX - minX, 1),
|
|
69
|
+
height: Math.max(maxY - minY, 1),
|
|
70
|
+
});
|
|
71
|
+
this.setCoords();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function buildCornerControl(index) {
|
|
75
|
+
return new canvasHelpers.q({
|
|
76
|
+
x: 0,
|
|
77
|
+
y: 0,
|
|
78
|
+
sizeX: CORNER_RADIUS * 2,
|
|
79
|
+
sizeY: CORNER_RADIUS * 2,
|
|
80
|
+
touchSizeX: CORNER_RADIUS * 4,
|
|
81
|
+
touchSizeY: CORNER_RADIUS * 4,
|
|
82
|
+
cursorStyleHandler: () => 'move',
|
|
83
|
+
positionHandler: (_dim, finalMatrix, fabricObject) => {
|
|
84
|
+
const quad = fabricObject;
|
|
85
|
+
return new canvasHelpers.N(quad.cornerOffsets[index].x, quad.cornerOffsets[index].y).transform(finalMatrix);
|
|
86
|
+
},
|
|
87
|
+
actionHandler: (_eventData, transformData, x, y) => {
|
|
88
|
+
const quad = transformData.target;
|
|
89
|
+
const center = quad.getCenterPoint();
|
|
90
|
+
quad.cornerOffsets[index] = new canvasHelpers.N(x - center.x, y - center.y);
|
|
91
|
+
quad.recalcBounds();
|
|
92
|
+
return true;
|
|
93
|
+
},
|
|
94
|
+
render: (ctx, left, top) => {
|
|
95
|
+
ctx.save();
|
|
96
|
+
ctx.fillStyle = '#2563eb';
|
|
97
|
+
ctx.strokeStyle = '#ffffff';
|
|
98
|
+
ctx.lineWidth = 2;
|
|
99
|
+
ctx.beginPath();
|
|
100
|
+
ctx.arc(left, top, CORNER_RADIUS, 0, Math.PI * 2);
|
|
101
|
+
ctx.fill();
|
|
102
|
+
ctx.stroke();
|
|
103
|
+
ctx.restore();
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function buildBulgeControl() {
|
|
108
|
+
const HANDLE_OFFSET_X = 24;
|
|
109
|
+
const TRACK_GAP = 4;
|
|
110
|
+
return new canvasHelpers.q({
|
|
111
|
+
x: 0.5,
|
|
112
|
+
y: 0,
|
|
113
|
+
offsetX: HANDLE_OFFSET_X,
|
|
114
|
+
sizeX: CORNER_RADIUS * 2,
|
|
115
|
+
sizeY: CORNER_RADIUS * 2,
|
|
116
|
+
touchSizeX: CORNER_RADIUS * 4,
|
|
117
|
+
touchSizeY: CORNER_RADIUS * 4,
|
|
118
|
+
cursorStyleHandler: () => 'ns-resize',
|
|
119
|
+
positionHandler: (dim, finalMatrix, fabricObject) => {
|
|
120
|
+
const quad = fabricObject;
|
|
121
|
+
return new canvasHelpers.N(0.5 * dim.x + HANDLE_OFFSET_X, -quad.bulge * dim.y / 2).transform(finalMatrix);
|
|
122
|
+
},
|
|
123
|
+
actionHandler: (_eventData, transformData, _x, y) => {
|
|
124
|
+
const quad = transformData.target;
|
|
125
|
+
const center = quad.getCenterPoint();
|
|
126
|
+
const halfH = (quad.height ?? 0) * (quad.scaleY ?? 1) / 2;
|
|
127
|
+
const bulge = Math.max(-1, Math.min(1, (center.y - y) / Math.max(halfH, 1)));
|
|
128
|
+
quad.bulge = bulge;
|
|
129
|
+
return true;
|
|
130
|
+
},
|
|
131
|
+
render: (ctx, left, top, _styleOverride, fabricObject) => {
|
|
132
|
+
const quad = fabricObject;
|
|
133
|
+
const halfH = ((quad.height ?? 0) * (quad.scaleY ?? 1)) / 2;
|
|
134
|
+
const angleRad = ((quad.angle ?? 0) * Math.PI) / 180;
|
|
135
|
+
const trackTopX = left + Math.sin(angleRad) * halfH;
|
|
136
|
+
const trackTopY = top - Math.cos(angleRad) * halfH;
|
|
137
|
+
const trackBotX = left - Math.sin(angleRad) * halfH;
|
|
138
|
+
const trackBotY = top + Math.cos(angleRad) * halfH;
|
|
139
|
+
ctx.save();
|
|
140
|
+
// Track line
|
|
141
|
+
ctx.strokeStyle = 'rgba(37, 99, 235, 0.3)';
|
|
142
|
+
ctx.lineWidth = 2;
|
|
143
|
+
ctx.setLineDash([3, 3]);
|
|
144
|
+
ctx.beginPath();
|
|
145
|
+
ctx.moveTo(trackTopX, trackTopY - TRACK_GAP);
|
|
146
|
+
ctx.lineTo(trackBotX, trackBotY + TRACK_GAP);
|
|
147
|
+
ctx.stroke();
|
|
148
|
+
ctx.setLineDash([]);
|
|
149
|
+
// Handle circle
|
|
150
|
+
ctx.fillStyle = '#2563eb';
|
|
151
|
+
ctx.strokeStyle = '#ffffff';
|
|
152
|
+
ctx.lineWidth = 2;
|
|
153
|
+
ctx.beginPath();
|
|
154
|
+
ctx.arc(left, top, CORNER_RADIUS, 0, Math.PI * 2);
|
|
155
|
+
ctx.fill();
|
|
156
|
+
ctx.stroke();
|
|
157
|
+
// Arrow up + "+" label
|
|
158
|
+
ctx.fillStyle = 'rgba(37, 99, 235, 0.5)';
|
|
159
|
+
ctx.beginPath();
|
|
160
|
+
ctx.moveTo(trackTopX, trackTopY - TRACK_GAP);
|
|
161
|
+
ctx.lineTo(trackTopX - 4, trackTopY - TRACK_GAP + 6);
|
|
162
|
+
ctx.lineTo(trackTopX + 4, trackTopY - TRACK_GAP + 6);
|
|
163
|
+
ctx.closePath();
|
|
164
|
+
ctx.fill();
|
|
165
|
+
ctx.font = 'bold 10px sans-serif';
|
|
166
|
+
ctx.textAlign = 'center';
|
|
167
|
+
ctx.fillText('+', trackTopX, trackTopY - TRACK_GAP - 4);
|
|
168
|
+
// Arrow down + "\u2212" label
|
|
169
|
+
ctx.beginPath();
|
|
170
|
+
ctx.moveTo(trackBotX, trackBotY + TRACK_GAP);
|
|
171
|
+
ctx.lineTo(trackBotX - 4, trackBotY + TRACK_GAP - 6);
|
|
172
|
+
ctx.lineTo(trackBotX + 4, trackBotY + TRACK_GAP - 6);
|
|
173
|
+
ctx.closePath();
|
|
174
|
+
ctx.fill();
|
|
175
|
+
ctx.fillText('\u2212', trackBotX, trackBotY + TRACK_GAP + 12);
|
|
176
|
+
ctx.restore();
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
const WtpPrintAreaEditor = class {
|
|
181
|
+
constructor(hostRef) {
|
|
182
|
+
index.registerInstance(this, hostRef);
|
|
183
|
+
this.wtpPrintAreaChange = index.createEvent(this, "wtpPrintAreaChange");
|
|
184
|
+
}
|
|
185
|
+
get el() { return index.getElement(this); }
|
|
186
|
+
/** Product background image URL. */
|
|
187
|
+
productImage;
|
|
188
|
+
/** Canvas width in pixels. */
|
|
189
|
+
width = 800;
|
|
190
|
+
/** Canvas height in pixels. */
|
|
191
|
+
height = 600;
|
|
192
|
+
/** Current print area (relative 0-1 coordinates). */
|
|
193
|
+
printArea;
|
|
194
|
+
/** Fires when the print area rectangle is modified. */
|
|
195
|
+
wtpPrintAreaChange;
|
|
196
|
+
canvas;
|
|
197
|
+
canvasEl;
|
|
198
|
+
areaQuad;
|
|
199
|
+
/** Generation counter to discard stale background loads. */
|
|
200
|
+
bgLoadGen = 0;
|
|
201
|
+
componentDidLoad() {
|
|
202
|
+
this.initCanvas();
|
|
203
|
+
}
|
|
204
|
+
disconnectedCallback() {
|
|
205
|
+
++this.bgLoadGen;
|
|
206
|
+
this.canvas?.dispose();
|
|
207
|
+
}
|
|
208
|
+
async onProductImageChange() {
|
|
209
|
+
if (this.canvas === undefined || this.productImage === undefined)
|
|
210
|
+
return;
|
|
211
|
+
await this.reloadCanvas();
|
|
212
|
+
}
|
|
213
|
+
async onSizeChange() {
|
|
214
|
+
if (this.canvas === undefined)
|
|
215
|
+
return;
|
|
216
|
+
await this.reloadCanvas();
|
|
217
|
+
}
|
|
218
|
+
onPrintAreaChange() {
|
|
219
|
+
this.syncQuadFromPrintArea();
|
|
220
|
+
}
|
|
221
|
+
/** Get the current print area as relative 0-1 coordinates. */
|
|
222
|
+
async getPrintArea() {
|
|
223
|
+
return this.readQuadAsPrintArea();
|
|
224
|
+
}
|
|
225
|
+
/** Set the print area and update the quad on canvas. */
|
|
226
|
+
async setPrintArea(printArea) {
|
|
227
|
+
this.printArea = printArea;
|
|
228
|
+
this.syncQuadFromPrintArea();
|
|
229
|
+
}
|
|
230
|
+
/** Create the Fabric Canvas once and perform the initial load. */
|
|
231
|
+
async initCanvas() {
|
|
232
|
+
if (this.canvasEl === undefined)
|
|
233
|
+
return;
|
|
234
|
+
this.canvas = new canvasHelpers.ho(this.canvasEl, {
|
|
235
|
+
width: this.width,
|
|
236
|
+
height: this.height,
|
|
237
|
+
backgroundColor: '#ffffff',
|
|
238
|
+
selection: false,
|
|
239
|
+
});
|
|
240
|
+
this.canvas.on('object:modified', (e) => {
|
|
241
|
+
if (e.target === this.areaQuad) {
|
|
242
|
+
this.emitPrintArea();
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
this.canvas.on('object:moving', (e) => {
|
|
246
|
+
if (e.target === this.areaQuad) {
|
|
247
|
+
this.clampQuadToBounds();
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
await this.reloadCanvas();
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Clear the canvas and reload background + quad.
|
|
254
|
+
* Keeps the Fabric Canvas instance alive (no dispose/recreate) to avoid
|
|
255
|
+
* stale DOM state from async dispose in Fabric v7.
|
|
256
|
+
*/
|
|
257
|
+
async reloadCanvas() {
|
|
258
|
+
if (this.canvas === undefined)
|
|
259
|
+
return;
|
|
260
|
+
const gen = ++this.bgLoadGen;
|
|
261
|
+
// Remove all objects (background image + quad)
|
|
262
|
+
this.areaQuad = undefined;
|
|
263
|
+
this.canvas.discardActiveObject();
|
|
264
|
+
for (const obj of this.canvas.getObjects().slice()) {
|
|
265
|
+
this.canvas.remove(obj);
|
|
266
|
+
}
|
|
267
|
+
// Reset internal state to bounding-box dimensions
|
|
268
|
+
this.canvas.viewportTransform = [1, 0, 0, 1, 0, 0];
|
|
269
|
+
this.canvas.setDimensions({ width: this.width, height: this.height });
|
|
270
|
+
this.canvas.backgroundColor = '#ffffff';
|
|
271
|
+
this.canvas.requestRenderAll();
|
|
272
|
+
// Load product image (may auto-resize canvas in contain mode)
|
|
273
|
+
if (this.productImage !== undefined && this.productImage !== '') {
|
|
274
|
+
await canvasHelpers.setCanvasBackground(this.canvas, this.productImage);
|
|
275
|
+
}
|
|
276
|
+
if (gen !== this.bgLoadGen)
|
|
277
|
+
return;
|
|
278
|
+
this.createAreaQuad();
|
|
279
|
+
this.canvas.renderAll();
|
|
280
|
+
}
|
|
281
|
+
createAreaQuad() {
|
|
282
|
+
if (this.canvas === undefined)
|
|
283
|
+
return;
|
|
284
|
+
const pa = this.printArea ?? canvasHelpers.defaultPrintArea();
|
|
285
|
+
// Convert relative coords directly to canvas pixels (image-relative coordinates)
|
|
286
|
+
const cw = this.canvas.getWidth();
|
|
287
|
+
const ch = this.canvas.getHeight();
|
|
288
|
+
const [tl, tr, br, bl] = canvasHelpers.printAreaToPixelCorners(pa, cw, ch);
|
|
289
|
+
// Compute centroid and bounding box
|
|
290
|
+
const cx = (tl.x + tr.x + br.x + bl.x) / 4;
|
|
291
|
+
const cy = (tl.y + tr.y + br.y + bl.y) / 4;
|
|
292
|
+
const xs = [tl.x, tr.x, br.x, bl.x];
|
|
293
|
+
const ys = [tl.y, tr.y, br.y, bl.y];
|
|
294
|
+
const bbW = Math.max(Math.max(...xs) - Math.min(...xs), 1);
|
|
295
|
+
const bbH = Math.max(Math.max(...ys) - Math.min(...ys), 1);
|
|
296
|
+
this.areaQuad = new PrintAreaQuad({
|
|
297
|
+
left: cx,
|
|
298
|
+
top: cy,
|
|
299
|
+
width: bbW,
|
|
300
|
+
height: bbH,
|
|
301
|
+
cornerOffsets: [
|
|
302
|
+
new canvasHelpers.N(tl.x - cx, tl.y - cy),
|
|
303
|
+
new canvasHelpers.N(tr.x - cx, tr.y - cy),
|
|
304
|
+
new canvasHelpers.N(br.x - cx, br.y - cy),
|
|
305
|
+
new canvasHelpers.N(bl.x - cx, bl.y - cy),
|
|
306
|
+
],
|
|
307
|
+
bulge: pa.bulge ?? 0,
|
|
308
|
+
originX: 'center',
|
|
309
|
+
originY: 'center',
|
|
310
|
+
fill: 'rgba(37, 99, 235, 0.15)',
|
|
311
|
+
stroke: '#2563eb',
|
|
312
|
+
strokeWidth: 2,
|
|
313
|
+
strokeDashArray: [6, 4],
|
|
314
|
+
// Disable standard resize/rotate controls
|
|
315
|
+
lockScalingX: true,
|
|
316
|
+
lockScalingY: true,
|
|
317
|
+
lockRotation: true,
|
|
318
|
+
hasRotatingPoint: false,
|
|
319
|
+
borderColor: '#2563eb',
|
|
320
|
+
});
|
|
321
|
+
// Replace all controls with corner handles + bulge
|
|
322
|
+
this.areaQuad.controls = {
|
|
323
|
+
corner0: buildCornerControl(0),
|
|
324
|
+
corner1: buildCornerControl(1),
|
|
325
|
+
corner2: buildCornerControl(2),
|
|
326
|
+
corner3: buildCornerControl(3),
|
|
327
|
+
bulgeHandle: buildBulgeControl(),
|
|
328
|
+
};
|
|
329
|
+
this.canvas.add(this.areaQuad);
|
|
330
|
+
this.canvas.renderAll();
|
|
331
|
+
}
|
|
332
|
+
syncQuadFromPrintArea() {
|
|
333
|
+
if (this.areaQuad === undefined || this.canvas === undefined)
|
|
334
|
+
return;
|
|
335
|
+
const pa = this.printArea ?? canvasHelpers.defaultPrintArea();
|
|
336
|
+
// Convert relative coords directly to canvas pixels (image-relative coordinates)
|
|
337
|
+
const cw = this.canvas.getWidth();
|
|
338
|
+
const ch = this.canvas.getHeight();
|
|
339
|
+
const [tl, tr, br, bl] = canvasHelpers.printAreaToPixelCorners(pa, cw, ch);
|
|
340
|
+
const cx = (tl.x + tr.x + br.x + bl.x) / 4;
|
|
341
|
+
const cy = (tl.y + tr.y + br.y + bl.y) / 4;
|
|
342
|
+
const xs = [tl.x, tr.x, br.x, bl.x];
|
|
343
|
+
const ys = [tl.y, tr.y, br.y, bl.y];
|
|
344
|
+
this.areaQuad.cornerOffsets = [
|
|
345
|
+
new canvasHelpers.N(tl.x - cx, tl.y - cy),
|
|
346
|
+
new canvasHelpers.N(tr.x - cx, tr.y - cy),
|
|
347
|
+
new canvasHelpers.N(br.x - cx, br.y - cy),
|
|
348
|
+
new canvasHelpers.N(bl.x - cx, bl.y - cy),
|
|
349
|
+
];
|
|
350
|
+
this.areaQuad.bulge = pa.bulge ?? 0;
|
|
351
|
+
this.areaQuad.set({
|
|
352
|
+
left: cx,
|
|
353
|
+
top: cy,
|
|
354
|
+
width: Math.max(Math.max(...xs) - Math.min(...xs), 1),
|
|
355
|
+
height: Math.max(Math.max(...ys) - Math.min(...ys), 1),
|
|
356
|
+
scaleX: 1,
|
|
357
|
+
scaleY: 1,
|
|
358
|
+
});
|
|
359
|
+
this.areaQuad.setCoords();
|
|
360
|
+
this.canvas.renderAll();
|
|
361
|
+
}
|
|
362
|
+
clampQuadToBounds() {
|
|
363
|
+
if (this.areaQuad === undefined)
|
|
364
|
+
return;
|
|
365
|
+
const center = this.areaQuad.getCenterPoint();
|
|
366
|
+
const abs = this.areaQuad.cornerOffsets.map(o => ({
|
|
367
|
+
x: center.x + o.x,
|
|
368
|
+
y: center.y + o.y,
|
|
369
|
+
}));
|
|
370
|
+
// Find how much the quad exceeds canvas bounds
|
|
371
|
+
let dx = 0;
|
|
372
|
+
let dy = 0;
|
|
373
|
+
for (const p of abs) {
|
|
374
|
+
if (p.x < 0)
|
|
375
|
+
dx = Math.max(dx, -p.x);
|
|
376
|
+
if (p.x > this.canvas.getWidth())
|
|
377
|
+
dx = Math.min(dx, this.canvas.getWidth() - p.x);
|
|
378
|
+
if (p.y < 0)
|
|
379
|
+
dy = Math.max(dy, -p.y);
|
|
380
|
+
if (p.y > this.canvas.getHeight())
|
|
381
|
+
dy = Math.min(dy, this.canvas.getHeight() - p.y);
|
|
382
|
+
}
|
|
383
|
+
if (dx !== 0 || dy !== 0) {
|
|
384
|
+
this.areaQuad.set({
|
|
385
|
+
left: (this.areaQuad.left ?? 0) + dx,
|
|
386
|
+
top: (this.areaQuad.top ?? 0) + dy,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
readQuadAsPrintArea() {
|
|
391
|
+
if (this.areaQuad === undefined) {
|
|
392
|
+
return this.printArea ?? canvasHelpers.defaultPrintArea();
|
|
393
|
+
}
|
|
394
|
+
const center = this.areaQuad.getCenterPoint();
|
|
395
|
+
const corners = [
|
|
396
|
+
{ x: center.x + this.areaQuad.cornerOffsets[0].x, y: center.y + this.areaQuad.cornerOffsets[0].y },
|
|
397
|
+
{ x: center.x + this.areaQuad.cornerOffsets[1].x, y: center.y + this.areaQuad.cornerOffsets[1].y },
|
|
398
|
+
{ x: center.x + this.areaQuad.cornerOffsets[2].x, y: center.y + this.areaQuad.cornerOffsets[2].y },
|
|
399
|
+
{ x: center.x + this.areaQuad.cornerOffsets[3].x, y: center.y + this.areaQuad.cornerOffsets[3].y },
|
|
400
|
+
];
|
|
401
|
+
// Convert canvas pixels directly to 0-1 image-relative coordinates
|
|
402
|
+
const cw = this.canvas.getWidth();
|
|
403
|
+
const ch = this.canvas.getHeight();
|
|
404
|
+
return canvasHelpers.pixelCornersToPrintArea(corners, cw, ch, this.areaQuad.bulge);
|
|
405
|
+
}
|
|
406
|
+
emitPrintArea() {
|
|
407
|
+
const area = this.readQuadAsPrintArea();
|
|
408
|
+
this.printArea = area;
|
|
409
|
+
this.wtpPrintAreaChange.emit(area);
|
|
410
|
+
}
|
|
411
|
+
render() {
|
|
412
|
+
return (index.h("div", { key: 'fbeaeb8ac49780440ceb1bffa38689a23aeef66a', class: "wtp-print-area-editor" }, index.h("canvas", { key: 'b7e97909efaa17c7ffb4b089cf31e508e20c094e', ref: el => (this.canvasEl = el) })));
|
|
413
|
+
}
|
|
414
|
+
static get watchers() { return {
|
|
415
|
+
"productImage": [{
|
|
416
|
+
"onProductImageChange": 0
|
|
417
|
+
}],
|
|
418
|
+
"width": [{
|
|
419
|
+
"onSizeChange": 0
|
|
420
|
+
}],
|
|
421
|
+
"height": [{
|
|
422
|
+
"onSizeChange": 0
|
|
423
|
+
}],
|
|
424
|
+
"printArea": [{
|
|
425
|
+
"onPrintAreaChange": 0
|
|
426
|
+
}]
|
|
427
|
+
}; }
|
|
428
|
+
};
|
|
429
|
+
WtpPrintAreaEditor.style = wtpPrintAreaEditorCss();
|
|
430
|
+
|
|
431
|
+
exports.wtp_print_area_editor = WtpPrintAreaEditor;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"entries": [
|
|
3
|
+
"components/wtp-editor/wtp-editor.js",
|
|
4
|
+
"components/wtp-logo-renderer/wtp-logo-renderer.js",
|
|
5
|
+
"components/wtp-logo-upload/wtp-logo-upload.js",
|
|
6
|
+
"components/wtp-print-area-editor/wtp-print-area-editor.js"
|
|
7
|
+
],
|
|
8
|
+
"mixins": [],
|
|
9
|
+
"compiler": {
|
|
10
|
+
"name": "@stencil/core",
|
|
11
|
+
"version": "4.43.4",
|
|
12
|
+
"typescriptVersion": "5.8.3"
|
|
13
|
+
},
|
|
14
|
+
"collections": [],
|
|
15
|
+
"bundles": []
|
|
16
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
*,
|
|
2
|
+
*::before,
|
|
3
|
+
*::after {
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
:host {
|
|
8
|
+
font-family: var(--wtp-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
|
|
9
|
+
color: var(--wtp-color-text, #1e293b);
|
|
10
|
+
line-height: 1.5;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.wtp-editor {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
border: 1px solid var(--wtp-color-border, #e2e8f0);
|
|
17
|
+
border-radius: 12px;
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
background: var(--wtp-color-bg, #ffffff);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.toolbar {
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
gap: 8px;
|
|
26
|
+
padding: 8px 16px;
|
|
27
|
+
border-bottom: 1px solid var(--wtp-color-border, #e2e8f0);
|
|
28
|
+
background: var(--wtp-color-bg-muted, #f8fafc);
|
|
29
|
+
flex-wrap: wrap;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.toolbar-btn {
|
|
33
|
+
display: inline-flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
gap: 8px;
|
|
36
|
+
padding: 8px 16px;
|
|
37
|
+
font-family: var(--wtp-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
|
|
38
|
+
font-size: 14px;
|
|
39
|
+
font-weight: 500;
|
|
40
|
+
border-radius: 8px;
|
|
41
|
+
border: 1px solid var(--wtp-color-border, #e2e8f0);
|
|
42
|
+
background: var(--wtp-color-bg, #ffffff);
|
|
43
|
+
color: var(--wtp-color-text, #1e293b);
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
transition-property: all;
|
|
46
|
+
transition-duration: 250ms;
|
|
47
|
+
transition-timing-function: ease;
|
|
48
|
+
}
|
|
49
|
+
.toolbar-btn:hover {
|
|
50
|
+
background: var(--wtp-color-bg-muted, #f8fafc);
|
|
51
|
+
}
|
|
52
|
+
.toolbar-btn:focus-visible {
|
|
53
|
+
outline: 2px solid var(--wtp-color-primary, #2563eb);
|
|
54
|
+
outline-offset: 2px;
|
|
55
|
+
}
|
|
56
|
+
.toolbar-btn:disabled {
|
|
57
|
+
opacity: 0.5;
|
|
58
|
+
cursor: not-allowed;
|
|
59
|
+
}
|
|
60
|
+
.toolbar-btn {
|
|
61
|
+
padding: 4px 8px;
|
|
62
|
+
font-size: 12px;
|
|
63
|
+
}
|
|
64
|
+
.toolbar-btn svg {
|
|
65
|
+
width: 16px;
|
|
66
|
+
height: 16px;
|
|
67
|
+
flex-shrink: 0;
|
|
68
|
+
}
|
|
69
|
+
.toolbar-btn.danger:hover:not(:disabled) {
|
|
70
|
+
background: var(--wtp-color-error-light, #fee2e2);
|
|
71
|
+
color: var(--wtp-color-error, #dc2626);
|
|
72
|
+
border-color: var(--wtp-color-error, #dc2626);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.font-select {
|
|
76
|
+
padding: 4px 8px;
|
|
77
|
+
font-family: var(--wtp-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
|
|
78
|
+
font-size: 12px;
|
|
79
|
+
border: 1px solid var(--wtp-color-border, #e2e8f0);
|
|
80
|
+
border-radius: 8px;
|
|
81
|
+
background: var(--wtp-color-bg, #ffffff);
|
|
82
|
+
color: var(--wtp-color-text, #1e293b);
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
transition-property: all;
|
|
85
|
+
transition-duration: 250ms;
|
|
86
|
+
transition-timing-function: ease;
|
|
87
|
+
}
|
|
88
|
+
.font-select:focus-visible {
|
|
89
|
+
outline: 2px solid var(--wtp-color-primary, #2563eb);
|
|
90
|
+
outline-offset: 2px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.color-input {
|
|
94
|
+
width: 32px;
|
|
95
|
+
height: 28px;
|
|
96
|
+
padding: 2px;
|
|
97
|
+
border: 1px solid var(--wtp-color-border, #e2e8f0);
|
|
98
|
+
border-radius: 8px;
|
|
99
|
+
background: var(--wtp-color-bg, #ffffff);
|
|
100
|
+
cursor: pointer;
|
|
101
|
+
transition-property: all;
|
|
102
|
+
transition-duration: 250ms;
|
|
103
|
+
transition-timing-function: ease;
|
|
104
|
+
}
|
|
105
|
+
.color-input:focus-visible {
|
|
106
|
+
outline: 2px solid var(--wtp-color-primary, #2563eb);
|
|
107
|
+
outline-offset: 2px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.toolbar-separator {
|
|
111
|
+
width: 1px;
|
|
112
|
+
height: 24px;
|
|
113
|
+
background: var(--wtp-color-border, #e2e8f0);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.toolbar-spacer {
|
|
117
|
+
flex: 1;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.canvas-container {
|
|
121
|
+
line-height: 0;
|
|
122
|
+
overflow: auto;
|
|
123
|
+
background: repeating-conic-gradient(#e5e7eb 0% 25%, transparent 0% 50%) 50%/20px 20px;
|
|
124
|
+
}
|