/** * 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, "~~"); }