printComplexReceipt static method
Print a complex receipt with mixed content types.
contents: List of content items (text, images, barcodes, etc.)printerWidth: Printer width in dotspadding: Padding around contentcutPaperAfter: Whether to cut paper after printingoptimizeAsciiText: Whether to optimize ASCII text printing
Returns true if printing was successful.
Implementation
static Future<bool> printComplexReceipt({
required List<ReceiptContent> contents,
int printerWidth = 576,
double padding = 10,
bool cutPaperAfter = true,
bool optimizeAsciiText = true, // Enable/disable optimization
}) async {
try {
// Optimization: Directly print ASCII text when possible
if (optimizeAsciiText) {
bool overallSuccess = true;
List<ReceiptContent> imageRenderBatch = [];
// Process each content item
for (int i = 0; i < contents.length; i++) {
ReceiptContent content = contents[i];
// Check if this content can be directly printed (ASCII text or blank lines)
if (_canDirectPrint(content)) {
// If we have a batch of items to image-render, do that first
if (imageRenderBatch.isNotEmpty) {
final batchResult = await _printContentBatchAsImage(
imageRenderBatch, printerWidth, padding);
if (!batchResult) overallSuccess = false;
imageRenderBatch = []; // Clear the batch
}
// Print this content directly
final directResult = await _printContentDirect(content);
if (!directResult) overallSuccess = false;
} else if (content.type == ReceiptContentType.barcode) {
// Handle barcode printing separately (since we have a specialized function)
if (imageRenderBatch.isNotEmpty) {
final batchResult = await _printContentBatchAsImage(
imageRenderBatch, printerWidth, padding);
if (!batchResult) overallSuccess = false;
imageRenderBatch = []; // Clear the batch
}
// Print barcode using our specialized function
final barcodeResult =
await _printBarcodeContent(content, printerWidth);
if (!barcodeResult) overallSuccess = false;
} else {
// Add to the batch for image rendering
imageRenderBatch.add(content);
}
}
// Print any remaining batch items
if (imageRenderBatch.isNotEmpty) {
final batchResult = await _printContentBatchAsImage(
imageRenderBatch, printerWidth, padding);
if (!batchResult) overallSuccess = false;
}
// Cut paper if requested
if (overallSuccess && cutPaperAfter) {
await PrinterCore.cutPaper();
}
return overallSuccess;
}
// Original non-optimized implementation (render everything as an image)
// Set up working directory for temporary files
final directory = await getTemporaryDirectory();
final tempPath =
'${directory.path}/complex_receipt_${DateTime.now().millisecondsSinceEpoch}.png';
// First pass: Calculate total height needed
double totalHeight = 0;
List<double> contentHeights = [];
for (var content in contents) {
double itemHeight = 0;
switch (content.type) {
case ReceiptContentType.text:
String text = content.data as String;
final fontSize = content.options?['fontSize'] as double? ?? 20;
final fontFamily =
content.options?['fontFamily'] as String? ?? 'Siemreap';
final lineSpacing =
content.options?['lineSpacing'] as double? ?? 1.5;
// Calculate text height
final textSpan = TextSpan(
text: text,
style: TextStyle(
fontSize: fontSize,
fontFamily: fontFamily,
),
);
final textPainter = TextPainter(
text: textSpan,
textDirection: TextDirection.ltr,
);
textPainter.layout(
maxWidth: printerWidth.toDouble() - (padding * 2));
itemHeight = textPainter.height * lineSpacing;
break;
case ReceiptContentType.centeredText:
String text = content.data as String;
final fontSize = content.options?['fontSize'] as double? ?? 24;
final fontFamily =
content.options?['fontFamily'] as String? ?? 'Siemreap';
// Calculate centered text height
final textSpan = TextSpan(
text: text,
style: TextStyle(
fontSize: fontSize,
fontFamily: fontFamily,
),
);
final textPainter = TextPainter(
text: textSpan,
textDirection: TextDirection.ltr,
textAlign: TextAlign.center,
);
textPainter.layout();
itemHeight = textPainter.height + (padding * 2);
break;
case ReceiptContentType.multiColumn:
final columns = content.data as List<PrintColumn>;
final lineHeight = content.options?['lineHeight'] as double? ?? 1.5;
final rowPadding = content.options?['padding'] as double? ?? 10;
// Calculate multi-column height based on the tallest text
double maxHeight = 0;
for (var col in columns) {
final textSpan = TextSpan(
text: col.text,
style: TextStyle(
fontSize: col.fontSize,
fontFamily: col.fontFamily,
),
);
final textPainter = TextPainter(
text: textSpan,
textDirection: TextDirection.ltr,
);
textPainter.layout();
if (textPainter.height > maxHeight) {
maxHeight = textPainter.height;
}
}
itemHeight = maxHeight * lineHeight + (rowPadding * 2);
break;
case ReceiptContentType.barcode:
final type =
content.options?['type'] as BarcodeType? ?? BarcodeType.code128;
final height = content.options?['height'] as int? ?? 100;
final textPosition =
content.options?['textPosition'] as BarcodeTextPosition? ??
BarcodeTextPosition.below;
final showText = textPosition != BarcodeTextPosition.none;
// Calculate barcode height
itemHeight =
height.toDouble() + (showText ? 30 : 0) + (padding * 2);
break;
case ReceiptContentType.image:
// For images, use the specified height or a default
itemHeight = (content.options?['height'] as double?) ?? 200;
break;
case ReceiptContentType.blankLines:
final lines = content.data as int;
itemHeight = lines * 20.0; // Estimate 20 pixels per line
break;
}
contentHeights.add(itemHeight);
totalHeight += itemHeight;
}
// Create recorder and canvas for the combined image
final recorder = PictureRecorder();
final canvas = Canvas(recorder);
// Draw white background
canvas.drawRect(
Rect.fromLTWH(0, 0, printerWidth.toDouble(), totalHeight),
Paint()..color = Colors.white,
);
// Current Y position for drawing
double currentY = 0;
// Second pass: Draw each element to the canvas
for (int i = 0; i < contents.length; i++) {
final content = contents[i];
final itemHeight = contentHeights[i];
switch (content.type) {
case ReceiptContentType.text:
final text = content.data as String;
final fontSize = content.options?['fontSize'] as double? ?? 20;
final fontFamily =
content.options?['fontFamily'] as String? ?? 'Siemreap';
final fontWeight = content.options?['fontWeight'] as FontWeight? ??
FontWeight.normal;
final color = content.options?['color'] as Color? ?? Colors.black;
final textSpan = TextSpan(
text: text,
style: TextStyle(
fontSize: fontSize,
fontFamily: fontFamily,
fontWeight: fontWeight,
color: color,
),
);
final textPainter = TextPainter(
text: textSpan,
textDirection: TextDirection.ltr,
);
textPainter.layout(
maxWidth: printerWidth.toDouble() - (padding * 2));
textPainter.paint(canvas, Offset(padding, currentY + padding));
break;
case ReceiptContentType.centeredText:
final text = content.data as String;
final fontSize = content.options?['fontSize'] as double? ?? 24;
final fontFamily =
content.options?['fontFamily'] as String? ?? 'Siemreap';
final fontWeight = content.options?['fontWeight'] as FontWeight? ??
FontWeight.normal;
final color = content.options?['color'] as Color? ?? Colors.black;
final textSpan = TextSpan(
text: text,
style: TextStyle(
fontSize: fontSize,
fontFamily: fontFamily,
fontWeight: fontWeight,
color: color,
),
);
final textPainter = TextPainter(
text: textSpan,
textDirection: TextDirection.ltr,
textAlign: TextAlign.center,
);
textPainter.layout(
maxWidth: printerWidth.toDouble() - (padding * 2));
// Center text horizontally
final dx = (printerWidth - textPainter.width) / 2;
textPainter.paint(
canvas, Offset(dx > 0 ? dx : padding, currentY + padding));
break;
case ReceiptContentType.multiColumn:
final columns = content.data as List<PrintColumn>;
final rowPadding = content.options?['padding'] as double? ?? 10;
for (var col in columns) {
final textSpan = TextSpan(
text: col.text,
style: TextStyle(
fontSize: col.fontSize,
fontWeight: col.fontWeight,
fontFamily: col.fontFamily,
color: col.color,
),
);
final textPainter = TextPainter(
text: textSpan,
textDirection: TextDirection.ltr,
textAlign: col.align,
);
textPainter.layout(maxWidth: printerWidth.toDouble());
double dx = col.x;
if (col.align == TextAlign.center) {
dx = col.x - textPainter.width / 2;
} else if (col.align == TextAlign.right) {
dx = col.x - textPainter.width;
}
textPainter.paint(canvas, Offset(dx, currentY + rowPadding));
}
break;
case ReceiptContentType.barcode:
final data = content.data as String;
final type =
content.options?['type'] as BarcodeType? ?? BarcodeType.qrCode;
final height = content.options?['height'] as int? ?? 100;
final width = content.options?['width'] as int? ?? 2;
final textPosition =
content.options?['textPosition'] as BarcodeTextPosition? ??
BarcodeTextPosition.below;
// For Code128 barcode
if (type == BarcodeType.code128) {
// Generate barcode pattern
final List<int> encodedData =
BarcodeGenerator.encodeCode128(data);
final List<String> patterns =
BarcodeGenerator.generateCode128Patterns(encodedData);
final String fullPattern = patterns.join();
// Calculate barcode dimensions
final double barMinWidth = width.toDouble();
final double barcodeWidth = fullPattern.length * barMinWidth;
final startX =
(printerWidth - barcodeWidth) / 2; // Center the barcode
double currentX = startX;
// Draw barcode bars
for (int j = 0; j < fullPattern.length; j++) {
if (fullPattern[j] == '1') {
canvas.drawRect(
Rect.fromLTWH(currentX, currentY + padding, barMinWidth,
height.toDouble()),
Paint()..color = Colors.black,
);
}
currentX += barMinWidth;
}
// Draw text under barcode if requested
if (textPosition != BarcodeTextPosition.none) {
final textY = currentY + height + padding + 5;
final textPainter = TextPainter(
text: TextSpan(
text: data,
style: const TextStyle(
fontSize: 16,
color: Colors.black,
),
),
textDirection: TextDirection.ltr,
textAlign: TextAlign.center,
);
textPainter.layout(maxWidth: printerWidth.toDouble());
// Center text under barcode
final dx = (printerWidth - textPainter.width) / 2;
textPainter.paint(canvas, Offset(dx, textY));
}
} else {
// For other barcode types (placeholder implementation)
final textPainter = TextPainter(
text: TextSpan(
text: "Barcode: $data",
style: const TextStyle(
fontSize: 16,
color: Colors.black,
),
),
textDirection: TextDirection.ltr,
);
textPainter.layout(
maxWidth: printerWidth.toDouble() - (padding * 2));
textPainter.paint(canvas, Offset(padding, currentY + padding));
}
break;
case ReceiptContentType.image:
// Image handling (placeholder implementation)
// In a real implementation, you would load and draw the image here
break;
case ReceiptContentType.blankLines:
// Nothing to draw for blank lines, we just add space
break;
}
// Move to the next position
currentY += itemHeight;
}
// Convert to image
final picture = recorder.endRecording();
final img = await picture.toImage(printerWidth, totalHeight.ceil());
final byteData = await img.toByteData(format: ImageByteFormat.png);
final buffer = byteData!.buffer.asUint8List();
// Save to temp file
await File(tempPath).writeAsBytes(buffer);
// Print the image as a single receipt
final result = await PrinterCore.printImage(
tempPath,
width: printerWidth,
alignment: TextFormatter.convertAlignment(Alignment.center),
);
// Cut paper if requested
if (result && cutPaperAfter) {
await PrinterCore.cutPaper();
}
return result;
} catch (e) {
print("Error printing complex receipt: $e");
return false;
}
}