246 lines
7.8 KiB
Dart
246 lines
7.8 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../domain/label_document.dart';
|
|
|
|
class PrintCompiler {
|
|
/// Compiles a LabelDocument into printer commands according to the chosen language
|
|
static String compile({
|
|
required LabelDocument document,
|
|
String? overrideLanguage,
|
|
int copies = 1,
|
|
}) {
|
|
final language = (overrideLanguage ?? document.config.printerLanguage).toUpperCase();
|
|
switch (language) {
|
|
case 'TSPL':
|
|
return TsplCompiler.compile(document, copies: copies);
|
|
case 'ZPL':
|
|
default:
|
|
return ZplCompiler.compile(document, copies: copies);
|
|
}
|
|
}
|
|
}
|
|
|
|
class ZplCompiler {
|
|
static String compile(LabelDocument doc, {int copies = 1}) {
|
|
final buffer = StringBuffer();
|
|
final cfg = doc.config;
|
|
|
|
// Convert total web width (accounting for 1-Up, 2-Up, etc.) to printer dots
|
|
final totalWebWidthDots = cfg.totalWebWidthDots;
|
|
final heightDots = cfg.heightDots;
|
|
|
|
buffer.writeln('^XA'); // Start of Label
|
|
buffer.writeln('^PW$totalWebWidthDots'); // Print Width across entire roll
|
|
buffer.writeln('^LL$heightDots'); // Label Length
|
|
buffer.writeln('^LS0'); // Label Shift
|
|
buffer.writeln('^LH0,0'); // Label Home
|
|
buffer.writeln('^CI28'); // UTF-8 Encoding
|
|
|
|
// Sort elements by zIndex
|
|
final sortedElements = List<LabelElement>.from(doc.elements)
|
|
..sort((a, b) => a.zIndex.compareTo(b.zIndex));
|
|
|
|
// Iterate across each column in the multi-up row (1-Up, 2-Up, 3-Up)
|
|
final cols = cfg.columnsAcross.clamp(1, 4);
|
|
for (int col = 0; col < cols; col++) {
|
|
final colOffsetMm = col * (cfg.widthMm + cfg.horizontalGapMm);
|
|
final colOffsetDots = cfg.mmToDots(colOffsetMm);
|
|
|
|
for (final elem in sortedElements) {
|
|
final xDots = cfg.mmToDots(elem.xMm) + colOffsetDots;
|
|
final yDots = cfg.mmToDots(elem.yMm);
|
|
final wDots = cfg.mmToDots(elem.widthMm);
|
|
final hDots = cfg.mmToDots(elem.heightMm);
|
|
|
|
if (elem is TextElement) {
|
|
_compileText(buffer, elem, cfg, xDots, yDots, wDots);
|
|
} else if (elem is DynamicTextElement) {
|
|
// In design mode or unresolved templates, output the placeholder
|
|
final textElem = TextElement(
|
|
id: elem.id,
|
|
xMm: elem.xMm,
|
|
yMm: elem.yMm,
|
|
widthMm: elem.widthMm,
|
|
heightMm: elem.heightMm,
|
|
text: elem.placeholder,
|
|
fontFamily: elem.fontFamily,
|
|
fontSizePt: elem.fontSizePt,
|
|
isBold: elem.isBold,
|
|
isItalic: elem.isItalic,
|
|
alignment: elem.alignment,
|
|
);
|
|
_compileText(buffer, textElem, cfg, xDots, yDots, wDots);
|
|
} else if (elem is BarcodeElement) {
|
|
_compileBarcode(buffer, elem, cfg, xDots, yDots, wDots, hDots);
|
|
} else if (elem is QrCodeElement) {
|
|
_compileQrCode(buffer, elem, cfg, xDots, yDots, wDots);
|
|
} else if (elem is RectangleElement) {
|
|
_compileRectangle(buffer, elem, cfg, xDots, yDots, wDots, hDots);
|
|
} else if (elem is LineElement) {
|
|
_compileLine(buffer, elem, cfg, xDots, yDots, wDots, hDots);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (copies > 1) {
|
|
buffer.writeln('^PQ$copies');
|
|
}
|
|
|
|
buffer.writeln('^XZ'); // End of Label
|
|
return buffer.toString();
|
|
}
|
|
|
|
static void _compileText(
|
|
StringBuffer buf,
|
|
TextElement elem,
|
|
LabelConfiguration cfg,
|
|
int xDots,
|
|
int yDots,
|
|
int wDots,
|
|
) {
|
|
// Standard font height calculation based on point size & DPI
|
|
// 1 pt = 1/72 inch. dots = pt / 72 * DPI
|
|
final fontHeightDots = ((elem.fontSizePt / 72.0) * cfg.dpi * 1.33).round().clamp(14, 300);
|
|
final fontWidthDots = (fontHeightDots * 0.85).round();
|
|
|
|
final alignCode = switch (elem.alignment) {
|
|
TextAlign.center => 'C',
|
|
TextAlign.right => 'R',
|
|
TextAlign.justify => 'J',
|
|
_ => 'L',
|
|
};
|
|
|
|
// Replace unicode Rupee sign with 'Rs. ' as printer internal font 0 does not have U+20B9
|
|
final printableText = elem.text.replaceAll('₹', 'Rs. ');
|
|
|
|
// Text block with field formatting
|
|
buf.writeln('^FO$xDots,$yDots');
|
|
buf.writeln('^A0N,$fontHeightDots,$fontWidthDots');
|
|
buf.writeln('^FB$wDots,2,0,$alignCode,0');
|
|
buf.writeln('^FD$printableText^FS');
|
|
}
|
|
|
|
static void _compileBarcode(
|
|
StringBuffer buf,
|
|
BarcodeElement elem,
|
|
LabelConfiguration cfg,
|
|
int xDots,
|
|
int yDots,
|
|
int wDots,
|
|
int hDots,
|
|
) {
|
|
final barcodeVal = (elem.valueType == 'dynamic' && elem.staticValue.isEmpty)
|
|
? elem.placeholder
|
|
: elem.staticValue;
|
|
|
|
// Module width: 2 dots for 203 DPI, 3 dots for 300 DPI (Citizen CL-E331) for readable scanning
|
|
final moduleWidth = (cfg.dpi >= 300) ? 3 : 2;
|
|
final barHeight = (hDots * (elem.showText ? 0.82 : 0.95)).round().clamp(24, 600);
|
|
final showTextChar = elem.showText ? 'Y' : 'N';
|
|
|
|
buf.writeln('^FO$xDots,$yDots');
|
|
buf.writeln('^BY$moduleWidth,3,$barHeight');
|
|
|
|
switch (elem.barcodeType) {
|
|
case BarcodeType.ean13:
|
|
buf.writeln('^BEN,$barHeight,$showTextChar,N');
|
|
break;
|
|
case BarcodeType.code39:
|
|
buf.writeln('^B3N,N,$barHeight,$showTextChar,N');
|
|
break;
|
|
case BarcodeType.upca:
|
|
buf.writeln('^BUN,$barHeight,$showTextChar,N,N');
|
|
break;
|
|
case BarcodeType.code128:
|
|
default:
|
|
buf.writeln('^BCN,$barHeight,$showTextChar,N,N');
|
|
break;
|
|
}
|
|
|
|
buf.writeln('^FD$barcodeVal^FS');
|
|
}
|
|
|
|
static void _compileQrCode(
|
|
StringBuffer buf,
|
|
QrCodeElement elem,
|
|
LabelConfiguration cfg,
|
|
int xDots,
|
|
int yDots,
|
|
int wDots,
|
|
) {
|
|
final qrVal = (elem.valueType == 'dynamic' && elem.staticValue.isEmpty)
|
|
? elem.placeholder
|
|
: elem.staticValue;
|
|
// Magnification factor (1 to 10)
|
|
final mag = (wDots / 30).round().clamp(2, 8);
|
|
|
|
buf.writeln('^FO$xDots,$yDots');
|
|
buf.writeln('^BQN,2,$mag');
|
|
buf.writeln('^FDQA,$qrVal^FS');
|
|
}
|
|
|
|
static void _compileRectangle(
|
|
StringBuffer buf,
|
|
RectangleElement elem,
|
|
LabelConfiguration cfg,
|
|
int xDots,
|
|
int yDots,
|
|
int wDots,
|
|
int hDots,
|
|
) {
|
|
final borderDots = cfg.mmToDots(elem.borderWidthMm).clamp(1, 40);
|
|
final cornerDots = cfg.mmToDots(elem.cornerRadiusMm).clamp(0, 8);
|
|
buf.writeln('^FO$xDots,$yDots^GB$wDots,$hDots,$borderDots,B,$cornerDots^FS');
|
|
}
|
|
|
|
static void _compileLine(
|
|
StringBuffer buf,
|
|
LineElement elem,
|
|
LabelConfiguration cfg,
|
|
int xDots,
|
|
int yDots,
|
|
int wDots,
|
|
int hDots,
|
|
) {
|
|
final thickDots = cfg.mmToDots(elem.thicknessMm).clamp(1, 30);
|
|
if (elem.isVertical) {
|
|
buf.writeln('^FO$xDots,$yDots^GB$thickDots,$hDots,$thickDots^FS');
|
|
} else {
|
|
buf.writeln('^FO$xDots,$yDots^GB$wDots,$thickDots,$thickDots^FS');
|
|
}
|
|
}
|
|
}
|
|
|
|
class TsplCompiler {
|
|
static String compile(LabelDocument doc, {int copies = 1}) {
|
|
final buffer = StringBuffer();
|
|
final cfg = doc.config;
|
|
final cols = cfg.columnsAcross.clamp(1, 4);
|
|
|
|
buffer.writeln('SIZE ${cfg.totalWebWidthMm} mm, ${cfg.heightMm} mm');
|
|
buffer.writeln('GAP ${cfg.verticalGapMm.toStringAsFixed(cfg.verticalGapMm % 1 == 0 ? 0 : 1)} mm, 0 mm');
|
|
buffer.writeln('DIRECTION 1');
|
|
buffer.writeln('CLS');
|
|
|
|
for (int col = 0; col < cols; col++) {
|
|
final colOffsetMm = col * (cfg.widthMm + cfg.horizontalGapMm);
|
|
final colOffsetDots = cfg.mmToDots(colOffsetMm);
|
|
|
|
for (final elem in doc.elements) {
|
|
final xDots = cfg.mmToDots(elem.xMm) + colOffsetDots;
|
|
final yDots = cfg.mmToDots(elem.yMm);
|
|
|
|
if (elem is TextElement) {
|
|
buffer.writeln('TEXT $xDots,$yDots,"3",0,1,1,"${elem.text}"');
|
|
} else if (elem is BarcodeElement) {
|
|
final val = elem.valueType == 'dynamic' ? elem.placeholder : elem.staticValue;
|
|
final hDots = cfg.mmToDots(elem.heightMm);
|
|
buffer.writeln('BARCODE $xDots,$yDots,"128",$hDots,${elem.showText ? 1 : 0},0,2,2,"$val"');
|
|
}
|
|
}
|
|
}
|
|
|
|
buffer.writeln('PRINT $copies,1');
|
|
return buffer.toString();
|
|
}
|
|
}
|