Flutter如何将PDF打印至热敏打印机?解决类型转换异常
解决Flutter中使用print_bluetooth_thermal打印PDF收据到热敏打印机的问题
问题分析
直接打印PDF字节的错误本质:
你遇到的ClassCastException: byte[] cannot be cast to java.util.List是插件Android端的类型转换问题,但核心矛盾是热敏打印机不支持直接解析PDF文件,必须将PDF内容转换成打印机可识别的格式(如单色适配图片、ESC/POS指令)才能正常打印。转图片打印失败的常见原因:
- 图片尺寸未匹配打印机固定宽度(热敏打印机通常为58mm/80mm,对应特定像素宽度)
- 未将图片转为单色(热敏打印机仅支持黑白打印,彩色/灰度图片会出现模糊或无法识别)
- 直接写入PNG字节未经过打印机适配的指令封装,导致设备无法解析
可行解决方案
方案1:优化PDF转单色适配图片打印
修改现有转图片逻辑,调整尺寸、转为单色,并生成打印机可识别的ESC/POS指令:
import 'dart:ui' as ui; import 'package:printing/printing.dart'; import 'package:image/image.dart' as img; // 需要添加image包依赖 Future<void> printPdfAsImage(File pdfFile) async { if (!pdfFile.existsSync()) { print('文件不存在'); return; } bool conexionStatus = await PrintBluetoothThermal.connectionStatus; if (!conexionStatus) { print('打印机未连接'); return; } final pdfBytes = pdfFile.readAsBytesSync(); // 80mm宽打印机用203DPI对应640像素,58mm宽对应384像素,按需调整 final pages = Printing.raster(pdfBytes, dpi: 203, width: 640); await for (final page in pages) { final image = await page.toImage(); final byteData = await image.toByteData(format: ui.ImageByteFormat.rawRgba); if (byteData == null) continue; // 将UI图片转为image库格式 final img.Image rawImage = img.Image.fromBytes( width: image.width, height: image.height, bytes: byteData.buffer.asUint8List(), format: img.Format.rgba, ); // 转为黑白单色图片 final img.Image monoImage = img.grayscale(rawImage); final img.Image thresholdImage = img.threshold(monoImage, threshold: 128); // 生成ESC/POS图片打印指令并发送 final List<int> escPosCommand = _generateEscPosImage(thresholdImage); final result = await PrintBluetoothThermal.writeBytes(escPosCommand); print("打印结果: $result"); // 打印后添加换行 await PrintBluetoothThermal.writeBytes('\n\n'.codeUnits); } pdfFile.delete(); } // 生成适配热敏打印机的ESC/POS图片指令 List<int> _generateEscPosImage(img.Image image) { List<int> command = []; // 初始化打印机 command.addAll([0x1B, 0x40]); // 设置单密度图片打印模式 command.addAll([0x1B, 0x2A, 0x01]); // 图片宽度(字节数,向上取整) int widthBytes = (image.width + 7) ~/ 8; command.addAll([widthBytes & 0xFF, (widthBytes >> 8) & 0xFF]); // 图片高度 command.addAll([image.height & 0xFF, (image.height >> 8) & 0xFF]); // 逐行写入图片像素数据(黑白反转适配热敏打印) for (int y = 0; y < image.height; y++) { for (int x = 0; x < widthBytes; x++) { byte pixelByte = 0; for (int bit = 0; bit < 8; bit++) { int pixelX = x * 8 + bit; if (pixelX < image.width) { int pixel = image.getPixel(pixelX, y); // 黑色像素设为1,白色设为0 if (img.getRed(pixel) < 128) { pixelByte |= (1 << (7 - bit)); } } } command.add(pixelByte); } } return command; }
方案2:直接生成ESC/POS收据(推荐)
如果你的PDF是结构简单的收据类文档,跳过PDF直接生成ESC/POS指令是更高效的方式,避免转图片的性能损耗:
Future<void> printReceiptDirectly() async { bool conexionStatus = await PrintBluetoothThermal.connectionStatus; if (!conexionStatus) { print('打印机未连接'); return; } List<int> receipt = []; // 初始化打印机 receipt.addAll([0x1B, 0x40]); // 设置字体加粗 receipt.addAll([0x1B, 0x45, 0x01]); // 添加标题 receipt.addAll('订单收据\n'.codeUnits); receipt.addAll([0x1B, 0x45, 0x00]); // 取消加粗 // 订单详情 receipt.addAll('订单号: 20240520001\n'.codeUnits); receipt.addAll('日期: 2024-05-20\n'.codeUnits); receipt.addAll('------------------------\n'.codeUnits); receipt.addAll('商品: 奶茶 x1\n'.codeUnits); receipt.addAll('金额: 15.00元\n'.codeUnits); receipt.addAll('------------------------\n'.codeUnits); receipt.addAll('总计: 15.00元\n'.codeUnits); // 切纸指令 receipt.addAll([0x1D, 0x56, 0x00]); final result = await PrintBluetoothThermal.writeBytes(receipt); print("打印结果: $result"); }
关键注意事项
- 添加依赖:使用
image库需在pubspec.yaml中添加image: ^4.0.17(版本号以最新为准) - 确认打印机的实际宽度和DPI,调整代码中的对应参数
- 优先选择直接生成ESC/POS指令的方式,打印速度更快、效果更稳定
内容的提问来源于stack exchange,提问作者Boby
相关产品推荐
相关产品推荐

