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.
Files changed (92) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cjs/app-globals-V2Kpy_OQ.js +5 -0
  3. package/dist/cjs/canvas-helpers-A6rp5rPD.js +765 -0
  4. package/dist/cjs/index-IFGFRm-i.js +1649 -0
  5. package/dist/cjs/index.cjs.js +232 -0
  6. package/dist/cjs/loader.cjs.js +13 -0
  7. package/dist/cjs/logo-BUX-b45R.js +18 -0
  8. package/dist/cjs/web-to-print.cjs.js +25 -0
  9. package/dist/cjs/wtp-editor_2.cjs.entry.js +12386 -0
  10. package/dist/cjs/wtp-logo-renderer.cjs.entry.js +353 -0
  11. package/dist/cjs/wtp-print-area-editor.cjs.entry.js +431 -0
  12. package/dist/collection/collection-manifest.json +16 -0
  13. package/dist/collection/components/wtp-editor/wtp-editor.css +124 -0
  14. package/dist/collection/components/wtp-editor/wtp-editor.js +1114 -0
  15. package/dist/collection/components/wtp-logo-renderer/wtp-logo-renderer.css +30 -0
  16. package/dist/collection/components/wtp-logo-renderer/wtp-logo-renderer.js +455 -0
  17. package/dist/collection/components/wtp-logo-upload/wtp-logo-upload.css +428 -0
  18. package/dist/collection/components/wtp-logo-upload/wtp-logo-upload.js +573 -0
  19. package/dist/collection/components/wtp-print-area-editor/wtp-print-area-editor.css +20 -0
  20. package/dist/collection/components/wtp-print-area-editor/wtp-print-area-editor.js +600 -0
  21. package/dist/collection/examples/schaeffler--big.svg +1 -0
  22. package/dist/collection/index.js +8 -0
  23. package/dist/collection/types/editor.js +1 -0
  24. package/dist/collection/types/index.js +2 -0
  25. package/dist/collection/types/labels.js +30 -0
  26. package/dist/collection/types/logo.js +13 -0
  27. package/dist/collection/utils/background-removal.js +717 -0
  28. package/dist/collection/utils/canvas-helpers.js +380 -0
  29. package/dist/collection/utils/format-detection.js +48 -0
  30. package/dist/collection/utils/html-render-helpers.js +106 -0
  31. package/dist/collection/utils/image-preview.js +54 -0
  32. package/dist/collection/utils/logo-validation.js +141 -0
  33. package/dist/collection/utils/pdf-export.js +224 -0
  34. package/dist/components/index.d.ts +35 -0
  35. package/dist/components/index.js +1 -0
  36. package/dist/components/p-5qCsRzlt.js +1 -0
  37. package/dist/components/p-Bn9gR_8e.js +1 -0
  38. package/dist/components/p-D8pVJRuX.js +1 -0
  39. package/dist/components/wtp-editor.d.ts +11 -0
  40. package/dist/components/wtp-editor.js +1 -0
  41. package/dist/components/wtp-logo-renderer.d.ts +11 -0
  42. package/dist/components/wtp-logo-renderer.js +1 -0
  43. package/dist/components/wtp-logo-upload.d.ts +11 -0
  44. package/dist/components/wtp-logo-upload.js +1 -0
  45. package/dist/components/wtp-print-area-editor.d.ts +11 -0
  46. package/dist/components/wtp-print-area-editor.js +1 -0
  47. package/dist/esm/app-globals-DQuL1Twl.js +3 -0
  48. package/dist/esm/canvas-helpers-CK8OAq2J.js +748 -0
  49. package/dist/esm/index-CUetmLbL.js +1641 -0
  50. package/dist/esm/index.js +228 -0
  51. package/dist/esm/loader.js +11 -0
  52. package/dist/esm/logo-D8pVJRuX.js +15 -0
  53. package/dist/esm/web-to-print.js +21 -0
  54. package/dist/esm/wtp-editor_2.entry.js +12383 -0
  55. package/dist/esm/wtp-logo-renderer.entry.js +351 -0
  56. package/dist/esm/wtp-print-area-editor.entry.js +429 -0
  57. package/dist/index.cjs.js +1 -0
  58. package/dist/index.js +1 -0
  59. package/dist/types/components/wtp-editor/wtp-editor.d.ts +101 -0
  60. package/dist/types/components/wtp-logo-renderer/wtp-logo-renderer.d.ts +55 -0
  61. package/dist/types/components/wtp-logo-upload/wtp-logo-upload.d.ts +76 -0
  62. package/dist/types/components/wtp-print-area-editor/wtp-print-area-editor.d.ts +43 -0
  63. package/dist/types/components.d.ts +507 -0
  64. package/dist/types/index.d.ts +11 -0
  65. package/dist/types/stencil-public-runtime.d.ts +1860 -0
  66. package/dist/types/types/editor.d.ts +79 -0
  67. package/dist/types/types/index.d.ts +5 -0
  68. package/dist/types/types/labels.d.ts +30 -0
  69. package/dist/types/types/logo.d.ts +47 -0
  70. package/dist/types/utils/background-removal.d.ts +95 -0
  71. package/dist/types/utils/canvas-helpers.d.ts +60 -0
  72. package/dist/types/utils/format-detection.d.ts +4 -0
  73. package/dist/types/utils/html-render-helpers.d.ts +44 -0
  74. package/dist/types/utils/image-preview.d.ts +13 -0
  75. package/dist/types/utils/logo-validation.d.ts +2 -0
  76. package/dist/types/utils/pdf-export.d.ts +32 -0
  77. package/dist/web-to-print/index.esm.js +1 -0
  78. package/dist/web-to-print/p-611ec561.entry.js +1 -0
  79. package/dist/web-to-print/p-703e4c52.entry.js +1 -0
  80. package/dist/web-to-print/p-CK8OAq2J.js +1 -0
  81. package/dist/web-to-print/p-CUetmLbL.js +2 -0
  82. package/dist/web-to-print/p-D8pVJRuX.js +1 -0
  83. package/dist/web-to-print/p-DQuL1Twl.js +1 -0
  84. package/dist/web-to-print/p-b532777b.entry.js +1 -0
  85. package/dist/web-to-print/web-to-print.esm.js +1 -0
  86. package/loader/cdn.js +1 -0
  87. package/loader/index.cjs.js +1 -0
  88. package/loader/index.d.ts +24 -0
  89. package/loader/index.es2017.js +1 -0
  90. package/loader/index.js +2 -0
  91. package/package.json +68 -0
  92. package/readme.md +490 -0
