qrlayout-core 1.0.0 → 1.0.2
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 +104 -72
- package/dist/layout/schema.d.ts +2 -1
- package/dist/pdf.js +8 -12
- package/dist/printer/StickerPrinter.js +10 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,108 +1,140 @@
|
|
|
1
1
|
# qrlayout-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A powerful, framework-agnostic engine for designing and printing QR code sticker layouts. Create pixel-perfect labels with text and QR codes, and export them to **PNG**, **PDF**, or **ZPL** (Zebra Programming Language).
|
|
4
4
|
|
|
5
5
|
> [!NOTE]
|
|
6
|
-
> This package
|
|
6
|
+
> This package is the core rendering engine. For a visual drag-and-drop designer and a live React example, visit our **[Live Demo](https://qr-layout-design-react-demo.netlify.app/)**.
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **Precise Layouts**: Define stickers in `mm`, `cm`, `in`, or `px`.
|
|
11
|
+
- **Multiple Formats**: Export to Canvas (preview), PNG/JPEG (image), PDF (print), or ZPL (industrial thermal printers).
|
|
12
|
+
- **Dynamic Content**: Use variable placeholders (e.g., `{{name}}`, `{{sku}}`) to batch generate unique stickers.
|
|
13
|
+
- **Lightweight**: Minimal dependencies. PDF export is optional to keep bundle size small.
|
|
14
|
+
|
|
15
|
+
## Live Demo & Examples
|
|
16
|
+
|
|
17
|
+
- **[Interactive React Demo](https://qr-layout-design-react-demo.netlify.app/)**: A full-featured designer built with this package.
|
|
18
|
+
- **[React Demo Source Code](https://github.com/shashi089/qr-code-layout-generate-tool/tree/example/demo-react)**: Reference implementation for monorepo usage.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
9
21
|
|
|
10
22
|
```bash
|
|
11
23
|
npm install qrlayout-core
|
|
12
24
|
```
|
|
13
25
|
|
|
14
|
-
## Quick
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### 1. Define a Layout
|
|
29
|
+
|
|
30
|
+
A layout is a JSON object describing the sticker dimensions and elements.
|
|
15
31
|
|
|
16
32
|
```ts
|
|
17
|
-
import {
|
|
33
|
+
import { type StickerLayout } from "qrlayout-core";
|
|
18
34
|
|
|
19
|
-
const
|
|
20
|
-
id: "layout
|
|
21
|
-
name: "Badge",
|
|
22
|
-
width: 100,
|
|
23
|
-
height: 60,
|
|
35
|
+
const myLayout: StickerLayout = {
|
|
36
|
+
id: "badge-layout",
|
|
37
|
+
name: "Conference Badge",
|
|
38
|
+
width: 100, // 100mm width
|
|
39
|
+
height: 60, // 60mm height
|
|
24
40
|
unit: "mm",
|
|
25
41
|
elements: [
|
|
26
|
-
{
|
|
27
|
-
|
|
28
|
-
|
|
42
|
+
{
|
|
43
|
+
id: "title",
|
|
44
|
+
type: "text",
|
|
45
|
+
x: 0, y: 5, w: 100, h: 10,
|
|
46
|
+
content: "VISITOR PASS",
|
|
47
|
+
style: { fontSize: 14, fontWeight: "bold", textAlign: "center" }
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "name",
|
|
51
|
+
type: "text",
|
|
52
|
+
x: 5, y: 25, w: 60, h: 10,
|
|
53
|
+
content: "{{name}}", // Placeholder
|
|
54
|
+
style: { fontSize: 12 }
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "qr-code",
|
|
58
|
+
type: "qr",
|
|
59
|
+
x: 70, y: 20, w: 25, h: 25,
|
|
60
|
+
content: "{{visitorId}}" // Placeholder
|
|
61
|
+
}
|
|
29
62
|
]
|
|
30
63
|
};
|
|
64
|
+
```
|
|
31
65
|
|
|
32
|
-
|
|
33
|
-
const printer = new StickerPrinter();
|
|
66
|
+
### 2. Render or Export
|
|
34
67
|
|
|
35
|
-
|
|
36
|
-
const canvas = document.getElementById("preview-canvas") as HTMLCanvasElement;
|
|
37
|
-
await printer.renderToCanvas(layout, data, canvas);
|
|
68
|
+
Pass data to the `StickerPrinter` to generate outputs.
|
|
38
69
|
|
|
39
|
-
|
|
40
|
-
|
|
70
|
+
```ts
|
|
71
|
+
import { StickerPrinter } from "qrlayout-core";
|
|
41
72
|
|
|
42
|
-
//
|
|
43
|
-
const
|
|
44
|
-
|
|
73
|
+
// Data to fill placeholders
|
|
74
|
+
const data = {
|
|
75
|
+
name: "Priyanka Verma",
|
|
76
|
+
visitorId: "https://example.com/visitors/priyanka"
|
|
77
|
+
};
|
|
45
78
|
|
|
46
|
-
|
|
47
|
-
const zpl = printer.exportToZPL(layout, [data]);
|
|
48
|
-
console.log(zpl[0]);
|
|
49
|
-
```
|
|
79
|
+
const printer = new StickerPrinter();
|
|
50
80
|
|
|
51
|
-
|
|
81
|
+
// --- Option A: Render to HTML Canvas (Browser) ---
|
|
82
|
+
const canvas = document.querySelector("#preview") as HTMLCanvasElement;
|
|
83
|
+
await printer.renderToCanvas(myLayout, data, canvas);
|
|
84
|
+
|
|
85
|
+
// --- Option B: Get an Image URL (PNG) ---
|
|
86
|
+
const imageUrl = await printer.renderToDataURL(myLayout, data, { format: "png", dpi: 300 });
|
|
87
|
+
console.log(imageUrl); // "data:image/png;base64,..."
|
|
88
|
+
|
|
89
|
+
// --- Option C: Generate ZPL for Thermal Printers ---
|
|
90
|
+
const zplCommands = printer.exportToZPL(myLayout, [data]);
|
|
91
|
+
console.log(zplCommands[0]); // "^XA^FO..."
|
|
52
92
|
|
|
53
|
-
```ts
|
|
54
|
-
type Unit = "mm" | "px" | "cm" | "in";
|
|
55
|
-
type ElementType = "text" | "qr" | "image";
|
|
56
|
-
|
|
57
|
-
interface StickerElement {
|
|
58
|
-
id: string;
|
|
59
|
-
type: ElementType;
|
|
60
|
-
x: number;
|
|
61
|
-
y: number;
|
|
62
|
-
w: number;
|
|
63
|
-
h: number;
|
|
64
|
-
content: string; // supports {{placeholders}}
|
|
65
|
-
style?: {
|
|
66
|
-
fontFamily?: string;
|
|
67
|
-
fontSize?: number;
|
|
68
|
-
fontWeight?: string | number;
|
|
69
|
-
textAlign?: "left" | "center" | "right";
|
|
70
|
-
color?: string;
|
|
71
|
-
backgroundColor?: string;
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
interface StickerLayout {
|
|
76
|
-
id: string;
|
|
77
|
-
name: string;
|
|
78
|
-
width: number;
|
|
79
|
-
height: number;
|
|
80
|
-
unit: Unit;
|
|
81
|
-
elements: StickerElement[];
|
|
82
|
-
backgroundColor?: string;
|
|
83
|
-
backgroundImage?: string;
|
|
84
|
-
}
|
|
85
93
|
```
|
|
86
94
|
|
|
87
|
-
##
|
|
95
|
+
## PDF Export (Optional)
|
|
88
96
|
|
|
89
|
-
|
|
90
|
-
- `renderToDataURL` for PNG/JPEG/WebP export
|
|
91
|
-
- `exportToPDF` for print-ready PDF (optional)
|
|
92
|
-
- `exportToZPL` for Zebra printers
|
|
97
|
+
To enable PDF export, you must install `jspdf`. This is an optional peer dependency to prevent bloating the core library for users who don't need PDF support.
|
|
93
98
|
|
|
94
|
-
|
|
99
|
+
### 1. Install jspdf
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npm install jspdf
|
|
103
|
+
```
|
|
95
104
|
|
|
96
|
-
|
|
105
|
+
### 2. Use the PDF Module
|
|
97
106
|
|
|
98
107
|
```ts
|
|
99
108
|
import { exportToPDF } from "qrlayout-core/pdf";
|
|
109
|
+
|
|
110
|
+
// Generate a PDF with multiple stickers (e.g., standard A4 sheet logic can be handled by caller,
|
|
111
|
+
// this function currently outputs one page per sticker or as configured).
|
|
112
|
+
const pdfDoc = await exportToPDF(myLayout, [data, data2, data3]);
|
|
113
|
+
|
|
114
|
+
// Save the PDF
|
|
115
|
+
pdfDoc.save("badges.pdf");
|
|
100
116
|
```
|
|
101
117
|
|
|
102
|
-
|
|
118
|
+
## API Reference
|
|
119
|
+
|
|
120
|
+
### `StickerLayout` Interface
|
|
121
|
+
|
|
122
|
+
| Property | Type | Description |
|
|
123
|
+
|----------|------|-------------|
|
|
124
|
+
| `width` | `number` | Width of the sticker. |
|
|
125
|
+
| `height` | `number` | Height of the sticker. |
|
|
126
|
+
| `unit` | `"mm" \| "cm" \| "in" \| "px"` | Unit of measurement. |
|
|
127
|
+
| `elements` | `StickerElement[]` | List of items on the sticker. |
|
|
128
|
+
|
|
129
|
+
### `StickerElement` Interface
|
|
130
|
+
|
|
131
|
+
| Property | Type | Description |
|
|
132
|
+
|----------|------|-------------|
|
|
133
|
+
| `type` | `"text" \| "qr"` | Type of element. |
|
|
134
|
+
| `x`, `y` | `number` | Position from top-left. |
|
|
135
|
+
| `w`, `h` | `number` | Width and height. |
|
|
136
|
+
| `content` | `string` | Text, URL, or image source. Supports `{{key}}` syntax. |
|
|
103
137
|
|
|
104
|
-
##
|
|
138
|
+
## License
|
|
105
139
|
|
|
106
|
-
|
|
107
|
-
- For ZPL, images are not embedded (text and QR only).
|
|
108
|
-
- When using external images in the browser, CORS headers are required or the canvas export will fail.
|
|
140
|
+
MIT
|
package/dist/layout/schema.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
export type Unit = "mm" | "px" | "cm" | "in";
|
|
2
|
-
export type ElementType = "text" | "qr"
|
|
2
|
+
export type ElementType = "text" | "qr";
|
|
3
3
|
export interface ElementStyle {
|
|
4
4
|
fontFamily?: string;
|
|
5
5
|
fontSize?: number;
|
|
6
6
|
fontWeight?: string | number;
|
|
7
7
|
textAlign?: "left" | "center" | "right";
|
|
8
|
+
verticalAlign?: "top" | "middle" | "bottom";
|
|
8
9
|
color?: string;
|
|
9
10
|
backgroundColor?: string;
|
|
10
11
|
}
|
package/dist/pdf.js
CHANGED
|
@@ -59,16 +59,6 @@ export async function exportToPDF(layout, dataList) {
|
|
|
59
59
|
doc.addImage(qrUrl, "PNG", x, y, w, h);
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
else if (element.type === "image") {
|
|
63
|
-
if (filledContent) {
|
|
64
|
-
try {
|
|
65
|
-
doc.addImage(filledContent, "PNG", x, y, w, h);
|
|
66
|
-
}
|
|
67
|
-
catch (e) {
|
|
68
|
-
console.warn("Could not add image to PDF", e);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
62
|
else if (element.type === "text") {
|
|
73
63
|
const style = element.style || {};
|
|
74
64
|
const fontSize = style.fontSize || 12;
|
|
@@ -81,8 +71,14 @@ export async function exportToPDF(layout, dataList) {
|
|
|
81
71
|
drawX = x + w / 2;
|
|
82
72
|
if (align === "right")
|
|
83
73
|
drawX = x + w;
|
|
84
|
-
|
|
85
|
-
|
|
74
|
+
let drawY = y;
|
|
75
|
+
const vAlign = style.verticalAlign || "top";
|
|
76
|
+
if (vAlign === "middle")
|
|
77
|
+
drawY = y + h / 2;
|
|
78
|
+
if (vAlign === "bottom")
|
|
79
|
+
drawY = y + h;
|
|
80
|
+
doc.text(filledContent, drawX, drawY, {
|
|
81
|
+
baseline: vAlign === "middle" ? "middle" : (vAlign === "bottom" ? "bottom" : "top"),
|
|
86
82
|
align: align
|
|
87
83
|
});
|
|
88
84
|
}
|
|
@@ -51,11 +51,6 @@ export class StickerPrinter {
|
|
|
51
51
|
else if (element.type === "text") {
|
|
52
52
|
this.drawText(ctx, element, filledContent, x, y, w, h);
|
|
53
53
|
}
|
|
54
|
-
else if (element.type === "image") {
|
|
55
|
-
if (filledContent) { // Assume content is URL
|
|
56
|
-
await this.drawImage(ctx, filledContent, x, y, w, h);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
54
|
}
|
|
60
55
|
}
|
|
61
56
|
async renderToDataURL(layout, data, options) {
|
|
@@ -79,15 +74,22 @@ export class StickerPrinter {
|
|
|
79
74
|
const fontWeight = style.fontWeight || "normal";
|
|
80
75
|
ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;
|
|
81
76
|
ctx.fillStyle = style.color || "#000";
|
|
82
|
-
|
|
77
|
+
// Handle Vertical Alignment (textBaseline)
|
|
78
|
+
ctx.textBaseline = style.verticalAlign === "middle" ? "middle" : (style.verticalAlign === "bottom" ? "bottom" : "top");
|
|
83
79
|
ctx.textAlign = style.textAlign || "left";
|
|
84
|
-
// Handle Alignment X
|
|
80
|
+
// Handle Horizontal Alignment X adjustment
|
|
85
81
|
let drawX = x;
|
|
86
82
|
if (style.textAlign === "center")
|
|
87
83
|
drawX = x + w / 2;
|
|
88
84
|
if (style.textAlign === "right")
|
|
89
85
|
drawX = x + w;
|
|
90
|
-
|
|
86
|
+
// Handle Vertical Alignment Y adjustment
|
|
87
|
+
let drawY = y;
|
|
88
|
+
if (style.verticalAlign === "middle")
|
|
89
|
+
drawY = y + h / 2;
|
|
90
|
+
if (style.verticalAlign === "bottom")
|
|
91
|
+
drawY = y + h;
|
|
92
|
+
ctx.fillText(text, drawX, drawY);
|
|
91
93
|
}
|
|
92
94
|
drawImage(ctx, url, x, y, w, h) {
|
|
93
95
|
return new Promise((resolve) => {
|