Files
vexplor/frontend/lib/zplGenerator.ts
2026-03-04 20:51:00 +09:00

68 lines
2.1 KiB
TypeScript

/**
* ZPL(Zebra Programming Language) 생성
* ZD421 등 Zebra 프린터용 라벨 데이터 생성 (200 DPI = 8 dots/mm 기준)
*/
import { BarcodeLabelLayout } from "@/types/barcode";
const MM_TO_PX = 4;
const DOTS_PER_MM = 8; // 200 DPI
function pxToDots(px: number): number {
const mm = px / MM_TO_PX;
return Math.round(mm * DOTS_PER_MM);
}
export function generateZPL(layout: BarcodeLabelLayout): string {
const { width_mm, height_mm, components } = layout;
const widthDots = Math.round(width_mm * DOTS_PER_MM);
const heightDots = Math.round(height_mm * DOTS_PER_MM);
const lines: string[] = [
"^XA",
"^PW" + widthDots,
"^LL" + heightDots,
"^LH0,0",
];
const sorted = [...components].sort((a, b) => (a.zIndex ?? 0) - (b.zIndex ?? 0));
for (const c of sorted) {
const x = pxToDots(c.x);
const y = pxToDots(c.y);
const w = pxToDots(c.width);
const h = pxToDots(c.height);
if (c.type === "text") {
const fontH = Math.max(10, Math.min(120, (c.fontSize || 10) * 4)); // 대략적 변환
const fontW = Math.round(fontH * 0.6);
lines.push(`^FO${x},${y}`);
lines.push(`^A0N,${fontH},${fontW}`);
lines.push(`^FD${escapeZPL(c.content || "")}^FS`);
} else if (c.type === "barcode") {
if (c.barcodeType === "QR") {
const size = Math.min(w, h);
const qrSize = Math.max(1, Math.min(10, Math.round(size / 20)));
lines.push(`^FO${x},${y}`);
lines.push(`^BQN,2,${qrSize}`);
lines.push(`^FDQA,${escapeZPL(c.barcodeValue || "")}^FS`);
} else {
// CODE128: ^BC, CODE39: ^B3
const mod = c.barcodeType === "CODE39" ? "^B3N" : "^BCN";
const showText = c.showBarcodeText !== false ? "Y" : "N";
lines.push(`^FO${x},${y}`);
lines.push(`${mod},${Math.max(20, h - 10)},${showText},N,N`);
lines.push(`^FD${escapeZPL(c.barcodeValue || "")}^FS`);
}
}
// 이미지/선/사각형은 ZPL에서 비트맵 또는 ^GB 등으로 확장 가능 (생략)
}
lines.push("^XZ");
return lines.join("\n");
}
function escapeZPL(s: string): string {
return s.replace(/\^/g, "^^").replace(/~/g, "~~");
}