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,141 @@
|
|
|
1
|
+
import ExifReader from "exifreader";
|
|
2
|
+
import { DEFAULT_VALIDATION_CONFIG } from "../types";
|
|
3
|
+
import { detectFileFormat, isSvgContent } from "./format-detection";
|
|
4
|
+
function createIssue(code, severity, message) {
|
|
5
|
+
return { code, severity, message };
|
|
6
|
+
}
|
|
7
|
+
async function extractDpi(file, format) {
|
|
8
|
+
if (format !== 'jpeg' && format !== 'tiff' && format !== 'png' && format !== 'avif') {
|
|
9
|
+
return { dpiX: null, dpiY: null };
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const buffer = await file.arrayBuffer();
|
|
13
|
+
const tags = ExifReader.load(buffer, { expanded: true });
|
|
14
|
+
// Try EXIF XResolution/YResolution
|
|
15
|
+
const xRes = tags.exif?.XResolution?.value;
|
|
16
|
+
const yRes = tags.exif?.YResolution?.value;
|
|
17
|
+
if (xRes !== undefined && xRes !== null && yRes !== undefined && yRes !== null) {
|
|
18
|
+
const dpiX = Array.isArray(xRes) ? xRes[0] / xRes[1] : Number(xRes);
|
|
19
|
+
const dpiY = Array.isArray(yRes) ? yRes[0] / yRes[1] : Number(yRes);
|
|
20
|
+
return { dpiX, dpiY };
|
|
21
|
+
}
|
|
22
|
+
// Try PNG pHYs chunk (pixels per meter -> DPI)
|
|
23
|
+
if (format === 'png' && 'pHYs' in tags) {
|
|
24
|
+
const pHYs = tags['pHYs'];
|
|
25
|
+
const pngX = pHYs?.['Pixels Per Unit X']?.value;
|
|
26
|
+
const pngY = pHYs?.['Pixels Per Unit Y']?.value;
|
|
27
|
+
const unit = pHYs?.['Unit']?.value;
|
|
28
|
+
if (pngX !== undefined && pngX !== null && pngY !== undefined && pngY !== null && unit === 1) {
|
|
29
|
+
return { dpiX: Math.round(Number(pngX) / 39.3701), dpiY: Math.round(Number(pngY) / 39.3701) };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { dpiX: null, dpiY: null };
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return { dpiX: null, dpiY: null };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function extractImageDimensions(file, format) {
|
|
39
|
+
if (format === 'svg') {
|
|
40
|
+
const text = await file.text();
|
|
41
|
+
const match = text.match(/viewBox=["'](\d+[\s,]+\d+[\s,]+(\d+(?:\.\d+)?)[\s,]+(\d+(?:\.\d+)?))["']/);
|
|
42
|
+
if (match !== null) {
|
|
43
|
+
return { width: Math.round(parseFloat(match[2])), height: Math.round(parseFloat(match[3])) };
|
|
44
|
+
}
|
|
45
|
+
const widthMatch = text.match(/width=["'](\d+(?:\.\d+)?)(?:px)?["']/);
|
|
46
|
+
const heightMatch = text.match(/height=["'](\d+(?:\.\d+)?)(?:px)?["']/);
|
|
47
|
+
return {
|
|
48
|
+
width: widthMatch !== null ? Math.round(parseFloat(widthMatch[1])) : 0,
|
|
49
|
+
height: heightMatch !== null ? Math.round(parseFloat(heightMatch[1])) : 0,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// For raster images, try to extract dimensions from EXIF/file header
|
|
53
|
+
try {
|
|
54
|
+
const buffer = await file.arrayBuffer();
|
|
55
|
+
const tags = ExifReader.load(buffer, { expanded: true });
|
|
56
|
+
const exifWidth = tags.file?.['Image Width']?.value ?? tags.exif?.ImageWidth?.value ?? tags.exif?.PixelXDimension?.value;
|
|
57
|
+
const exifHeight = tags.file?.['Image Height']?.value ?? tags.exif?.ImageLength?.value ?? tags.exif?.PixelYDimension?.value;
|
|
58
|
+
if (exifWidth !== undefined && exifWidth !== null && exifHeight !== undefined && exifHeight !== null) {
|
|
59
|
+
return { width: Number(exifWidth), height: Number(exifHeight) };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Fall through
|
|
64
|
+
}
|
|
65
|
+
// Fallback: use Image element if available (browser environment)
|
|
66
|
+
if (typeof globalThis.Image !== 'undefined') {
|
|
67
|
+
return new Promise(resolve => {
|
|
68
|
+
const url = URL.createObjectURL(file);
|
|
69
|
+
const img = new globalThis.Image();
|
|
70
|
+
img.onload = () => {
|
|
71
|
+
URL.revokeObjectURL(url);
|
|
72
|
+
resolve({ width: img.naturalWidth, height: img.naturalHeight });
|
|
73
|
+
};
|
|
74
|
+
img.onerror = () => {
|
|
75
|
+
URL.revokeObjectURL(url);
|
|
76
|
+
resolve({ width: 0, height: 0 });
|
|
77
|
+
};
|
|
78
|
+
img.src = url;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return { width: 0, height: 0 };
|
|
82
|
+
}
|
|
83
|
+
export async function validateLogo(file, config = DEFAULT_VALIDATION_CONFIG) {
|
|
84
|
+
const issues = [];
|
|
85
|
+
const format = await detectFileFormat(file);
|
|
86
|
+
// Format check
|
|
87
|
+
if (!config.allowedFormats.includes(format)) {
|
|
88
|
+
issues.push(createIssue('FORMAT_NOT_ALLOWED', 'error', `Format "${format}" is not allowed. Allowed: ${config.allowedFormats.join(', ')}`));
|
|
89
|
+
}
|
|
90
|
+
// File size check
|
|
91
|
+
if (file.size > config.maxFileSize) {
|
|
92
|
+
const maxMb = Math.round(config.maxFileSize / (1024 * 1024));
|
|
93
|
+
issues.push(createIssue('FILE_TOO_LARGE', 'error', `File size exceeds ${maxMb}MB limit`));
|
|
94
|
+
}
|
|
95
|
+
// SVG content validation
|
|
96
|
+
if (format === 'svg') {
|
|
97
|
+
const text = await file.text();
|
|
98
|
+
if (!isSvgContent(text)) {
|
|
99
|
+
issues.push(createIssue('INVALID_SVG', 'error', 'File does not contain valid SVG content'));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Extract metadata
|
|
103
|
+
const { dpiX, dpiY } = await extractDpi(file, format);
|
|
104
|
+
const { width, height } = await extractImageDimensions(file, format);
|
|
105
|
+
// DPI check for raster images
|
|
106
|
+
const isRaster = format === 'png' || format === 'jpeg' || format === 'tiff' || format === 'avif';
|
|
107
|
+
if (isRaster) {
|
|
108
|
+
if (dpiX === null || dpiY === null) {
|
|
109
|
+
issues.push(createIssue('DPI_UNKNOWN', 'warning', 'Could not determine image DPI. Recommended minimum is ' + config.minDpi + ' DPI'));
|
|
110
|
+
}
|
|
111
|
+
else if (dpiX < config.minDpi || dpiY < config.minDpi) {
|
|
112
|
+
const effectiveDpi = Math.min(dpiX, dpiY);
|
|
113
|
+
issues.push(createIssue('DPI_TOO_LOW', 'error', `Image DPI (${effectiveDpi}) is below minimum ${config.minDpi} DPI`));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Dimension check for raster images only (vector formats like SVG/PDF are resolution-independent)
|
|
117
|
+
if (isRaster) {
|
|
118
|
+
if (width > 0 && width < config.minWidth) {
|
|
119
|
+
issues.push(createIssue('WIDTH_TOO_SMALL', 'error', `Image width (${width}px) is below minimum ${config.minWidth}px`));
|
|
120
|
+
}
|
|
121
|
+
if (height > 0 && height < config.minHeight) {
|
|
122
|
+
issues.push(createIssue('HEIGHT_TOO_SMALL', 'error', `Image height (${height}px) is below minimum ${config.minHeight}px`));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const metadata = {
|
|
126
|
+
format,
|
|
127
|
+
width,
|
|
128
|
+
height,
|
|
129
|
+
dpiX,
|
|
130
|
+
dpiY,
|
|
131
|
+
fileSize: file.size,
|
|
132
|
+
fileName: file.name,
|
|
133
|
+
mimeType: file.type,
|
|
134
|
+
hasTransparency: format === 'png' || format === 'svg' || format === 'avif',
|
|
135
|
+
};
|
|
136
|
+
return {
|
|
137
|
+
valid: !issues.some(i => i.severity === 'error'),
|
|
138
|
+
metadata,
|
|
139
|
+
issues,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { printAreaToPixelCorners, upscaleSvgDataUrl } from "./canvas-helpers";
|
|
2
|
+
const DEFAULT_PDF_CONFIG = {
|
|
3
|
+
pageFormat: 'a4',
|
|
4
|
+
orientation: 'portrait',
|
|
5
|
+
marginMm: 15,
|
|
6
|
+
showPrintAreaGuides: true,
|
|
7
|
+
title: 'Logo Print Specification',
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Get the jsPDF constructor from the global scope.
|
|
11
|
+
* jsPDF must be loaded via script tag (e.g. from unpkg CDN) before calling exportProductPdf().
|
|
12
|
+
* This avoids bundling jsPDF into the Stencil build (which fails due to Rollup/CommonJS conflicts).
|
|
13
|
+
*/
|
|
14
|
+
function getJsPDF() {
|
|
15
|
+
const global = typeof window !== 'undefined' ? window : undefined;
|
|
16
|
+
const jspdfModule = global?.['jspdf'];
|
|
17
|
+
if (jspdfModule?.jsPDF != null) {
|
|
18
|
+
return jspdfModule.jsPDF;
|
|
19
|
+
}
|
|
20
|
+
throw new Error('jsPDF is required for PDF export. Load it via: <script src="https://unpkg.com/jspdf@4.1.0/dist/jspdf.umd.min.js"></script>');
|
|
21
|
+
}
|
|
22
|
+
/** Detect image format from a data URL for jsPDF's addImage(). */
|
|
23
|
+
export function dataUrlToImageFormat(dataUrl) {
|
|
24
|
+
if (dataUrl.startsWith('data:image/jpeg') || dataUrl.startsWith('data:image/jpg')) {
|
|
25
|
+
return 'JPEG';
|
|
26
|
+
}
|
|
27
|
+
return 'PNG';
|
|
28
|
+
}
|
|
29
|
+
/** Returns true if the data URL is an SVG (which jsPDF cannot embed directly). */
|
|
30
|
+
export function isSvgDataUrl(dataUrl) {
|
|
31
|
+
return dataUrl.startsWith('data:image/svg+xml');
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Rasterize a data URL to PNG via an offscreen canvas.
|
|
35
|
+
* SVG data URLs must be rasterized because jsPDF only supports PNG/JPEG.
|
|
36
|
+
* For SVGs, upscaleSvgDataUrl is applied first so the browser rasterizes
|
|
37
|
+
* the vector artwork at high resolution (maxSize px) instead of the often
|
|
38
|
+
* tiny intrinsic SVG dimensions.
|
|
39
|
+
* Non-SVG data URLs are returned unchanged.
|
|
40
|
+
*/
|
|
41
|
+
export function rasterizeDataUrl(dataUrl, maxSize = 4000) {
|
|
42
|
+
if (!isSvgDataUrl(dataUrl))
|
|
43
|
+
return Promise.resolve(dataUrl);
|
|
44
|
+
// Upscale SVG so the browser rasterizes vectors at high resolution
|
|
45
|
+
const { dataUrl: upscaledUrl } = upscaleSvgDataUrl(dataUrl, maxSize);
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const img = new Image();
|
|
48
|
+
img.onload = () => {
|
|
49
|
+
const w = img.naturalWidth || maxSize;
|
|
50
|
+
const h = img.naturalHeight || maxSize;
|
|
51
|
+
const canvas = document.createElement('canvas');
|
|
52
|
+
canvas.width = w;
|
|
53
|
+
canvas.height = h;
|
|
54
|
+
const ctx = canvas.getContext('2d');
|
|
55
|
+
if (ctx == null) {
|
|
56
|
+
reject(new Error('Failed to get 2D context for SVG rasterization'));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
ctx.drawImage(img, 0, 0, w, h);
|
|
60
|
+
resolve(canvas.toDataURL('image/png'));
|
|
61
|
+
};
|
|
62
|
+
img.onerror = () => reject(new Error('Failed to load image for rasterization'));
|
|
63
|
+
img.src = upscaledUrl;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/** Merge partial config with defaults. */
|
|
67
|
+
export function buildPdfConfig(partial) {
|
|
68
|
+
return { ...DEFAULT_PDF_CONFIG, ...partial };
|
|
69
|
+
}
|
|
70
|
+
/** Format a file size in bytes as a human-readable string. */
|
|
71
|
+
function formatFileSize(bytes) {
|
|
72
|
+
if (bytes < 1024)
|
|
73
|
+
return `${bytes} B`;
|
|
74
|
+
if (bytes < 1024 * 1024)
|
|
75
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
76
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
77
|
+
}
|
|
78
|
+
/** Render page 1: logo at full quality with metadata table. */
|
|
79
|
+
function renderLogoPage(doc, logo, rasterLogoDataUrl, config) {
|
|
80
|
+
const pageW = doc.internal.pageSize.getWidth();
|
|
81
|
+
const pageH = doc.internal.pageSize.getHeight();
|
|
82
|
+
const margin = config.marginMm;
|
|
83
|
+
const contentW = pageW - 2 * margin;
|
|
84
|
+
// Title
|
|
85
|
+
doc.setFontSize(18);
|
|
86
|
+
doc.setFont('helvetica', 'bold');
|
|
87
|
+
doc.text(config.title, pageW / 2, margin + 8, { align: 'center' });
|
|
88
|
+
// Logo image — centered, filling available width while maintaining aspect ratio
|
|
89
|
+
const logoFormat = dataUrlToImageFormat(rasterLogoDataUrl);
|
|
90
|
+
const logoY = margin + 16;
|
|
91
|
+
const maxLogoH = pageH * 0.5;
|
|
92
|
+
const aspectRatio = logo.metadata.width / Math.max(logo.metadata.height, 1);
|
|
93
|
+
let imgW = contentW;
|
|
94
|
+
let imgH = imgW / aspectRatio;
|
|
95
|
+
if (imgH > maxLogoH) {
|
|
96
|
+
imgH = maxLogoH;
|
|
97
|
+
imgW = imgH * aspectRatio;
|
|
98
|
+
}
|
|
99
|
+
const imgX = (pageW - imgW) / 2;
|
|
100
|
+
doc.addImage(rasterLogoDataUrl, logoFormat, imgX, logoY, imgW, imgH);
|
|
101
|
+
// Metadata table below the logo
|
|
102
|
+
const tableY = logoY + imgH + 10;
|
|
103
|
+
const meta = logo.metadata;
|
|
104
|
+
const rows = [
|
|
105
|
+
['Format', meta.format.toUpperCase()],
|
|
106
|
+
['Dimensions', `${meta.width} x ${meta.height} px`],
|
|
107
|
+
['DPI', meta.dpiX !== null ? `${meta.dpiX} x ${meta.dpiY ?? meta.dpiX}` : 'Not available'],
|
|
108
|
+
['File Size', formatFileSize(meta.fileSize)],
|
|
109
|
+
['Transparency', meta.hasTransparency ? 'Yes' : 'No'],
|
|
110
|
+
['File Name', meta.fileName],
|
|
111
|
+
];
|
|
112
|
+
doc.setFontSize(10);
|
|
113
|
+
const rowH = 7;
|
|
114
|
+
const col1W = 30;
|
|
115
|
+
rows.forEach((row, i) => {
|
|
116
|
+
const y = tableY + i * rowH;
|
|
117
|
+
doc.setFont('helvetica', 'bold');
|
|
118
|
+
doc.text(row[0], margin, y);
|
|
119
|
+
doc.setFont('helvetica', 'normal');
|
|
120
|
+
doc.text(row[1], margin + col1W, y);
|
|
121
|
+
});
|
|
122
|
+
// Separator line above the table
|
|
123
|
+
doc.setDrawColor(200, 200, 200);
|
|
124
|
+
doc.setLineWidth(0.3);
|
|
125
|
+
doc.line(margin, tableY - 4, margin + contentW, tableY - 4);
|
|
126
|
+
}
|
|
127
|
+
/** Draw dashed print area guide overlay on the product mockup. */
|
|
128
|
+
function drawPrintAreaGuide(doc, printArea, imgX, imgY, imgW, imgH, canvasW, canvasH) {
|
|
129
|
+
const corners = printAreaToPixelCorners(printArea, canvasW, canvasH);
|
|
130
|
+
const scaleX = imgW / canvasW;
|
|
131
|
+
const scaleY = imgH / canvasH;
|
|
132
|
+
const pdfCorners = corners.map(c => ({
|
|
133
|
+
x: imgX + c.x * scaleX,
|
|
134
|
+
y: imgY + c.y * scaleY,
|
|
135
|
+
}));
|
|
136
|
+
doc.setDrawColor(37, 99, 235);
|
|
137
|
+
doc.setLineWidth(0.4);
|
|
138
|
+
doc.setLineDashPattern([2, 1.5], 0);
|
|
139
|
+
for (let i = 0; i < 4; i++) {
|
|
140
|
+
const from = pdfCorners[i];
|
|
141
|
+
const to = pdfCorners[(i + 1) % 4];
|
|
142
|
+
doc.line(from.x, from.y, to.x, to.y);
|
|
143
|
+
}
|
|
144
|
+
doc.setLineDashPattern([], 0);
|
|
145
|
+
}
|
|
146
|
+
/** Render page 2: product mockup with header and print area guides. */
|
|
147
|
+
function renderProductPage(doc, article, viewIndex, mockupDataUrl, canvasW, canvasH, config) {
|
|
148
|
+
const pageW = doc.internal.pageSize.getWidth();
|
|
149
|
+
const pageH = doc.internal.pageSize.getHeight();
|
|
150
|
+
const margin = config.marginMm;
|
|
151
|
+
const contentW = pageW - 2 * margin;
|
|
152
|
+
doc.setFontSize(16);
|
|
153
|
+
doc.setFont('helvetica', 'bold');
|
|
154
|
+
doc.text(article.name, pageW / 2, margin + 8, { align: 'center' });
|
|
155
|
+
// Article details table
|
|
156
|
+
const view = article.views[viewIndex];
|
|
157
|
+
const detailRows = [['Article ID', article.id]];
|
|
158
|
+
if (view?.impMethod)
|
|
159
|
+
detailRows.push(['Print Method', view.impMethod]);
|
|
160
|
+
if (view?.impLocation)
|
|
161
|
+
detailRows.push(['Print Location', view.impLocation]);
|
|
162
|
+
if (view?.impDiameterMm && view.impDiameterMm > 0) {
|
|
163
|
+
detailRows.push(['Print Area', `\u00D8 ${view.impDiameterMm} mm`]);
|
|
164
|
+
}
|
|
165
|
+
else if (view?.impWidthMm && view?.impHeightMm) {
|
|
166
|
+
detailRows.push(['Print Area', `${view.impWidthMm} \u00D7 ${view.impHeightMm} mm`]);
|
|
167
|
+
}
|
|
168
|
+
if (view?.maxColours)
|
|
169
|
+
detailRows.push(['Max Colors', `${view.maxColours}`]);
|
|
170
|
+
const detailRowH = 6;
|
|
171
|
+
const detailCol1W = 30;
|
|
172
|
+
const detailTableY = margin + 16;
|
|
173
|
+
doc.setFontSize(9);
|
|
174
|
+
detailRows.forEach((row, i) => {
|
|
175
|
+
const y = detailTableY + i * detailRowH;
|
|
176
|
+
doc.setFont('helvetica', 'bold');
|
|
177
|
+
doc.text(row[0], margin, y);
|
|
178
|
+
doc.setFont('helvetica', 'normal');
|
|
179
|
+
doc.text(row[1], margin + detailCol1W, y);
|
|
180
|
+
});
|
|
181
|
+
doc.setDrawColor(200, 200, 200);
|
|
182
|
+
doc.setLineWidth(0.3);
|
|
183
|
+
doc.line(margin, detailTableY + detailRows.length * detailRowH + 2, margin + contentW, detailTableY + detailRows.length * detailRowH + 2);
|
|
184
|
+
const mockupFormat = dataUrlToImageFormat(mockupDataUrl);
|
|
185
|
+
const mockupY = detailTableY + detailRows.length * detailRowH + 6;
|
|
186
|
+
const maxMockupH = pageH - mockupY - margin;
|
|
187
|
+
const aspectRatio = canvasW / Math.max(canvasH, 1);
|
|
188
|
+
let imgW = contentW;
|
|
189
|
+
let imgH = imgW / aspectRatio;
|
|
190
|
+
if (imgH > maxMockupH) {
|
|
191
|
+
imgH = maxMockupH;
|
|
192
|
+
imgW = imgH * aspectRatio;
|
|
193
|
+
}
|
|
194
|
+
const imgX = (pageW - imgW) / 2;
|
|
195
|
+
doc.addImage(mockupDataUrl, mockupFormat, imgX, mockupY, imgW, imgH);
|
|
196
|
+
if (config.showPrintAreaGuides && view?.printArea != null) {
|
|
197
|
+
drawPrintAreaGuide(doc, view.printArea, imgX, mockupY, imgW, imgH, canvasW, canvasH);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Generate and download a print-shop-ready PDF for a product.
|
|
202
|
+
*
|
|
203
|
+
* Page 1: Logo at full quality with metadata table.
|
|
204
|
+
* Page 2: High-resolution product mockup with print area guides.
|
|
205
|
+
*
|
|
206
|
+
* Requires jsPDF to be loaded globally via script tag before calling this function.
|
|
207
|
+
*/
|
|
208
|
+
export async function exportProductPdf(logo, article, viewIndex, productMockupDataUrl, canvasWidth, canvasHeight, config) {
|
|
209
|
+
const cfg = buildPdfConfig(config);
|
|
210
|
+
const JsPDF = getJsPDF();
|
|
211
|
+
// Rasterize SVG data URLs — jsPDF only accepts PNG/JPEG
|
|
212
|
+
const rasterLogoDataUrl = await rasterizeDataUrl(logo.dataUrl);
|
|
213
|
+
const doc = new JsPDF({
|
|
214
|
+
orientation: cfg.orientation,
|
|
215
|
+
unit: 'mm',
|
|
216
|
+
format: cfg.pageFormat,
|
|
217
|
+
});
|
|
218
|
+
// Page 1: Logo source
|
|
219
|
+
renderLogoPage(doc, logo, rasterLogoDataUrl, cfg);
|
|
220
|
+
// Page 2: Product mockup
|
|
221
|
+
doc.addPage();
|
|
222
|
+
renderProductPage(doc, article, viewIndex, productMockupDataUrl, canvasWidth, canvasHeight, cfg);
|
|
223
|
+
doc.save(`${article.id}.pdf`);
|
|
224
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the base path to where the assets can be found. Use "setAssetPath(path)"
|
|
3
|
+
* if the path needs to be customized.
|
|
4
|
+
*/
|
|
5
|
+
export declare const getAssetPath: (path: string) => string;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Used to manually set the base path where assets can be found.
|
|
9
|
+
* If the script is used as "module", it's recommended to use "import.meta.url",
|
|
10
|
+
* such as "setAssetPath(import.meta.url)". Other options include
|
|
11
|
+
* "setAssetPath(document.currentScript.src)", or using a bundler's replace plugin to
|
|
12
|
+
* dynamically set the path at build time, such as "setAssetPath(process.env.ASSET_PATH)".
|
|
13
|
+
* But do note that this configuration depends on how your script is bundled, or lack of
|
|
14
|
+
* bundling, and where your assets can be loaded from. Additionally custom bundling
|
|
15
|
+
* will have to ensure the static assets are copied to its build directory.
|
|
16
|
+
*/
|
|
17
|
+
export declare const setAssetPath: (path: string) => void;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Used to specify a nonce value that corresponds with an application's CSP.
|
|
21
|
+
* When set, the nonce will be added to all dynamically created script and style tags at runtime.
|
|
22
|
+
* Alternatively, the nonce value can be set on a meta tag in the DOM head
|
|
23
|
+
* (<meta name="csp-nonce" content="{ nonce value here }" />) which
|
|
24
|
+
* will result in the same behavior.
|
|
25
|
+
*/
|
|
26
|
+
export declare const setNonce: (nonce: string) => void
|
|
27
|
+
|
|
28
|
+
export interface SetPlatformOptions {
|
|
29
|
+
raf?: (c: FrameRequestCallback) => number;
|
|
30
|
+
ael?: (el: EventTarget, eventName: string, listener: EventListenerOrEventListenerObject, options: boolean | AddEventListenerOptions) => void;
|
|
31
|
+
rel?: (el: EventTarget, eventName: string, listener: EventListenerOrEventListenerObject, options: boolean | AddEventListenerOptions) => void;
|
|
32
|
+
}
|
|
33
|
+
export declare const setPlatformOptions: (opts: SetPlatformOptions) => void;
|
|
34
|
+
|
|
35
|
+
export * from '../types';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{u as t,p as n}from"./p-5qCsRzlt.js";export{g as getAssetPath,r as render,s as setAssetPath,a as setNonce,b as setPlatformOptions}from"./p-5qCsRzlt.js";export{a as DEFAULT_BG_REMOVAL_CONFIG,D as DEFAULT_VALIDATION_CONFIG}from"./p-D8pVJRuX.js";const o={pageFormat:"a4",orientation:"portrait",marginMm:15,showPrintAreaGuides:!0,title:"Logo Print Specification"};function e(t){return t.startsWith("data:image/jpeg")||t.startsWith("data:image/jpg")?"JPEG":"PNG"}function i(t){return t<1024?t+" B":t<1048576?(t/1024).toFixed(1)+" KB":(t/1048576).toFixed(1)+" MB"}async function c(r,a,s,c,l,m,u){const d=function(t){return{...o,...t}}(u),f=function(){const t="undefined"!=typeof window?window:void 0,n=t?.jspdf;if(null!=n?.jsPDF)return n.jsPDF;throw Error('jsPDF is required for PDF export. Load it via: <script src="https://unpkg.com/jspdf@4.1.0/dist/jspdf.umd.min.js"><\/script>')}(),p=await function(n,o=4e3){if(!function(t){return t.startsWith("data:image/svg+xml")}(n))return Promise.resolve(n);const{dataUrl:e}=t(n,o);return new Promise(((t,n)=>{const r=new Image;r.onload=()=>{const e=r.naturalWidth||o,a=r.naturalHeight||o,i=document.createElement("canvas");i.width=e,i.height=a;const s=i.getContext("2d");null!=s?(s.drawImage(r,0,0,e,a),t(i.toDataURL("image/png"))):n(Error("Failed to get 2D context for SVG rasterization"))},r.onerror=()=>n(Error("Failed to load image for rasterization")),r.src=e}))}(r.dataUrl),g=new f({orientation:d.orientation,unit:"mm",format:d.pageFormat});!function(t,n,o,r){const a=t.internal.pageSize.getWidth(),s=t.internal.pageSize.getHeight(),c=r.marginMm,l=a-2*c;t.setFontSize(18),t.setFont("helvetica","bold"),t.text(r.title,a/2,c+8,{align:"center"});const m=e(o),u=c+16,d=.5*s,f=n.metadata.width/Math.max(n.metadata.height,1);let p=l,g=p/f;g>d&&(g=d,p=g*f),t.addImage(o,m,(a-p)/2,u,p,g);const P=u+g+10,h=n.metadata,F=[["Format",h.format.toUpperCase()],["Dimensions",`${h.width} x ${h.height} px`],["DPI",null!==h.dpiX?`${h.dpiX} x ${h.dpiY??h.dpiX}`:"Not available"],["File Size",i(h.fileSize)],["Transparency",h.hasTransparency?"Yes":"No"],["File Name",h.fileName]];t.setFontSize(10),F.forEach(((n,o)=>{const e=P+7*o;t.setFont("helvetica","bold"),t.text(n[0],c,e),t.setFont("helvetica","normal"),t.text(n[1],c+30,e)})),t.setDrawColor(200,200,200),t.setLineWidth(.3),t.line(c,P-4,c+l,P-4)}(g,r,p,d),g.addPage(),function(t,o,r,a,i,s,c){const l=t.internal.pageSize.getWidth(),m=t.internal.pageSize.getHeight(),u=c.marginMm,d=l-2*u;t.setFontSize(16),t.setFont("helvetica","bold"),t.text(o.name,l/2,u+8,{align:"center"});const f=o.views[r],p=[["Article ID",o.id]];f?.impMethod&&p.push(["Print Method",f.impMethod]),f?.impLocation&&p.push(["Print Location",f.impLocation]),f?.impDiameterMm&&f.impDiameterMm>0?p.push(["Print Area",`Ø ${f.impDiameterMm} mm`]):f?.impWidthMm&&f?.impHeightMm&&p.push(["Print Area",`${f.impWidthMm} × ${f.impHeightMm} mm`]),f?.maxColours&&p.push(["Max Colors",""+f.maxColours]);const g=u+16;t.setFontSize(9),p.forEach(((n,o)=>{const e=g+6*o;t.setFont("helvetica","bold"),t.text(n[0],u,e),t.setFont("helvetica","normal"),t.text(n[1],u+30,e)})),t.setDrawColor(200,200,200),t.setLineWidth(.3),t.line(u,g+6*p.length+2,u+d,g+6*p.length+2);const P=e(a),h=g+6*p.length+6,F=m-h-u,v=i/Math.max(s,1);let x=d,A=x/v;A>F&&(A=F,x=A*v);const D=(l-x)/2;t.addImage(a,P,D,h,x,A),c.showPrintAreaGuides&&null!=f?.printArea&&function(t,o,e,r,a,i,s,c){const l=n(o,s,c),m=a/s,u=i/c,d=l.map((t=>({x:e+t.x*m,y:r+t.y*u})));t.setDrawColor(37,99,235),t.setLineWidth(.4),t.setLineDashPattern([2,1.5],0);for(let n=0;n<4;n++){const o=d[n],e=d[(n+1)%4];t.line(o.x,o.y,e.x,e.y)}t.setLineDashPattern([],0)}(t,f.printArea,D,h,x,A,i,s)}(g,a,s,c,l,m,d),g.save(a.id+".pdf")}export{c as exportProductPdf}
|