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 +76 -0
- package/dist/adapters/browser.d.ts +6 -0
- package/dist/adapters/browser.js +117 -0
- package/dist/adapters/clodop.d.ts +6 -0
- package/dist/adapters/clodop.js +227 -0
- package/dist/capture.d.ts +1 -0
- package/dist/capture.js +22 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +72 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/mapper.d.ts +9 -0
- package/dist/mapper.js +58 -0
- package/dist/service.d.ts +4 -0
- package/dist/service.js +31 -0
- package/dist/types.d.ts +50 -0
- package/dist/types.js +1 -0
- package/package.json +26 -0
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>;
|
package/dist/capture.js
ADDED
|
@@ -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
|
+
};
|
package/dist/config.d.ts
ADDED
|
@@ -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
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -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';
|
package/dist/mapper.d.ts
ADDED
|
@@ -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[];
|
package/dist/service.js
ADDED
|
@@ -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;
|
package/dist/types.d.ts
ADDED
|
@@ -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
|
+
}
|