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,79 @@
1
+ export interface CanvasTransform {
2
+ x: number;
3
+ y: number;
4
+ scaleX: number;
5
+ scaleY: number;
6
+ angle: number;
7
+ skewX?: number;
8
+ skewY?: number;
9
+ }
10
+ export interface PlacedLogo {
11
+ id: string;
12
+ dataUrl: string;
13
+ /** Downscaled preview for product catalog rendering (optional). */
14
+ previewDataUrl?: string;
15
+ transform?: CanvasTransform;
16
+ }
17
+ /** A point in 0-1 relative coordinates (fraction of canvas width/height). */
18
+ export interface RelativePoint {
19
+ x: number;
20
+ y: number;
21
+ }
22
+ /** Print area defined by 4 independent corner points (any quadrilateral). */
23
+ export interface PrintArea {
24
+ /** Top-left corner (0-1, relative to canvas) */
25
+ topLeft: RelativePoint;
26
+ /** Top-right corner (0-1, relative to canvas) */
27
+ topRight: RelativePoint;
28
+ /** Bottom-right corner (0-1, relative to canvas) */
29
+ bottomRight: RelativePoint;
30
+ /** Bottom-left corner (0-1, relative to canvas) */
31
+ bottomLeft: RelativePoint;
32
+ /** Top/bottom edge curvature (-1 to 1; positive = outward/convex, negative = inward/concave, default: 0) */
33
+ bulge?: number;
34
+ }
35
+ /** Legacy print area format (center + dimensions + transforms) for migration. */
36
+ export interface LegacyPrintArea {
37
+ x: number;
38
+ y: number;
39
+ width: number;
40
+ height: number;
41
+ angle?: number;
42
+ skewX?: number;
43
+ skewY?: number;
44
+ bulge?: number;
45
+ taper?: number;
46
+ }
47
+ export interface PlacedText {
48
+ id: string;
49
+ text: string;
50
+ fontFamily: string;
51
+ fontSize: number;
52
+ fill: string;
53
+ transform: CanvasTransform;
54
+ }
55
+ export interface EditorState {
56
+ fabricJson: string;
57
+ logos: PlacedLogo[];
58
+ texts: PlacedText[];
59
+ productImage: string | null;
60
+ width: number;
61
+ height: number;
62
+ }
63
+ export interface ArticleView {
64
+ image: string;
65
+ label: string;
66
+ printArea: PrintArea | null;
67
+ impMethod?: string;
68
+ impLocation?: string;
69
+ impWidthMm?: number;
70
+ impHeightMm?: number;
71
+ impDiameterMm?: number;
72
+ maxColours?: number;
73
+ }
74
+ export interface Article {
75
+ id: string;
76
+ name: string;
77
+ description: string;
78
+ views: ArticleView[];
79
+ }
@@ -0,0 +1,5 @@
1
+ export type { LogoFormat, LogoMetadata, LogoValidationIssue, LogoValidationConfig, LogoValidationResult, LogoData, BgRemovalConfig } from './logo';
2
+ export { DEFAULT_VALIDATION_CONFIG, DEFAULT_BG_REMOVAL_CONFIG } from './logo';
3
+ export type { CanvasTransform, PlacedLogo, PlacedText, EditorState, PrintArea, RelativePoint, LegacyPrintArea, ArticleView, Article } from './editor';
4
+ export type { LogoUploadLabels, EditorLabels } from './labels';
5
+ export { DEFAULT_LOGO_UPLOAD_LABELS, DEFAULT_EDITOR_LABELS } from './labels';
@@ -0,0 +1,30 @@
1
+ export interface LogoUploadLabels {
2
+ urlPlaceholder: string;
3
+ urlSubmit: string;
4
+ dividerText: string;
5
+ dropPromptText: string;
6
+ dropPromptHint: string;
7
+ uploadAriaLabel: string;
8
+ removeAriaLabel: (fileName: string) => string;
9
+ urlErrorEmpty: string;
10
+ urlErrorInvalid: string;
11
+ urlErrorProtocol: string;
12
+ urlErrorHttp: (status: number, statusText: string) => string;
13
+ urlErrorNetwork: string;
14
+ urlErrorFetch: string;
15
+ bgRemovalProcessing: string;
16
+ bgRemovalUseOriginal: string;
17
+ bgRemovalUseRemoved: string;
18
+ bgRemovalFailed: string;
19
+ rejectionDpiUnit: string;
20
+ }
21
+ export declare const DEFAULT_LOGO_UPLOAD_LABELS: LogoUploadLabels;
22
+ export interface EditorLabels {
23
+ addTextButton: string;
24
+ addTextTooltip: string;
25
+ fontSelectTooltip: string;
26
+ colorPickerTooltip: string;
27
+ deleteButtonTooltip: string;
28
+ defaultText: string;
29
+ }
30
+ export declare const DEFAULT_EDITOR_LABELS: EditorLabels;
@@ -0,0 +1,47 @@
1
+ export type LogoFormat = 'png' | 'jpeg' | 'svg' | 'tiff' | 'avif' | 'unknown';
2
+ export interface LogoMetadata {
3
+ format: LogoFormat;
4
+ width: number;
5
+ height: number;
6
+ dpiX: number | null;
7
+ dpiY: number | null;
8
+ fileSize: number;
9
+ fileName: string;
10
+ mimeType: string;
11
+ hasTransparency: boolean;
12
+ }
13
+ export interface LogoValidationIssue {
14
+ code: string;
15
+ severity: 'error' | 'warning';
16
+ message: string;
17
+ }
18
+ export interface LogoValidationConfig {
19
+ minDpi: number;
20
+ maxFileSize: number;
21
+ minWidth: number;
22
+ minHeight: number;
23
+ allowedFormats: LogoFormat[];
24
+ }
25
+ export interface LogoValidationResult {
26
+ valid: boolean;
27
+ metadata: LogoMetadata;
28
+ issues: LogoValidationIssue[];
29
+ }
30
+ export interface LogoData {
31
+ dataUrl: string;
32
+ /** Downscaled preview for product catalog rendering (optional, for backward compat). */
33
+ previewDataUrl?: string;
34
+ metadata: LogoMetadata;
35
+ }
36
+ export declare const DEFAULT_VALIDATION_CONFIG: LogoValidationConfig;
37
+ export interface BgRemovalConfig {
38
+ /** Color distance threshold (0-255). Default: 40 */
39
+ tolerance: number;
40
+ /** Min fraction of edge pixels sharing a color to count as background. Default: 0.3 */
41
+ minEdgeRatio: number;
42
+ /** Trim result to opaque content bounding box after background removal. Default: true */
43
+ autoCrop: boolean;
44
+ /** Padding in pixels around the opaque bounding box when auto-cropping. Default: 2 */
45
+ autoCropPadding: number;
46
+ }
47
+ export declare const DEFAULT_BG_REMOVAL_CONFIG: BgRemovalConfig;
@@ -0,0 +1,95 @@
1
+ import { BgRemovalConfig } from '../types';
2
+ export interface BgRemovalResult {
3
+ blob: Blob;
4
+ dataUrl: string;
5
+ width: number;
6
+ height: number;
7
+ }
8
+ /** Euclidean distance between two RGB colors. */
9
+ export declare function colorDistance(r1: number, g1: number, b1: number, r2: number, g2: number, b2: number): number;
10
+ export interface PixelData {
11
+ data: Uint8ClampedArray;
12
+ width: number;
13
+ height: number;
14
+ }
15
+ /**
16
+ * Find the dominant color among a list of RGB samples.
17
+ *
18
+ * Phase 1: Coarse quantization (step 24) to find the candidate color cluster center.
19
+ * Phase 2: Count all samples within `tolerance` Euclidean RGB distance of that center.
20
+ * This two-phase approach handles JPEG artifacts and gradients that split across
21
+ * fine bucket boundaries.
22
+ *
23
+ * Returns [r, g, b] or null if no dominant color is found.
24
+ */
25
+ export declare function findDominantColor(samples: [number, number, number][], minRatio: number, tolerance?: number): [number, number, number] | null;
26
+ /**
27
+ * Sample all pixels along the 4 image edges.
28
+ * Returns array of [r, g, b] samples.
29
+ */
30
+ export declare function sampleImageEdges(pixels: PixelData): [number, number, number][];
31
+ /**
32
+ * Sample opaque pixels that border transparent pixels (the transparency boundary).
33
+ * Returns array of [r, g, b] samples.
34
+ */
35
+ export declare function sampleTransparencyBoundary(pixels: PixelData): [number, number, number][];
36
+ /** Backward-compatible wrapper: detect dominant color along image edges. */
37
+ export declare function detectBackgroundColor(pixels: PixelData, minEdgeRatio: number, tolerance?: number): [number, number, number] | null;
38
+ /**
39
+ * BFS flood-fill from all edge pixels that match the background color within tolerance.
40
+ * Uses 8-directional connectivity for better coverage through diagonal gaps.
41
+ * Sets alpha to 0 for all connected matching pixels.
42
+ * Modifies `data` in place.
43
+ */
44
+ export declare function floodFillFromEdges(pixels: PixelData, bgColor: [number, number, number], tolerance: number): void;
45
+ /**
46
+ * BFS flood-fill inward from the transparency boundary (opaque pixels adjacent to transparent).
47
+ * Removes connected pixels matching bgColor within tolerance.
48
+ * Modifies `data` in place. Returns the number of pixels removed.
49
+ */
50
+ export declare function floodFillFromBoundary(pixels: PixelData, bgColor: [number, number, number], tolerance: number): number;
51
+ /**
52
+ * Remove all opaque pixels matching `color` within `tolerance` (color-key removal).
53
+ * Unlike flood-fill, this does not require connectivity — it removes matching pixels
54
+ * anywhere in the image. Useful for cleaning up trapped interior regions and bypassing
55
+ * anti-aliased fringes that block BFS-based approaches.
56
+ * Modifies `data` in place. Returns the number of pixels removed.
57
+ */
58
+ export declare function removeMatchingPixels(pixels: PixelData, color: [number, number, number], tolerance: number): number;
59
+ /**
60
+ * Remove bg-colored pixel clusters that are fully enclosed by non-bg opaque pixels
61
+ * (no pixel in the cluster is adjacent to a transparent pixel). These are trapped
62
+ * interior regions — e.g. white background inside a letter 'O' — that edge flood fill
63
+ * can't reach because the foreground forms a closed boundary.
64
+ * Unlike removeMatchingPixels, this preserves bg-colored pixels at foreground edges
65
+ * (anti-aliased transitions), preventing the inner-pass safety check from failing.
66
+ * Modifies `data` in place. Returns the number of pixels removed.
67
+ */
68
+ export declare function removeEnclosedBackground(pixels: PixelData, bgColor: [number, number, number], tolerance: number): number;
69
+ /**
70
+ * Anti-alias edges between transparent (removed) and opaque (kept) pixels.
71
+ * For each opaque pixel adjacent to a transparent pixel, set partial alpha
72
+ * based on its color distance from the background — creating smooth transitions.
73
+ * Modifies `data` in place.
74
+ */
75
+ export declare function antiAliasEdges(pixels: PixelData, bgColor: [number, number, number], tolerance: number): void;
76
+ /**
77
+ * Compute the fraction of non-matching opaque pixels that sit on the transparency
78
+ * boundary (adjacent to a transparent pixel). A high fraction indicates the non-matching
79
+ * pixels are anti-aliased foreground edges, meaning the matching color IS the foreground
80
+ * content and should not be removed.
81
+ */
82
+ export declare function nonMatchingBoundaryFraction(pixels: PixelData, color: [number, number, number], tolerance: number): number;
83
+ /**
84
+ * Compute the fraction of matching pixels in the largest connected component.
85
+ * Text consists of multiple disconnected components (individual letters);
86
+ * background panels are one large contiguous region.
87
+ * Uses 8-directional connectivity for consistency with flood fill.
88
+ */
89
+ export declare function largestMatchingComponentFraction(pixels: PixelData, color: [number, number, number], tolerance: number, matchCount: number): number;
90
+ /**
91
+ * Find the bounding box of all opaque pixels (alpha > threshold).
92
+ * Returns [minX, minY, maxX, maxY] or null if no opaque pixels are found.
93
+ */
94
+ export declare function findOpaqueBoundingBox(pixels: PixelData, alphaThreshold?: number): [number, number, number, number] | null;
95
+ export declare function removeBackground(source: File | Blob, config?: Partial<BgRemovalConfig>): Promise<BgRemovalResult>;
@@ -0,0 +1,60 @@
1
+ import { StaticCanvas, FabricObject } from 'fabric';
2
+ import { CanvasTransform, PrintArea, LegacyPrintArea, RelativePoint } from '../types';
3
+ export declare function generateObjectId(): string;
4
+ export declare function setCanvasBackground(canvas: StaticCanvas, imageUrl: string, fitMode?: 'cover' | 'contain' | 'fill'): Promise<void>;
5
+ /** Convert PrintArea corner coordinates (0-1) to absolute pixel positions. */
6
+ export declare function printAreaToPixelCorners(pa: PrintArea, canvasW: number, canvasH: number): [RelativePoint, RelativePoint, RelativePoint, RelativePoint];
7
+ /** Convert absolute pixel corner positions back to 0-1 relative PrintArea. */
8
+ export declare function pixelCornersToPrintArea(corners: [RelativePoint, RelativePoint, RelativePoint, RelativePoint], canvasW: number, canvasH: number, bulge?: number): PrintArea;
9
+ /**
10
+ * Convert a legacy center+dimensions print area to the 4-corner format.
11
+ * Applies taper, skew, and rotation in 0-1 coordinate space.
12
+ */
13
+ export declare function legacyToPrintArea(legacy: LegacyPrintArea): PrintArea;
14
+ /** Detect whether a PrintArea uses pixel coordinates (at least one value > 1). */
15
+ export declare function isPixelPrintArea(pa: PrintArea): boolean;
16
+ /** Normalize a PrintArea from pixel coordinates to 0–1 relative values. Already-normalized areas are returned unchanged. */
17
+ export declare function normalizePrintArea(pa: PrintArea, imageWidth: number, imageHeight: number): PrintArea;
18
+ /** Returns a default centered print area (30% width x 35% height rectangle). */
19
+ export declare function defaultPrintArea(): PrintArea;
20
+ /**
21
+ * Compute a CanvasTransform to fit a logo within a 4-corner print area.
22
+ * Uses the centroid for position, average edge lengths for dimensions,
23
+ * and the bottom edge angle for rotation.
24
+ */
25
+ export declare function fitLogoToPrintArea(logoWidth: number, logoHeight: number, printArea: PrintArea, canvasWidth: number, canvasHeight: number): CanvasTransform;
26
+ /**
27
+ * Warp a logo image to follow the bulge curvature of a print area.
28
+ * Slices the image into vertical strips and displaces each vertically
29
+ * according to the same quadratic Bezier curve used by the print area outline.
30
+ */
31
+ export declare function warpImageForBulge(sourceImg: HTMLImageElement | HTMLCanvasElement, bulge: number, areaHeight: number, logoScale: number): HTMLCanvasElement;
32
+ /**
33
+ * Extract width/height from an SVG data URL by parsing viewBox or width/height attributes.
34
+ * Returns null for non-SVG data URLs or when dimensions cannot be determined.
35
+ */
36
+ export declare function parseSvgDimensions(svgDataUrl: string): {
37
+ width: number;
38
+ height: number;
39
+ } | null;
40
+ /**
41
+ * Upscale an SVG data URL so the browser rasterizes it at high resolution.
42
+ * SVGs loaded via `<img src>` are rasterized at their intrinsic dimensions
43
+ * (from viewBox or width/height attributes), which are often small. This
44
+ * function sets explicit width/height on the root `<svg>` element to ensure
45
+ * high-resolution rasterization when loaded into Fabric.js.
46
+ *
47
+ * Returns the (possibly modified) data URL and the uniform scale factor applied.
48
+ * For non-SVG data URLs, returns the input unchanged with scaleApplied = 1.
49
+ */
50
+ export declare function upscaleSvgDataUrl(svgDataUrl: string, maxSize?: number): {
51
+ dataUrl: string;
52
+ scaleApplied: number;
53
+ };
54
+ /**
55
+ * Trim excess whitespace from an SVG by adjusting its viewBox to the actual content bounds.
56
+ * Uses `getBBox()` which requires the SVG to be temporarily inserted into the DOM.
57
+ * Returns the input unchanged for non-SVG data URLs or when trimming is not possible.
58
+ */
59
+ export declare function trimSvgWhitespace(svgDataUrl: string, padding?: number): Promise<string>;
60
+ export declare function addLogoToCanvas(canvas: StaticCanvas, logoDataUrl: string, transform: CanvasTransform, id: string): Promise<FabricObject>;
@@ -0,0 +1,4 @@
1
+ import { LogoFormat } from '../types';
2
+ export declare function mimeToFormat(mime: string): LogoFormat;
3
+ export declare function isSvgContent(text: string): boolean;
4
+ export declare function detectFileFormat(file: File): Promise<LogoFormat>;
@@ -0,0 +1,44 @@
1
+ /** Utility functions for the HTML/CSS-based logo renderer and Canvas 2D export. */
2
+ import { CanvasTransform } from '../types';
3
+ /** Load an image from a URL/data-URL and return the HTMLImageElement once loaded. */
4
+ export declare function loadImage(src: string): Promise<HTMLImageElement>;
5
+ /** Compute contain-fit dimensions (uniform scale to fit inside container). */
6
+ export declare function computeContainFit(containerW: number, containerH: number, imgW: number, imgH: number): {
7
+ fittedW: number;
8
+ fittedH: number;
9
+ };
10
+ /**
11
+ * Convert a Fabric.js center-origin transform to CSS top-left positioning.
12
+ * Fabric.js stores x/y as the center of the object. CSS `transform-origin: center`
13
+ * means the origin is at the center of the unscaled element box, so left/top are
14
+ * computed by subtracting half the *natural* (unscaled) dimensions.
15
+ */
16
+ export declare function centerOriginToTopLeft(transform: CanvasTransform, naturalW: number, naturalH: number): {
17
+ left: number;
18
+ top: number;
19
+ };
20
+ /**
21
+ * Parse an SVG data URL to extract intrinsic dimensions from viewBox or width/height attributes.
22
+ * Returns null if the data URL is not SVG or dimensions cannot be determined.
23
+ */
24
+ export declare function getSvgIntrinsicSize(svgDataUrl: string): {
25
+ width: number;
26
+ height: number;
27
+ } | null;
28
+ export interface RenderLayer {
29
+ img: HTMLImageElement;
30
+ naturalWidth: number;
31
+ naturalHeight: number;
32
+ left: number;
33
+ top: number;
34
+ scaleX: number;
35
+ scaleY: number;
36
+ angle: number;
37
+ skewX: number;
38
+ skewY: number;
39
+ }
40
+ /**
41
+ * Canvas 2D export: draw product background + logo layers onto a plain canvas.
42
+ * Returns a data URL string.
43
+ */
44
+ export declare function renderToCanvas(containerW: number, containerH: number, bgColor: string, productImg: HTMLImageElement | undefined, layers: RenderLayer[], format?: 'png' | 'jpeg', quality?: number): string;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Generate a downscaled preview data URL from a full-resolution data URL.
3
+ *
4
+ * - SVG data URLs are returned unchanged (vectors are compact and scale-independent).
5
+ * - Raster images are downscaled so the longest side is at most `maxSize` pixels.
6
+ * If the image is already smaller, the original is returned unchanged.
7
+ * - Output is PNG to preserve transparency.
8
+ *
9
+ * @param dataUrl Full-resolution data URL
10
+ * @param maxSize Maximum pixel dimension for the longest side (default: 800)
11
+ * @returns Downscaled PNG data URL, or the original for SVGs / small images
12
+ */
13
+ export declare function generatePreviewDataUrl(dataUrl: string, maxSize?: number): Promise<string>;
@@ -0,0 +1,2 @@
1
+ import { LogoValidationConfig, LogoValidationResult } from '../types';
2
+ export declare function validateLogo(file: File, config?: LogoValidationConfig): Promise<LogoValidationResult>;
@@ -0,0 +1,32 @@
1
+ import { LogoData, Article } from '../types';
2
+ export interface PdfExportConfig {
3
+ pageFormat: 'a4' | 'letter';
4
+ orientation: 'portrait' | 'landscape';
5
+ marginMm: number;
6
+ showPrintAreaGuides: boolean;
7
+ title: string;
8
+ }
9
+ /** Detect image format from a data URL for jsPDF's addImage(). */
10
+ export declare function dataUrlToImageFormat(dataUrl: string): 'PNG' | 'JPEG';
11
+ /** Returns true if the data URL is an SVG (which jsPDF cannot embed directly). */
12
+ export declare function isSvgDataUrl(dataUrl: string): boolean;
13
+ /**
14
+ * Rasterize a data URL to PNG via an offscreen canvas.
15
+ * SVG data URLs must be rasterized because jsPDF only supports PNG/JPEG.
16
+ * For SVGs, upscaleSvgDataUrl is applied first so the browser rasterizes
17
+ * the vector artwork at high resolution (maxSize px) instead of the often
18
+ * tiny intrinsic SVG dimensions.
19
+ * Non-SVG data URLs are returned unchanged.
20
+ */
21
+ export declare function rasterizeDataUrl(dataUrl: string, maxSize?: number): Promise<string>;
22
+ /** Merge partial config with defaults. */
23
+ export declare function buildPdfConfig(partial?: Partial<PdfExportConfig>): PdfExportConfig;
24
+ /**
25
+ * Generate and download a print-shop-ready PDF for a product.
26
+ *
27
+ * Page 1: Logo at full quality with metadata table.
28
+ * Page 2: High-resolution product mockup with print area guides.
29
+ *
30
+ * Requires jsPDF to be loaded globally via script tag before calling this function.
31
+ */
32
+ export declare function exportProductPdf(logo: LogoData, article: Article, viewIndex: number, productMockupDataUrl: string, canvasWidth: number, canvasHeight: number, config?: Partial<PdfExportConfig>): Promise<void>;
@@ -0,0 +1 @@
1
+ export{a as DEFAULT_BG_REMOVAL_CONFIG,D as DEFAULT_VALIDATION_CONFIG}from"./p-D8pVJRuX.js";import{u as t,p as n}from"./p-CK8OAq2J.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"}async function i(i,r,a,s,c,l,m){const d=(u=m,{...o,...u});var u;const p=function(){const t="undefined"!=typeof window?window:void 0,n=t?.jspdf;if(null!=n?.jsPDF)return n.jsPDF;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>')}(),f=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 i=new Image;i.onload=()=>{const e=i.naturalWidth||o,r=i.naturalHeight||o,a=document.createElement("canvas");a.width=e,a.height=r;const s=a.getContext("2d");null!=s?(s.drawImage(i,0,0,e,r),t(a.toDataURL("image/png"))):n(new Error("Failed to get 2D context for SVG rasterization"))},i.onerror=()=>n(new Error("Failed to load image for rasterization")),i.src=e}))}(i.dataUrl),g=new p({orientation:d.orientation,unit:"mm",format:d.pageFormat});!function(t,n,o,i){const r=t.internal.pageSize.getWidth(),a=t.internal.pageSize.getHeight(),s=i.marginMm,c=r-2*s;t.setFontSize(18),t.setFont("helvetica","bold"),t.text(i.title,r/2,s+8,{align:"center"});const l=e(o),m=s+16,d=.5*a,u=n.metadata.width/Math.max(n.metadata.height,1);let p=c,f=p/u;f>d&&(f=d,p=f*u),t.addImage(o,l,(r-p)/2,m,p,f);const g=m+f+10,v=n.metadata,w=[["Format",v.format.toUpperCase()],["Dimensions",`${v.width} x ${v.height} px`],["DPI",null!==v.dpiX?`${v.dpiX} x ${v.dpiY??v.dpiX}`:"Not available"],["File Size",(P=v.fileSize,P<1024?`${P} B`:P<1048576?`${(P/1024).toFixed(1)} KB`:`${(P/1048576).toFixed(1)} MB`)],["Transparency",v.hasTransparency?"Yes":"No"],["File Name",v.fileName]];var P;t.setFontSize(10),w.forEach(((n,o)=>{const e=g+7*o;t.setFont("helvetica","bold"),t.text(n[0],s,e),t.setFont("helvetica","normal"),t.text(n[1],s+30,e)})),t.setDrawColor(200,200,200),t.setLineWidth(.3),t.line(s,g-4,s+c,g-4)}(g,i,f,d),g.addPage(),function(t,o,i,r,a,s,c){const l=t.internal.pageSize.getWidth(),m=t.internal.pageSize.getHeight(),d=c.marginMm,u=l-2*d;t.setFontSize(16),t.setFont("helvetica","bold"),t.text(o.name,l/2,d+8,{align:"center"});const p=o.views[i],f=[["Article ID",o.id]];p?.impMethod&&f.push(["Print Method",p.impMethod]),p?.impLocation&&f.push(["Print Location",p.impLocation]),p?.impDiameterMm&&p.impDiameterMm>0?f.push(["Print Area",`Ø ${p.impDiameterMm} mm`]):p?.impWidthMm&&p?.impHeightMm&&f.push(["Print Area",`${p.impWidthMm} × ${p.impHeightMm} mm`]),p?.maxColours&&f.push(["Max Colors",`${p.maxColours}`]);const g=d+16;t.setFontSize(9),f.forEach(((n,o)=>{const e=g+6*o;t.setFont("helvetica","bold"),t.text(n[0],d,e),t.setFont("helvetica","normal"),t.text(n[1],d+30,e)})),t.setDrawColor(200,200,200),t.setLineWidth(.3),t.line(d,g+6*f.length+2,d+u,g+6*f.length+2);const v=e(r),w=g+6*f.length+6,P=m-w-d,h=a/Math.max(s,1);let F=u,$=F/h;$>P&&($=P,F=$*h);const D=(l-F)/2;t.addImage(r,v,D,w,F,$),c.showPrintAreaGuides&&null!=p?.printArea&&function(t,o,e,i,r,a,s,c){const l=n(o,s,c),m=r/s,d=a/c,u=l.map((t=>({x:e+t.x*m,y:i+t.y*d})));t.setDrawColor(37,99,235),t.setLineWidth(.4),t.setLineDashPattern([2,1.5],0);for(let n=0;n<4;n++){const o=u[n],e=u[(n+1)%4];t.line(o.x,o.y,e.x,e.y)}t.setLineDashPattern([],0)}(t,p.printArea,D,w,F,$,a,s)}(g,r,a,s,c,l,d),g.save(`${r.id}.pdf`)}export{i as exportProductPdf}
@@ -0,0 +1 @@
1
+ import{r as t,c as e,g as o,h as s}from"./p-CUetmLbL.js";import{u as r,b as i,f as n,p as a}from"./p-CK8OAq2J.js";function l(t){return new Promise(((e,o)=>{const s=new Image;s.onload=()=>e(s),s.onerror=()=>o(new Error(`Failed to load image: ${t.slice(0,80)}`)),s.src=t}))}function h(t,e,o,s){const r=Math.min(t/o,e/s);return{fittedW:o*r,fittedH:s*r}}function c(t,e,o){return{left:t.x-e/2,top:t.y-o/2}}function d(t){if(!t.startsWith("data:image/svg+xml"))return null;let e;const o=t.indexOf(";base64,");if(-1!==o)e=atob(t.slice(o+8));else{const o=t.indexOf(",");if(-1===o)return null;e=decodeURIComponent(t.slice(o+1))}const s=e.match(/<svg[^>]*>/i);if(null===s)return null;const r=s[0],i=r.match(/viewBox=["']([^"']+)["']/);if(null!==i){const t=i[1].trim().split(/[\s,]+/),e=parseFloat(t[2]),o=parseFloat(t[3]);if(e>0&&o>0)return{width:e,height:o}}const n=r.match(/\bwidth=["']([^"']+)["']/),a=r.match(/\bheight=["']([^"']+)["']/);if(null!==n&&null!==a){const t=parseFloat(n[1]),e=parseFloat(a[1]);if(t>0&&e>0)return{width:t,height:e}}return null}const g=class{constructor(o){t(this,o),this.wtpRenderComplete=e(this,"wtpRenderComplete"),this.wtpRenderError=e(this,"wtpRenderError")}get el(){return o(this)}productImage;width=600;height=400;logos=[];backgroundColor="#ffffff";printArea;wtpRenderComplete;wtpRenderError;layers=[];containerWidth=0;containerHeight=0;productImg;componentWillLoad(){this.computeLayout()}onProductImageChange(){this.computeLayout()}onLogosChange(){this.computeLayout()}onPrintAreaChange(){this.computeLayout()}onSizeChange(){this.computeLayout()}onBackgroundColorChange(){this.emitRenderComplete()}async exportImage(t="png",e=1){const o=this.containerWidth||this.width,s=this.containerHeight||this.height,r=this.layers.map((t=>({img:t.exportImg,naturalWidth:t.naturalWidth,naturalHeight:t.naturalHeight,left:t.left,top:t.top,scaleX:t.scaleX,scaleY:t.scaleY,angle:t.angle,skewX:t.skewX,skewY:t.skewY})));return function(t,e,o,s,r,i="png",n=1){const a=document.createElement("canvas");a.width=t,a.height=e;const l=a.getContext("2d");if(l.fillStyle=o,l.fillRect(0,0,t,e),void 0!==s){const{fittedW:o,fittedH:r}=h(t,e,s.naturalWidth,s.naturalHeight);l.drawImage(s,(t-o)/2,(e-r)/2,o,r)}for(const t of r){l.save(),l.translate(t.left+t.naturalWidth/2,t.top+t.naturalHeight/2),l.rotate(t.angle*Math.PI/180);const e=Math.tan(t.skewX*Math.PI/180),o=Math.tan(t.skewY*Math.PI/180);l.transform(1,o,e,1,0,0),l.scale(t.scaleX,t.scaleY),l.drawImage(t.img,-t.naturalWidth/2,-t.naturalHeight/2,t.naturalWidth,t.naturalHeight),l.restore()}return a.toDataURL(`image/${i}`,n)}(o,s,this.backgroundColor,this.productImg,r,t,e)}async computeLayout(){try{let t=this.width,e=this.height;if(void 0!==this.productImage&&""!==this.productImage){const o=await l(this.productImage);this.productImg=o;const{fittedW:s,fittedH:r}=h(this.width,this.height,o.naturalWidth,o.naturalHeight);t=s,e=r}else this.productImg=void 0;this.containerWidth=t,this.containerHeight=e;const o=[];for(const s of this.logos){const h=s.previewDataUrl??s.dataUrl;if(void 0!==s.transform){const t=await l(s.dataUrl),e=t.naturalWidth,r=t.naturalHeight,{left:i,top:n}=c(s.transform,e,r);o.push({id:s.id,src:h,exportImg:t,naturalWidth:e,naturalHeight:r,left:i,top:n,scaleX:s.transform.scaleX,scaleY:s.transform.scaleY,angle:s.transform.angle,skewX:s.transform.skewX??0,skewY:s.transform.skewY??0})}else if(void 0!==this.printArea){let g,p,f=h;if(void 0!==this.printArea.bulge&&0!==this.printArea.bulge){const h=Math.round(2*Math.max(t,e)),{dataUrl:d}=r(s.dataUrl,h),u=await l(d);g=u.naturalWidth,p=u.naturalHeight;const w=n(g,p,this.printArea,t,e),[m,b,k,C]=a(this.printArea,t,e),y=Math.hypot(C.x-m.x,C.y-m.y),v=Math.hypot(k.x-b.x,k.y-b.y),x=i(u,this.printArea.bulge,(y+v)/2,w.scaleX);f=x.toDataURL("image/png"),g=x.width,p=x.height;const $=await l(f),{left:I,top:M}=c(w,g,p);o.push({id:s.id,src:f,exportImg:$,naturalWidth:g,naturalHeight:p,left:I,top:M,scaleX:w.scaleX,scaleY:w.scaleY,angle:w.angle,skewX:0,skewY:0})}else{const r=d(s.dataUrl);if(null!==r)g=r.width,p=r.height;else{const t=await l(s.dataUrl);g=t.naturalWidth,p=t.naturalHeight}const i=n(g,p,this.printArea,t,e),a=await l(f),{left:h,top:u}=c(i,g,p);o.push({id:s.id,src:f,exportImg:a,naturalWidth:g,naturalHeight:p,left:h,top:u,scaleX:i.scaleX,scaleY:i.scaleY,angle:i.angle,skewX:0,skewY:0})}}}this.layers=o,this.emitRenderComplete()}catch(t){const e=t instanceof Error?t.message:"Unknown render error";this.wtpRenderError.emit({message:e})}}async emitRenderComplete(){try{const t=await this.exportImage("png");this.wtpRenderComplete.emit({dataUrl:t})}catch{}}render(){return s("div",{key:"3a720dae0cd0ab070a90b1f20dff9adb699b0ba5",class:"wtp-logo-renderer",style:{width:`${this.containerWidth||this.width}px`,height:`${this.containerHeight||this.height}px`,backgroundColor:this.backgroundColor}},void 0!==this.productImage&&""!==this.productImage&&s("img",{key:"88b50d0a4fd320a935bd7008ef3ddfee1644cbab",class:"product-bg",src:this.productImage,alt:""}),this.layers.map((t=>s("img",{key:t.id,class:"logo-layer",src:t.src,alt:"",style:{position:"absolute",left:`${t.left}px`,top:`${t.top}px`,width:`${t.naturalWidth}px`,height:`${t.naturalHeight}px`,transformOrigin:"center",transform:`rotate(${t.angle}deg) skewX(${t.skewX}deg) skewY(${t.skewY}deg) scale(${t.scaleX}, ${t.scaleY})`}}))))}static get watchers(){return{productImage:[{onProductImageChange:0}],logos:[{onLogosChange:0}],printArea:[{onPrintAreaChange:0}],width:[{onSizeChange:0}],height:[{onSizeChange:0}],backgroundColor:[{onBackgroundColorChange:0}]}}};g.style='*.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}';export{g as wtp_logo_renderer}