@@ -0,0 +1,351 @@
1
+ import { r as registerInstance, c as createEvent, g as getElement, h } from './index-CUetmLbL.js';
2
+ import { u as upscaleSvgDataUrl, b as warpImageForBulge, f as fitLogoToPrintArea, p as printAreaToPixelCorners } from './canvas-helpers-CK8OAq2J.js';
3
+
4
+ /** Utility functions for the HTML/CSS-based logo renderer and Canvas 2D export. */
5
+ /** Load an image from a URL/data-URL and return the HTMLImageElement once loaded. */
6
+ function loadImage(src) {
7
+ return new Promise((resolve, reject) => {
8
+ const img = new Image();
9
+ img.onload = () => resolve(img);
10
+ img.onerror = () => reject(new Error(`Failed to load image: ${src.slice(0, 80)}`));
11
+ img.src = src;
12
+ });
13
+ }
14
+ /** Compute contain-fit dimensions (uniform scale to fit inside container). */
15
+ function computeContainFit(containerW, containerH, imgW, imgH) {
16
+ const scale = Math.min(containerW / imgW, containerH / imgH);
17
+ return { fittedW: imgW * scale, fittedH: imgH * scale };
18
+ }
19
+ /**
20
+ * Convert a Fabric.js center-origin transform to CSS top-left positioning.
21
+ * Fabric.js stores x/y as the center of the object. CSS `transform-origin: center`
22
+ * means the origin is at the center of the unscaled element box, so left/top are
23
+ * computed by subtracting half the *natural* (unscaled) dimensions.
24
+ */
25
+ function centerOriginToTopLeft(transform, naturalW, naturalH) {
26
+ return {
27
+ left: transform.x - naturalW / 2,
28
+ top: transform.y - naturalH / 2,
29
+ };
30
+ }
31
+ /**
32
+ * Parse an SVG data URL to extract intrinsic dimensions from viewBox or width/height attributes.
33
+ * Returns null if the data URL is not SVG or dimensions cannot be determined.
34
+ */
35
+ function getSvgIntrinsicSize(svgDataUrl) {
36
+ if (!svgDataUrl.startsWith('data:image/svg+xml'))
37
+ return null;
38
+ let svgText;
39
+ const base64Idx = svgDataUrl.indexOf(';base64,');
40
+ if (base64Idx !== -1) {
41
+ svgText = atob(svgDataUrl.slice(base64Idx + 8));
42
+ }
43
+ else {
44
+ const commaIdx = svgDataUrl.indexOf(',');
45
+ if (commaIdx === -1)
46
+ return null;
47
+ svgText = decodeURIComponent(svgDataUrl.slice(commaIdx + 1));
48
+ }
49
+ // Use regex instead of DOMParser to avoid JSDOM/mock-doc XML parsing limitations
50
+ const svgMatch = svgText.match(/<svg[^>]*>/i);
51
+ if (svgMatch === null)
52
+ return null;
53
+ const svgTag = svgMatch[0];
54
+ const viewBoxMatch = svgTag.match(/viewBox=["']([^"']+)["']/);
55
+ if (viewBoxMatch !== null) {
56
+ const parts = viewBoxMatch[1].trim().split(/[\s,]+/);
57
+ const vbW = parseFloat(parts[2]);
58
+ const vbH = parseFloat(parts[3]);
59
+ if (vbW > 0 && vbH > 0)
60
+ return { width: vbW, height: vbH };
61
+ }
62
+ const widthMatch = svgTag.match(/\bwidth=["']([^"']+)["']/);
63
+ const heightMatch = svgTag.match(/\bheight=["']([^"']+)["']/);
64
+ if (widthMatch !== null && heightMatch !== null) {
65
+ const svgW = parseFloat(widthMatch[1]);
66
+ const svgH = parseFloat(heightMatch[1]);
67
+ if (svgW > 0 && svgH > 0)
68
+ return { width: svgW, height: svgH };
69
+ }
70
+ return null;
71
+ }
72
+ /**
73
+ * Canvas 2D export: draw product background + logo layers onto a plain canvas.
74
+ * Returns a data URL string.
75
+ */
76
+ function renderToCanvas(containerW, containerH, bgColor, productImg, layers, format = 'png', quality = 1) {
77
+ const canvas = document.createElement('canvas');
78
+ canvas.width = containerW;
79
+ canvas.height = containerH;
80
+ const ctx = canvas.getContext('2d');
81
+ // Background color
82
+ ctx.fillStyle = bgColor;
83
+ ctx.fillRect(0, 0, containerW, containerH);
84
+ // Product image (contain-fit, centered)
85
+ if (productImg !== undefined) {
86
+ const { fittedW, fittedH } = computeContainFit(containerW, containerH, productImg.naturalWidth, productImg.naturalHeight);
87
+ const ox = (containerW - fittedW) / 2;
88
+ const oy = (containerH - fittedH) / 2;
89
+ ctx.drawImage(productImg, ox, oy, fittedW, fittedH);
90
+ }
91
+ // Logo layers
92
+ for (const layer of layers) {
93
+ ctx.save();
94
+ // Move to the center of the object (top-left + half natural size, matching CSS transform-origin: center)
95
+ const cx = layer.left + layer.naturalWidth / 2;
96
+ const cy = layer.top + layer.naturalHeight / 2;
97
+ ctx.translate(cx, cy);
98
+ // Apply transforms in Fabric.js order: rotate → skew → scale
99
+ ctx.rotate((layer.angle * Math.PI) / 180);
100
+ const tanSkX = Math.tan((layer.skewX * Math.PI) / 180);
101
+ const tanSkY = Math.tan((layer.skewY * Math.PI) / 180);
102
+ ctx.transform(1, tanSkY, tanSkX, 1, 0, 0);
103
+ ctx.scale(layer.scaleX, layer.scaleY);
104
+ // Draw image centered at origin
105
+ ctx.drawImage(layer.img, -layer.naturalWidth / 2, -layer.naturalHeight / 2, layer.naturalWidth, layer.naturalHeight);
106
+ ctx.restore();
107
+ }
108
+ return canvas.toDataURL(`image/${format}`, quality);
109
+ }
110
+
111
+ const wtpLogoRendererCss = () => `*.sc-wtp-logo-renderer,*.sc-wtp-logo-renderer::before,*.sc-wtp-logo-renderer::after{box-sizing:border-box}.sc-wtp-logo-renderer-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-logo-renderer.sc-wtp-logo-renderer{display:inline-block;position:relative;overflow:hidden;border:1px solid var(--wtp-color-border, #e2e8f0);border-radius:8px}.product-bg.sc-wtp-logo-renderer{display:block;width:100%;height:100%;object-fit:contain}.logo-layer.sc-wtp-logo-renderer{pointer-events:none}`;
112
+
113
+ const WtpLogoRenderer = class {
114
+ constructor(hostRef) {
115
+ registerInstance(this, hostRef);
116
+ this.wtpRenderComplete = createEvent(this, "wtpRenderComplete");
117
+ this.wtpRenderError = createEvent(this, "wtpRenderError");
118
+ }
119
+ get el() { return getElement(this); }
120
+ /** Product background image URL. */
121
+ productImage;
122
+ /** Container width in pixels. */
123
+ width = 600;
124
+ /** Container height in pixels. */
125
+ height = 400;
126
+ /** Array of logos to place on the renderer. */
127
+ logos = [];
128
+ /** Background color. */
129
+ backgroundColor = '#ffffff';
130
+ /** Print area definition for auto-fitting logos (relative 0-1 coordinates). */
131
+ printArea;
132
+ /** Fires when the renderer has finished rendering all logos. */
133
+ wtpRenderComplete;
134
+ /** Fires when a rendering error occurs. */
135
+ wtpRenderError;
136
+ layers = [];
137
+ containerWidth = 0;
138
+ containerHeight = 0;
139
+ productImg;
140
+ componentWillLoad() {
141
+ this.computeLayout();
142
+ }
143
+ onProductImageChange() {
144
+ this.computeLayout();
145
+ }
146
+ onLogosChange() {
147
+ this.computeLayout();
148
+ }
149
+ onPrintAreaChange() {
150
+ this.computeLayout();
151
+ }
152
+ onSizeChange() {
153
+ this.computeLayout();
154
+ }
155
+ onBackgroundColorChange() {
156
+ // Background color is applied via inline style in render(), so Stencil
157
+ // re-renders automatically. We still need to re-emit the export data URL.
158
+ this.emitRenderComplete();
159
+ }
160
+ /** Export the rendered scene as a data URL image. */
161
+ async exportImage(format = 'png', quality = 1) {
162
+ const w = this.containerWidth || this.width;
163
+ const h = this.containerHeight || this.height;
164
+ const exportLayers = this.layers.map(l => ({
165
+ img: l.exportImg,
166
+ naturalWidth: l.naturalWidth,
167
+ naturalHeight: l.naturalHeight,
168
+ left: l.left,
169
+ top: l.top,
170
+ scaleX: l.scaleX,
171
+ scaleY: l.scaleY,
172
+ angle: l.angle,
173
+ skewX: l.skewX,
174
+ skewY: l.skewY,
175
+ }));
176
+ return renderToCanvas(w, h, this.backgroundColor, this.productImg, exportLayers, format, quality);
177
+ }
178
+ async computeLayout() {
179
+ try {
180
+ let cw = this.width;
181
+ let ch = this.height;
182
+ // Load product image and compute contain-fit dimensions
183
+ if (this.productImage !== undefined && this.productImage !== '') {
184
+ const img = await loadImage(this.productImage);
185
+ this.productImg = img;
186
+ const { fittedW, fittedH } = computeContainFit(this.width, this.height, img.naturalWidth, img.naturalHeight);
187
+ cw = fittedW;
188
+ ch = fittedH;
189
+ }
190
+ else {
191
+ this.productImg = undefined;
192
+ }
193
+ this.containerWidth = cw;
194
+ this.containerHeight = ch;
195
+ const newLayers = [];
196
+ for (const logo of this.logos) {
197
+ const displayUrl = logo.previewDataUrl ?? logo.dataUrl;
198
+ if (logo.transform !== undefined) {
199
+ // Explicit transform path
200
+ const exportImg = await loadImage(logo.dataUrl);
201
+ const nw = exportImg.naturalWidth;
202
+ const nh = exportImg.naturalHeight;
203
+ const { left, top } = centerOriginToTopLeft(logo.transform, nw, nh);
204
+ newLayers.push({
205
+ id: logo.id,
206
+ src: displayUrl,
207
+ exportImg,
208
+ naturalWidth: nw,
209
+ naturalHeight: nh,
210
+ left,
211
+ top,
212
+ scaleX: logo.transform.scaleX,
213
+ scaleY: logo.transform.scaleY,
214
+ angle: logo.transform.angle,
215
+ skewX: logo.transform.skewX ?? 0,
216
+ skewY: logo.transform.skewY ?? 0,
217
+ });
218
+ }
219
+ else if (this.printArea !== undefined) {
220
+ // Auto-fit path: determine logo dimensions, fit into print area
221
+ const hasBulge = this.printArea.bulge !== undefined && this.printArea.bulge !== 0;
222
+ let logoW;
223
+ let logoH;
224
+ let displaySrc = displayUrl;
225
+ if (hasBulge) {
226
+ // Bulge path: upscale SVG first (rasters pass through unchanged), then
227
+ // compute fit from actual pixel dimensions — matches the old Fabric.js flow
228
+ // where FabricImage.fromURL loaded the upscaled image before fitLogoToPrintArea.
229
+ const rendererMaxSize = Math.round(Math.max(cw, ch) * 2);
230
+ const { dataUrl: processedUrl } = upscaleSvgDataUrl(logo.dataUrl, rendererMaxSize);
231
+ const warpSrc = await loadImage(processedUrl);
232
+ logoW = warpSrc.naturalWidth;
233
+ logoH = warpSrc.naturalHeight;
234
+ const transform = fitLogoToPrintArea(logoW, logoH, this.printArea, cw, ch);
235
+ const [tl, tr, br, bl] = printAreaToPixelCorners(this.printArea, cw, ch);
236
+ const leftH = Math.hypot(bl.x - tl.x, bl.y - tl.y);
237
+ const rightH = Math.hypot(br.x - tr.x, br.y - tr.y);
238
+ const avgHeight = (leftH + rightH) / 2;
239
+ const warped = warpImageForBulge(warpSrc, this.printArea.bulge, avgHeight, transform.scaleX);
240
+ displaySrc = warped.toDataURL('image/png');
241
+ // Warped canvas may be taller due to displacement padding
242
+ logoW = warped.width;
243
+ logoH = warped.height;
244
+ const exportImg = await loadImage(displaySrc);
245
+ const { left, top } = centerOriginToTopLeft(transform, logoW, logoH);
246
+ newLayers.push({
247
+ id: logo.id,
248
+ src: displaySrc,
249
+ exportImg,
250
+ naturalWidth: logoW,
251
+ naturalHeight: logoH,
252
+ left,
253
+ top,
254
+ scaleX: transform.scaleX,
255
+ scaleY: transform.scaleY,
256
+ angle: transform.angle,
257
+ skewX: 0,
258
+ skewY: 0,
259
+ });
260
+ }
261
+ else {
262
+ // No bulge: use intrinsic SVG dimensions (vector quality in <img>)
263
+ // or raster naturalWidth/Height
264
+ const svgSize = getSvgIntrinsicSize(logo.dataUrl);
265
+ if (svgSize !== null) {
266
+ logoW = svgSize.width;
267
+ logoH = svgSize.height;
268
+ }
269
+ else {
270
+ const tempImg = await loadImage(logo.dataUrl);
271
+ logoW = tempImg.naturalWidth;
272
+ logoH = tempImg.naturalHeight;
273
+ }
274
+ const transform = fitLogoToPrintArea(logoW, logoH, this.printArea, cw, ch);
275
+ const exportImg = await loadImage(displaySrc);
276
+ const { left, top } = centerOriginToTopLeft(transform, logoW, logoH);
277
+ newLayers.push({
278
+ id: logo.id,
279
+ src: displaySrc,
280
+ exportImg,
281
+ naturalWidth: logoW,
282
+ naturalHeight: logoH,
283
+ left,
284
+ top,
285
+ scaleX: transform.scaleX,
286
+ scaleY: transform.scaleY,
287
+ angle: transform.angle,
288
+ skewX: 0,
289
+ skewY: 0,
290
+ });
291
+ }
292
+ }
293
+ }
294
+ this.layers = newLayers;
295
+ this.emitRenderComplete();
296
+ }
297
+ catch (err) {
298
+ const message = err instanceof Error ? err.message : 'Unknown render error';
299
+ this.wtpRenderError.emit({ message });
300
+ }
301
+ }
302
+ async emitRenderComplete() {
303
+ try {
304
+ const dataUrl = await this.exportImage('png');
305
+ this.wtpRenderComplete.emit({ dataUrl });
306
+ }
307
+ catch {
308
+ // Export may fail if called before layout is ready; ignore silently
309
+ }
310
+ }
311
+ render() {
312
+ const renderW = this.containerWidth || this.width;
313
+ const renderH = this.containerHeight || this.height;
314
+ return (h("div", { key: '3a720dae0cd0ab070a90b1f20dff9adb699b0ba5', class: "wtp-logo-renderer", style: {
315
+ width: `${renderW}px`,
316
+ height: `${renderH}px`,
317
+ backgroundColor: this.backgroundColor,
318
+ } }, this.productImage !== undefined && this.productImage !== '' && (h("img", { key: '88b50d0a4fd320a935bd7008ef3ddfee1644cbab', class: "product-bg", src: this.productImage, alt: "" })), this.layers.map(layer => (h("img", { key: layer.id, class: "logo-layer", src: layer.src, alt: "", style: {
319
+ position: 'absolute',
320
+ left: `${layer.left}px`,
321
+ top: `${layer.top}px`,
322
+ width: `${layer.naturalWidth}px`,
323
+ height: `${layer.naturalHeight}px`,
324
+ transformOrigin: 'center',
325
+ transform: `rotate(${layer.angle}deg) skewX(${layer.skewX}deg) skewY(${layer.skewY}deg) scale(${layer.scaleX}, ${layer.scaleY})`,
326
+ } })))));
327
+ }
328
+ static get watchers() { return {
329
+ "productImage": [{
330
+ "onProductImageChange": 0
331
+ }],
332
+ "logos": [{
333
+ "onLogosChange": 0
334
+ }],
335
+ "printArea": [{
336
+ "onPrintAreaChange": 0
337
+ }],
338
+ "width": [{
339
+ "onSizeChange": 0
340
+ }],
341
+ "height": [{
342
+ "onSizeChange": 0
343
+ }],
344
+ "backgroundColor": [{
345
+ "onBackgroundColorChange": 0
346
+ }]
347
+ }; }
348
+ };
349
+ WtpLogoRenderer.style = wtpLogoRendererCss();
350
+
351
+ export { WtpLogoRenderer as wtp_logo_renderer };