Files
desktop-plugin/lib/print/pavo/pavo_backend.dart
T
dzc 0a78ff1b58 feat: Windows PavoApi ffi 直打后端(Phase 3 代码完成,待实机验收)
- lib/print/pavo/:PavoApiW_x64.dll 的 dart:ffi 绑定(结构体布局对齐 C#,W 后缀导出名回退兼容)
- pavo_printer.dart:枚举(UTF-8/UTF-16 自动解码)/状态/色带信息/单卡直打/忙碌轮询(3 分钟超时)/LayerHolder 非托管内存管理
- pavo_backend.dart:全部 ffi 调用与像素转换跑在 Isolate.run,主 isolate HTTP 服务零阻塞
- lib/image/card_image.dart:PNG data URL → 纵横比选卡面 → 手动 alpha 铺白底 → BGR24/Gray8 紧凑像素(image 包 compositeImage 对全透明像素非 src-over 语义,改为逐像素混合对齐 C#)
- windows/ 目录:从 Flutter SDK windows.tmpl 手动实例化(mac 上 flutter create 拒绝生成的绕过),窗口标题"饭卡打印助手"
- windows/vendor/:收录 SDK 的 PavoApiW_x64.dll/PavoCardSDK_x64.dll/HeatTB3.dll/heatthcd.bin,CMake POST_BUILD 自动拷到 exe 旁
- backend.dart 工厂接通 Windows 分支;analyze 零问题、单测 12 个全过
2026-09-21 12:19:07 +08:00

122 lines
4.2 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:isolate';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';
import '../../image/card_image.dart';
import '../../server/protocol.dart';
import '../backend.dart';
import '../cups_backend.dart' show noopLog;
import 'pavo_ffi.dart';
import 'pavo_printer.dart';
/// Windows 打印后端:HiTi PavoApi 直打(BGR/K 分层 BITMAP)。
///
/// 所有 ffi 调用(含像素转换的 CPU 密集计算)都放进 `Isolate.run`:
/// PavoApi 的同步调用最长可阻塞数秒(发送 2MB 像素)、打印轮询最长 3 分钟,
/// 放独立 isolate 保证主 isolate 的 HTTP 服务(任务状态轮询)不被卡住。
/// DLL 与非托管指针都不跨 isolate(闭包内自建自释放)。
class PavoPrintBackend implements PrintBackend {
PavoPrintBackend({void Function(String line)? onLog}) : _log = onLog ?? noopLog;
final void Function(String line) _log;
@override
String get name => 'pavo';
@override
Future<PrinterStatus> getStatus() => Isolate.run(_statusSync);
PrinterStatus _statusSync() {
try {
final api = PavoApi();
final printers = PavoPrinter.enumUsbPrinters(api);
if (printers.isEmpty) {
return PrinterStatus.offline('打印机未连接');
}
final printer = printers.first.toNativeUtf16();
try {
final status = PavoPrinter.getStatus(api, printer);
final (ribbonType, remainCount) = PavoPrinter.getRibbonInfo(api, printer);
final dto = PrinterStatus(
online: true,
model: PavoPrinter.getDeviceInfoText(api, printer, PavoApi.devinfoModelName),
firmware: PavoPrinter.getDeviceInfoText(api, printer, PavoApi.devinfoFirmwareVersion),
connection: 'USB',
ribbon: RibbonInfo(
type: PavoPrinter.ribbonTypeName(ribbonType),
// 设备返回剩余张数,按常见 100 张装 YMCKO 色带折算百分比
remain: ribbonType == PavoApi.ribbonMissing ? -1 : (remainCount > 100 ? 100 : remainCount),
remainCount: remainCount,
),
status: status,
statusText: PavoPrinter.statusTextOf(status),
);
if (status == PavoApi.dsCardLow || status == PavoApi.dsCardOut) {
dto.cardLow = true;
}
return dto;
} finally {
calloc.free(printer);
}
} catch (e) {
return PrinterStatus.offline('状态读取失败:$e');
}
}
@override
Future<void> printOneCard(PrintRequest request) {
_log('Pavo 直打任务开始(份数 ${request.copies},后端在子 isolate 执行)');
final front = request.front!;
final back = request.back;
// 只把 data URL 字符串与份数传进 isolate;图层转换与 ffi 调用全部在子 isolate 内完成
final frontColor = front.color;
final frontK = front.k;
final backColor = back?.color;
final backK = back?.k;
final copies = request.copies;
return Isolate.run(() async {
final api = PavoApi();
final printers = PavoPrinter.enumUsbPrinters(api);
if (printers.isEmpty) {
throw StateError('未找到 USB 连接的 HiTi 证卡打印机');
}
final printer = printers.first.toNativeUtf16();
try {
final frontImg = CardImage.fromDataUrl(frontColor);
final frontBgr = frontImg.toBgr24();
final Uint8List? frontKGray;
if (frontK != null && frontK.isNotEmpty) {
frontKGray = CardImage.fromDataUrl(frontK).toGray8();
} else {
frontKGray = null;
}
final Uint8List? backBgr;
final Uint8List? backKGray;
if (backColor != null && backColor.isNotEmpty) {
final backImg = CardImage.fromDataUrl(backColor);
backBgr = backImg.toBgr24();
backKGray =
backK != null && backK.isNotEmpty ? CardImage.fromDataUrl(backK).toGray8() : null;
} else {
backBgr = null;
backKGray = null;
}
await PavoPrinter.printOneCard(
api,
printer,
frontBgr: frontBgr,
frontK: frontKGray,
backBgr: backBgr,
backK: backKGray,
isLandscape: frontImg.isLandscape,
copies: copies,
);
} finally {
calloc.free(printer);
}
});
}
}