manifest-label-print 0.1.1

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/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # manifest-label-print
2
+
3
+ Reusable label print module for `manifest-design`.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm i manifest-label-print
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - Template normalization (`components/canvasConfig`, legacy `data`, array input)
14
+ - Print engine abstraction (`browser` and `clodop`)
15
+ - Engine preference storage with legacy key migration
16
+ - Browser engine prints canvas snapshot (`target` is required)
17
+ - Print mode applies to CLodop engine (`native` falls back to `image` when unsupported)
18
+
19
+ ## Quick Start
20
+
21
+ ```ts
22
+ import {
23
+ toPrintTemplate,
24
+ executePrint,
25
+ getPreferredPrintEngine,
26
+ setPreferredPrintEngine,
27
+ setPreferredPrintMode
28
+ } from 'manifest-label-print'
29
+
30
+ const input = {
31
+ components: [],
32
+ canvasConfig: { width: 60, height: 40, unit: 'mm' }
33
+ }
34
+
35
+ const template = toPrintTemplate(input)
36
+ const printTarget = document.querySelector('.preview-canvas') as HTMLElement | null
37
+
38
+ setPreferredPrintEngine('browser')
39
+ setPreferredPrintMode('image')
40
+ await executePrint(template, {
41
+ engine: getPreferredPrintEngine(),
42
+ target: printTarget
43
+ })
44
+ ```
45
+
46
+ ## API
47
+
48
+ - `toPrintTemplate(input)`:
49
+ Normalize `components/canvasConfig`, legacy `{ data }`, or component-array inputs.
50
+ - `executePrint(template, options)`:
51
+ Execute print by selected engine and fallback to browser print when needed.
52
+ - `getPreferredPrintEngine()` / `setPreferredPrintEngine(engine)`:
53
+ Read/write print engine preference with legacy key migration support.
54
+ - `getPreferredPrintMode()` / `setPreferredPrintMode(mode)`:
55
+ Read/write print mode preference with legacy key migration support.
56
+ - `BrowserPrintAdapter` / `ClodopPrintAdapter`:
57
+ Built-in engine adapters.
58
+
59
+ ## Behavior Notes
60
+
61
+ - Browser engine always uses snapshot printing and requires `options.target`.
62
+ - Print mode (`image` / `native`) is used by CLodop engine.
63
+ - If CLodop native mode cannot render a template, it automatically falls back to image mode.
64
+
65
+ ## Build
66
+
67
+ ```bash
68
+ npm run build
69
+ ```
70
+
71
+ ## Release
72
+
73
+ ```bash
74
+ npm version patch
75
+ npm publish --access public
76
+ ```
@@ -0,0 +1,6 @@
1
+ import type { PrintAdapter, PrintJobOptions, PrintJobResult, PrintTemplate } from '../types';
2
+ export declare class BrowserPrintAdapter implements PrintAdapter {
3
+ readonly engine: "browser";
4
+ isAvailable(): boolean;
5
+ print(_template: PrintTemplate, options: PrintJobOptions): Promise<PrintJobResult>;
6
+ }
@@ -0,0 +1,117 @@
1
+ import { capturePrintTarget } from '../capture';
2
+ const createPrintFrame = () => {
3
+ const iframe = document.createElement('iframe');
4
+ iframe.setAttribute('aria-hidden', 'true');
5
+ iframe.style.position = 'fixed';
6
+ iframe.style.right = '0';
7
+ iframe.style.bottom = '0';
8
+ iframe.style.width = '0';
9
+ iframe.style.height = '0';
10
+ iframe.style.border = '0';
11
+ document.body.appendChild(iframe);
12
+ return iframe;
13
+ };
14
+ const cleanupPrintFrame = (iframe) => {
15
+ window.setTimeout(() => {
16
+ iframe.remove();
17
+ }, 1000);
18
+ };
19
+ const printImage = async (dataUrl, width, height, copies) => {
20
+ const iframe = createPrintFrame();
21
+ const doc = iframe.contentDocument;
22
+ if (!doc) {
23
+ iframe.remove();
24
+ throw new Error('Print frame is unavailable');
25
+ }
26
+ doc.open();
27
+ doc.write(`
28
+ <!DOCTYPE html>
29
+ <html>
30
+ <head>
31
+ <title>Print</title>
32
+ <style>
33
+ @page { margin: 0; }
34
+ html, body {
35
+ margin: 0;
36
+ padding: 0;
37
+ background: #ffffff;
38
+ }
39
+ body {
40
+ display: flex;
41
+ align-items: center;
42
+ justify-content: center;
43
+ }
44
+ img {
45
+ display: block;
46
+ width: ${width};
47
+ height: ${height};
48
+ object-fit: contain;
49
+ }
50
+ </style>
51
+ </head>
52
+ <body>
53
+ <img src="${dataUrl}" alt="print-target" />
54
+ </body>
55
+ </html>
56
+ `);
57
+ doc.close();
58
+ await new Promise((resolve) => {
59
+ const image = doc.querySelector('img');
60
+ if (!image) {
61
+ resolve();
62
+ return;
63
+ }
64
+ image.addEventListener('load', () => resolve(), { once: true });
65
+ image.addEventListener('error', () => resolve(), { once: true });
66
+ });
67
+ const frameWindow = iframe.contentWindow;
68
+ if (!frameWindow) {
69
+ iframe.remove();
70
+ throw new Error('Print frame window is unavailable');
71
+ }
72
+ for (let i = 0; i < copies; i++) {
73
+ frameWindow.focus();
74
+ frameWindow.print();
75
+ }
76
+ cleanupPrintFrame(iframe);
77
+ };
78
+ export class BrowserPrintAdapter {
79
+ constructor() {
80
+ this.engine = 'browser';
81
+ }
82
+ isAvailable() {
83
+ return typeof window !== 'undefined' && typeof window.print === 'function';
84
+ }
85
+ async print(_template, options) {
86
+ if (!this.isAvailable()) {
87
+ return {
88
+ success: false,
89
+ message: 'Browser print is unavailable'
90
+ };
91
+ }
92
+ if (!options.target) {
93
+ return {
94
+ success: false,
95
+ message: 'Print target is missing'
96
+ };
97
+ }
98
+ try {
99
+ const dataUrl = await capturePrintTarget(options.target);
100
+ const copies = Math.max(1, options.copies || 1);
101
+ const unit = _template.canvasConfig.unit || 'mm';
102
+ const width = `${_template.canvasConfig.width}${unit}`;
103
+ const height = `${_template.canvasConfig.height}${unit}`;
104
+ await printImage(dataUrl, width, height, copies);
105
+ return {
106
+ success: true
107
+ };
108
+ }
109
+ catch (error) {
110
+ return {
111
+ success: false,
112
+ message: 'Browser print failed',
113
+ error
114
+ };
115
+ }
116
+ }
117
+ }
@@ -0,0 +1,6 @@
1
+ import type { PrintAdapter, PrintJobOptions, PrintJobResult, PrintTemplate } from '../types';
2
+ export declare class ClodopPrintAdapter implements PrintAdapter {
3
+ readonly engine: "clodop";
4
+ isAvailable(): boolean;
5
+ print(template: PrintTemplate, options: PrintJobOptions): Promise<PrintJobResult>;
6
+ }
@@ -0,0 +1,227 @@
1
+ import { capturePrintTarget } from '../capture';
2
+ const getLodopInstance = () => {
3
+ if (typeof window === 'undefined') {
4
+ return null;
5
+ }
6
+ const lodopWindow = window;
7
+ if (lodopWindow.CLODOP) {
8
+ return lodopWindow.CLODOP;
9
+ }
10
+ if (typeof lodopWindow.getCLodop === 'function') {
11
+ try {
12
+ return lodopWindow.getCLodop() || null;
13
+ }
14
+ catch (_error) {
15
+ return null;
16
+ }
17
+ }
18
+ return null;
19
+ };
20
+ const mmToClodop01mm = (value) => {
21
+ return Math.round(value * 10);
22
+ };
23
+ const mmToPoints = (value) => {
24
+ return Math.max(1, Math.round(value * 2.83465));
25
+ };
26
+ const toNumber = (value, fallback = 0) => {
27
+ return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
28
+ };
29
+ const toStringValue = (value, fallback = '') => {
30
+ return typeof value === 'string' ? value : fallback;
31
+ };
32
+ const isVisible = (component) => {
33
+ return component.visible !== false;
34
+ };
35
+ const supportsNativeMode = (template) => {
36
+ return template.components.every((component) => {
37
+ const type = component.type || '';
38
+ const rotation = toNumber(component.rotation, 0);
39
+ const opacity = toNumber(component.opacity, 1);
40
+ if (!isVisible(component) || rotation !== 0 || opacity !== 1) {
41
+ return false;
42
+ }
43
+ switch (type) {
44
+ case 'text':
45
+ case 'barcode':
46
+ case 'qrcode':
47
+ case 'horizontal-line':
48
+ case 'vertical-line':
49
+ case 'rectangle':
50
+ return true;
51
+ case 'image':
52
+ return typeof component.src === 'string' && component.src.length > 0;
53
+ default:
54
+ return false;
55
+ }
56
+ });
57
+ };
58
+ const setupPageSize = (lodop, template) => {
59
+ const pageWidth = mmToClodop01mm(template.canvasConfig.width);
60
+ const pageHeight = mmToClodop01mm(template.canvasConfig.height);
61
+ if (lodop.SET_PRINT_PAGESIZE) {
62
+ lodop.SET_PRINT_PAGESIZE(1, pageWidth, pageHeight, '');
63
+ }
64
+ if (lodop.SET_PRINT_STYLEA) {
65
+ lodop.SET_PRINT_STYLEA(0, 'PageWidth', pageWidth);
66
+ lodop.SET_PRINT_STYLEA(0, 'PageHeight', pageHeight);
67
+ }
68
+ return { pageWidth, pageHeight };
69
+ };
70
+ const executePrintTask = (lodop, options) => {
71
+ if (options.preview && lodop.PREVIEW) {
72
+ lodop.PREVIEW();
73
+ }
74
+ else if (lodop.PRINT) {
75
+ lodop.PRINT();
76
+ }
77
+ };
78
+ const applyTextStyles = (lodop, itemIndex, component) => {
79
+ if (!lodop.SET_PRINT_STYLEA) {
80
+ return;
81
+ }
82
+ lodop.SET_PRINT_STYLEA(itemIndex, 'FontSize', mmToPoints(toNumber(component.fontSize, 3)));
83
+ const fontFamily = toStringValue(component.fontFamily);
84
+ if (fontFamily) {
85
+ lodop.SET_PRINT_STYLEA(itemIndex, 'FontName', fontFamily);
86
+ }
87
+ const color = toStringValue(component.color);
88
+ if (color) {
89
+ lodop.SET_PRINT_STYLEA(itemIndex, 'FontColor', color);
90
+ }
91
+ const fontWeight = toStringValue(component.fontWeight);
92
+ if (fontWeight.toLowerCase() === 'bold') {
93
+ lodop.SET_PRINT_STYLEA(itemIndex, 'Bold', 1);
94
+ }
95
+ const textAlign = toStringValue(component.textAlign, 'left');
96
+ const alignMap = {
97
+ left: 1,
98
+ center: 2,
99
+ right: 3
100
+ };
101
+ lodop.SET_PRINT_STYLEA(itemIndex, 'Alignment', alignMap[textAlign] || 1);
102
+ };
103
+ const renderNativeComponent = (lodop, component, itemIndex) => {
104
+ const type = component.type || '';
105
+ const top = mmToClodop01mm(toNumber(component.y));
106
+ const left = mmToClodop01mm(toNumber(component.x));
107
+ const width = mmToClodop01mm(toNumber(component.width));
108
+ const height = mmToClodop01mm(toNumber(component.height));
109
+ switch (type) {
110
+ case 'text': {
111
+ lodop.ADD_PRINT_TEXT?.(top, left, width, height, toStringValue(component.content));
112
+ applyTextStyles(lodop, itemIndex, component);
113
+ return;
114
+ }
115
+ case 'barcode': {
116
+ lodop.ADD_PRINT_BARCODE?.(top, left, width, height, toStringValue(component.format, 'Code128'), toStringValue(component.content));
117
+ if (lodop.SET_PRINT_STYLEA && component.displayValue === false) {
118
+ lodop.SET_PRINT_STYLEA(itemIndex, 'ShowBarText', 0);
119
+ }
120
+ return;
121
+ }
122
+ case 'qrcode': {
123
+ lodop.ADD_PRINT_BARCODE?.(top, left, width, height, 'QRCode', toStringValue(component.content));
124
+ if (lodop.SET_PRINT_STYLEA) {
125
+ const errorLevel = toStringValue(component.errorCorrectionLevel, 'M');
126
+ lodop.SET_PRINT_STYLEA(itemIndex, 'QRCodeErrorLevel', errorLevel);
127
+ }
128
+ return;
129
+ }
130
+ case 'horizontal-line': {
131
+ const strokeWidth = Math.max(1, mmToClodop01mm(toNumber(component.strokeWidth, 0.2)));
132
+ lodop.ADD_PRINT_LINE?.(top, left, top, left + width, strokeWidth, 0);
133
+ return;
134
+ }
135
+ case 'vertical-line': {
136
+ const strokeWidth = Math.max(1, mmToClodop01mm(toNumber(component.strokeWidth, 0.2)));
137
+ lodop.ADD_PRINT_LINE?.(top, left, top + height, left, strokeWidth, 0);
138
+ return;
139
+ }
140
+ case 'rectangle': {
141
+ const strokeWidth = Math.max(1, mmToClodop01mm(toNumber(component.strokeWidth, 0.2)));
142
+ lodop.ADD_PRINT_RECT?.(top, left, width, height, strokeWidth, 0);
143
+ return;
144
+ }
145
+ case 'image': {
146
+ lodop.ADD_PRINT_IMAGE?.(top, left, width, height, toStringValue(component.src));
147
+ if (lodop.SET_PRINT_STYLEA) {
148
+ lodop.SET_PRINT_STYLEA(itemIndex, 'Stretch', 2);
149
+ }
150
+ return;
151
+ }
152
+ }
153
+ };
154
+ export class ClodopPrintAdapter {
155
+ constructor() {
156
+ this.engine = 'clodop';
157
+ }
158
+ isAvailable() {
159
+ return getLodopInstance() !== null;
160
+ }
161
+ async print(template, options) {
162
+ const lodop = getLodopInstance();
163
+ if (!lodop) {
164
+ return {
165
+ success: false,
166
+ message: 'CLodop is unavailable'
167
+ };
168
+ }
169
+ try {
170
+ const mode = options.mode || 'image';
171
+ const canUseNativeMode = mode === 'native' && supportsNativeMode(template);
172
+ lodop.PRINT_INIT(`manifest-label-print-${Date.now()}`);
173
+ setupPageSize(lodop, template);
174
+ if (options.printerName && lodop.SET_PRINTER_INDEXA) {
175
+ lodop.SET_PRINTER_INDEXA(options.printerName);
176
+ }
177
+ if (canUseNativeMode) {
178
+ let itemIndex = 1;
179
+ for (const component of template.components) {
180
+ if (!isVisible(component)) {
181
+ continue;
182
+ }
183
+ renderNativeComponent(lodop, component, itemIndex);
184
+ itemIndex += 1;
185
+ }
186
+ executePrintTask(lodop, options);
187
+ return {
188
+ success: true,
189
+ message: 'Printed with CLodop native mode'
190
+ };
191
+ }
192
+ if (!options.target) {
193
+ return {
194
+ success: false,
195
+ message: 'Print target is missing'
196
+ };
197
+ }
198
+ if (!lodop.ADD_PRINT_IMAGE) {
199
+ return {
200
+ success: false,
201
+ message: 'CLodop image printing is unavailable'
202
+ };
203
+ }
204
+ const dataUrl = await capturePrintTarget(options.target);
205
+ const pageWidth = mmToClodop01mm(template.canvasConfig.width);
206
+ const pageHeight = mmToClodop01mm(template.canvasConfig.height);
207
+ lodop.ADD_PRINT_IMAGE(0, 0, pageWidth, pageHeight, dataUrl);
208
+ if (lodop.SET_PRINT_STYLEA) {
209
+ lodop.SET_PRINT_STYLEA(1, 'Stretch', 2);
210
+ }
211
+ executePrintTask(lodop, options);
212
+ return {
213
+ success: true,
214
+ message: mode === 'native'
215
+ ? 'Native mode fallback to image mode'
216
+ : 'Printed with CLodop image mode'
217
+ };
218
+ }
219
+ catch (error) {
220
+ return {
221
+ success: false,
222
+ message: 'CLodop print failed',
223
+ error
224
+ };
225
+ }
226
+ }
227
+ }
@@ -0,0 +1 @@
1
+ export declare const capturePrintTarget: (target: HTMLElement) => Promise<string>;
@@ -0,0 +1,22 @@
1
+ import html2canvas from 'html2canvas';
2
+ const DEFAULT_IGNORED_SELECTORS = [
3
+ '.grid-background',
4
+ '.selection-handles',
5
+ '.selection-box',
6
+ '.snap-guide',
7
+ '.multi-selection-area',
8
+ '.context-menu-overlay'
9
+ ];
10
+ const shouldIgnoreElement = (element) => {
11
+ return DEFAULT_IGNORED_SELECTORS.some((selector) => element.matches(selector));
12
+ };
13
+ export const capturePrintTarget = async (target) => {
14
+ const canvas = await html2canvas(target, {
15
+ backgroundColor: '#ffffff',
16
+ scale: 2,
17
+ useCORS: true,
18
+ logging: false,
19
+ ignoreElements: (element) => shouldIgnoreElement(element)
20
+ });
21
+ return canvas.toDataURL('image/png');
22
+ };
@@ -0,0 +1,7 @@
1
+ import type { PrintEngine, PrintMode } from './types';
2
+ export declare const getStoredPrintEngine: () => PrintEngine | null;
3
+ export declare const getPreferredPrintEngine: () => PrintEngine;
4
+ export declare const setPreferredPrintEngine: (engine: PrintEngine) => void;
5
+ export declare const getStoredPrintMode: () => PrintMode | null;
6
+ export declare const getPreferredPrintMode: () => PrintMode;
7
+ export declare const setPreferredPrintMode: (mode: PrintMode) => void;
package/dist/config.js ADDED
@@ -0,0 +1,72 @@
1
+ const PRINT_ENGINE_STORAGE_KEY = 'manifest-print-engine';
2
+ const PRINT_MODE_STORAGE_KEY = 'manifest-print-mode';
3
+ const LEGACY_ENGINE_STORAGE_KEYS = ['print_engine', 'label_print_engine'];
4
+ const LEGACY_MODE_STORAGE_KEYS = ['print_mode', 'label_print_mode'];
5
+ const isPrintEngine = (value) => {
6
+ return value === 'browser' || value === 'clodop';
7
+ };
8
+ const isPrintMode = (value) => {
9
+ return value === 'image' || value === 'native';
10
+ };
11
+ const readStorageValue = (key) => {
12
+ if (typeof window === 'undefined') {
13
+ return null;
14
+ }
15
+ try {
16
+ return window.localStorage.getItem(key);
17
+ }
18
+ catch (_error) {
19
+ return null;
20
+ }
21
+ };
22
+ const writeStorageValue = (key, value) => {
23
+ if (typeof window === 'undefined') {
24
+ return;
25
+ }
26
+ try {
27
+ window.localStorage.setItem(key, value);
28
+ }
29
+ catch (_error) {
30
+ // Ignore localStorage unavailable scenarios.
31
+ }
32
+ };
33
+ export const getStoredPrintEngine = () => {
34
+ const primary = readStorageValue(PRINT_ENGINE_STORAGE_KEY);
35
+ if (isPrintEngine(primary)) {
36
+ return primary;
37
+ }
38
+ for (const key of LEGACY_ENGINE_STORAGE_KEYS) {
39
+ const legacyValue = readStorageValue(key);
40
+ if (isPrintEngine(legacyValue)) {
41
+ writeStorageValue(PRINT_ENGINE_STORAGE_KEY, legacyValue);
42
+ return legacyValue;
43
+ }
44
+ }
45
+ return null;
46
+ };
47
+ export const getPreferredPrintEngine = () => {
48
+ return getStoredPrintEngine() || 'browser';
49
+ };
50
+ export const setPreferredPrintEngine = (engine) => {
51
+ writeStorageValue(PRINT_ENGINE_STORAGE_KEY, engine);
52
+ };
53
+ export const getStoredPrintMode = () => {
54
+ const primary = readStorageValue(PRINT_MODE_STORAGE_KEY);
55
+ if (isPrintMode(primary)) {
56
+ return primary;
57
+ }
58
+ for (const key of LEGACY_MODE_STORAGE_KEYS) {
59
+ const legacyValue = readStorageValue(key);
60
+ if (isPrintMode(legacyValue)) {
61
+ writeStorageValue(PRINT_MODE_STORAGE_KEY, legacyValue);
62
+ return legacyValue;
63
+ }
64
+ }
65
+ return null;
66
+ };
67
+ export const getPreferredPrintMode = () => {
68
+ return getStoredPrintMode() || 'image';
69
+ };
70
+ export const setPreferredPrintMode = (mode) => {
71
+ writeStorageValue(PRINT_MODE_STORAGE_KEY, mode);
72
+ };
@@ -0,0 +1,6 @@
1
+ export type { DesignTemplateSnapshot, PrintAdapter, PrintCanvasConfig, PrintComponent, PrintEngine, PrintMode, PrintJobOptions, PrintJobResult, PrintTemplate } from './types';
2
+ export { normalizeTemplateInput, toPrintTemplate } from './mapper';
3
+ export { BrowserPrintAdapter } from './adapters/browser';
4
+ export { ClodopPrintAdapter } from './adapters/clodop';
5
+ export { executePrint, getDefaultPrintAdapters, resolvePrintAdapter } from './service';
6
+ export { getPreferredPrintEngine, getPreferredPrintMode, getStoredPrintEngine, getStoredPrintMode, setPreferredPrintEngine, setPreferredPrintMode } from './config';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { normalizeTemplateInput, toPrintTemplate } from './mapper';
2
+ export { BrowserPrintAdapter } from './adapters/browser';
3
+ export { ClodopPrintAdapter } from './adapters/clodop';
4
+ export { executePrint, getDefaultPrintAdapters, resolvePrintAdapter } from './service';
5
+ export { getPreferredPrintEngine, getPreferredPrintMode, getStoredPrintEngine, getStoredPrintMode, setPreferredPrintEngine, setPreferredPrintMode } from './config';
@@ -0,0 +1,9 @@
1
+ import type { DesignTemplateSnapshot, PrintCanvasConfig, PrintComponent, PrintTemplate } from './types';
2
+ type LegacyTemplateInput = DesignTemplateSnapshot | {
3
+ data?: unknown;
4
+ components?: unknown;
5
+ canvasConfig?: Partial<PrintCanvasConfig>;
6
+ } | PrintComponent[];
7
+ export declare const normalizeTemplateInput: (input: LegacyTemplateInput) => DesignTemplateSnapshot;
8
+ export declare const toPrintTemplate: (input: LegacyTemplateInput) => PrintTemplate;
9
+ export {};
package/dist/mapper.js ADDED
@@ -0,0 +1,58 @@
1
+ const defaultCanvasConfig = {
2
+ width: 60,
3
+ height: 40,
4
+ unit: 'mm',
5
+ zoom: 1,
6
+ showGrid: true,
7
+ showRuler: true,
8
+ showGuides: true
9
+ };
10
+ const deepClone = (value) => JSON.parse(JSON.stringify(value));
11
+ const asComponents = (value) => {
12
+ return Array.isArray(value) ? value : [];
13
+ };
14
+ export const normalizeTemplateInput = (input) => {
15
+ if (Array.isArray(input)) {
16
+ return {
17
+ components: deepClone(input),
18
+ canvasConfig: deepClone(defaultCanvasConfig)
19
+ };
20
+ }
21
+ if ('components' in input && 'canvasConfig' in input && Array.isArray(input.components)) {
22
+ return {
23
+ components: deepClone(asComponents(input.components)),
24
+ canvasConfig: {
25
+ ...defaultCanvasConfig,
26
+ ...(input.canvasConfig || {}),
27
+ unit: input.canvasConfig?.unit || 'mm'
28
+ }
29
+ };
30
+ }
31
+ if ('data' in input && Array.isArray(input.data)) {
32
+ return {
33
+ components: deepClone(asComponents(input.data)),
34
+ canvasConfig: {
35
+ ...defaultCanvasConfig,
36
+ ...(input.canvasConfig || {}),
37
+ unit: input.canvasConfig?.unit || 'mm'
38
+ }
39
+ };
40
+ }
41
+ return {
42
+ components: [],
43
+ canvasConfig: deepClone(defaultCanvasConfig)
44
+ };
45
+ };
46
+ export const toPrintTemplate = (input) => {
47
+ const normalized = normalizeTemplateInput(input);
48
+ return {
49
+ schemaVersion: '1.0',
50
+ source: 'manifest-design',
51
+ components: deepClone(normalized.components),
52
+ canvasConfig: {
53
+ ...deepClone(defaultCanvasConfig),
54
+ ...deepClone(normalized.canvasConfig),
55
+ unit: normalized.canvasConfig.unit || 'mm'
56
+ }
57
+ };
58
+ };
@@ -0,0 +1,4 @@
1
+ import type { PrintAdapter, PrintEngine, PrintJobOptions, PrintJobResult, PrintTemplate } from './types';
2
+ export declare const resolvePrintAdapter: (engine: PrintEngine, adapters?: PrintAdapter[], fallbackToBrowser?: boolean) => Promise<PrintAdapter | null>;
3
+ export declare const executePrint: (template: PrintTemplate, options: PrintJobOptions, adapters?: PrintAdapter[]) => Promise<PrintJobResult>;
4
+ export declare const getDefaultPrintAdapters: () => PrintAdapter[];
@@ -0,0 +1,31 @@
1
+ import { BrowserPrintAdapter } from './adapters/browser';
2
+ import { ClodopPrintAdapter } from './adapters/clodop';
3
+ const defaultAdapters = [new BrowserPrintAdapter(), new ClodopPrintAdapter()];
4
+ const getAdapterByEngine = (adapters, engine) => {
5
+ return adapters.find((adapter) => adapter.engine === engine);
6
+ };
7
+ export const resolvePrintAdapter = async (engine, adapters = defaultAdapters, fallbackToBrowser = true) => {
8
+ const target = getAdapterByEngine(adapters, engine);
9
+ if (target && (await target.isAvailable())) {
10
+ return target;
11
+ }
12
+ if (!fallbackToBrowser || engine === 'clodop') {
13
+ return null;
14
+ }
15
+ const browser = getAdapterByEngine(adapters, 'browser');
16
+ if (browser && (await browser.isAvailable())) {
17
+ return browser;
18
+ }
19
+ return null;
20
+ };
21
+ export const executePrint = async (template, options, adapters = defaultAdapters) => {
22
+ const adapter = await resolvePrintAdapter(options.engine, adapters, options.fallbackToBrowser ?? options.engine === 'browser');
23
+ if (!adapter) {
24
+ return {
25
+ success: false,
26
+ message: `${options.engine} print engine is unavailable`
27
+ };
28
+ }
29
+ return adapter.print(template, options);
30
+ };
31
+ export const getDefaultPrintAdapters = () => defaultAdapters;
@@ -0,0 +1,50 @@
1
+ export type PrintEngine = 'browser' | 'clodop';
2
+ export type PrintMode = 'image' | 'native';
3
+ export interface PrintComponent {
4
+ id?: string;
5
+ type?: string;
6
+ x?: number;
7
+ y?: number;
8
+ width?: number;
9
+ height?: number;
10
+ [key: string]: unknown;
11
+ }
12
+ export interface PrintCanvasConfig {
13
+ width: number;
14
+ height: number;
15
+ unit: string;
16
+ zoom?: number;
17
+ showGrid?: boolean;
18
+ showRuler?: boolean;
19
+ showGuides?: boolean;
20
+ [key: string]: unknown;
21
+ }
22
+ export interface DesignTemplateSnapshot {
23
+ components: PrintComponent[];
24
+ canvasConfig: PrintCanvasConfig;
25
+ }
26
+ export interface PrintTemplate {
27
+ schemaVersion: '1.0';
28
+ source: string;
29
+ components: PrintComponent[];
30
+ canvasConfig: PrintCanvasConfig;
31
+ }
32
+ export interface PrintJobOptions {
33
+ engine: PrintEngine;
34
+ mode?: PrintMode;
35
+ copies?: number;
36
+ preview?: boolean;
37
+ printerName?: string;
38
+ target?: HTMLElement | null;
39
+ fallbackToBrowser?: boolean;
40
+ }
41
+ export interface PrintJobResult {
42
+ success: boolean;
43
+ message?: string;
44
+ error?: unknown;
45
+ }
46
+ export interface PrintAdapter {
47
+ readonly engine: PrintEngine;
48
+ isAvailable: () => boolean | Promise<boolean>;
49
+ print: (template: PrintTemplate, options: PrintJobOptions) => Promise<PrintJobResult>;
50
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "manifest-label-print",
3
+ "version": "0.1.1",
4
+ "description": "Label print engine adapters and template mapper for manifest-design",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "node ../../node_modules/typescript/bin/tsc -p tsconfig.build.json",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "license": "MIT",
23
+ "dependencies": {
24
+ "html2canvas": "^1.4.1"
25
+ }
26
+ }