diff --git a/lib/print/pavo/pavo_printer.dart b/lib/print/pavo/pavo_printer.dart index a15ea17..b39f007 100644 --- a/lib/print/pavo/pavo_printer.dart +++ b/lib/print/pavo/pavo_printer.dart @@ -71,7 +71,29 @@ class PavoPrinter { } /// 按可打印字符占比自动判断 UTF-8 / UTF-16 编码。 + /// + /// 实测(Win11 无驱动裸机):枚举缓冲返回 UTF-16LE 的设备路径 + /// (\\?\usb#vid_xxxx&pid_xxxx#serial#{guid})。ASCII 的 UTF-16LE 呈现 + /// "奇数位字节全零"特征——printableRatio 会把 \0 算作可打印,导致 UTF-8 + /// 误判通过、路径被 \0 拆成单字符碎片(后续 API 拿 '\' 单字符全部 0x80)。 + /// 故先用奇数位全零特征做 UTF-16LE 判定,再回退比例启发。 static String decodeAuto(Uint8List buf) { + if (buf.length >= 8) { + var oddAllZero = true; + for (var i = 1; i < buf.length; i += 2) { + if (buf[i] != 0) { + oddAllZero = false; + break; + } + } + if (oddAllZero) { + final codes = []; + for (var i = 0; i + 1 < buf.length; i += 2) { + codes.add(buf[i] | (buf[i + 1] << 8)); + } + return String.fromCharCodes(codes); + } + } final utf8Text = utf8.decode(buf, allowMalformed: true); if (printableRatio(utf8Text) >= 0.9) { return utf8Text; diff --git a/lib/ui/setup_wizard.dart b/lib/ui/setup_wizard.dart index 5d7cfef..cc849df 100644 --- a/lib/ui/setup_wizard.dart +++ b/lib/ui/setup_wizard.dart @@ -228,8 +228,10 @@ class _SetupWizardState extends State { final ready = s != null && s.online && (s.status == 0 || s.status == 0x00000002 || s.status == 0x00000005 || s.status == 0x00000006); - final driverMissing = _driverInstalled == false; - final bundledInstaller = DriverInstall.bundledInstallerPath(); + // 仅 mac 需要驱动(CUPS);Windows 走 PavoApi 裸 USB 直连—— + // 实测装 HiTi 驱动反而会接管 USB 接口导致枚举为空,故 Windows 不提供驱动安装 + final driverMissing = Platform.isMacOS && _driverInstalled == false; + final bundledInstaller = Platform.isMacOS ? DriverInstall.bundledInstallerPath() : null; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -313,26 +315,16 @@ class _SetupWizardState extends State { Text( Platform.isMacOS ? '驱动已安装。请确认:① 打印机 USB 已连接并开机;② 系统设置 → 打印机与扫描仪 里出现 HiTi 打印机(没有的话点下方「+」添加,选 HiTi CS-200e);③ 回到这里重新检测。' - : '请先:① 用 USB 线连接打印机并开机,点「重新检测」——本应用通过 SDK 直连打印机,通常无需安装驱动;② 若反复检测不到打印机,再考虑打开 HiTi 官网安装 CS-2xx Windows 驱动(可选兜底)。', + : '请确认:① 打印机 USB 已连接并开机,点「重新检测」——本应用通过 SDK 直连 USB 打印机,无需安装任何驱动(已装 HiTi 驱动的机器请先卸载,驱动会占用 USB 接口导致无法直连);② 检测不到时换一个 USB 口试试。', style: Theme.of(context).textTheme.bodySmall?.copyWith(height: 1.6), ), const SizedBox(height: 8), - if (!Platform.isMacOS) - Align( - alignment: Alignment.centerLeft, - child: Text( - '驱动下载(可选兜底)', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: scheme.onSurfaceVariant, - ), - ), - ), Align( alignment: Alignment.centerLeft, child: TextButton.icon( onPressed: () => launchUrl(Uri.parse(_driverUrl)), icon: const Icon(Icons.open_in_new, size: 14), - label: Text(Platform.isMacOS ? '打开 HiTi 驱动下载页' : 'HiTi 驱动下载页(可选)'), + label: Text(Platform.isMacOS ? '打开 HiTi 驱动下载页' : 'HiTi 官网(无需驱动,仅供查阅)'), ), ), const SizedBox(height: 4), diff --git a/test/card_image_test.dart b/test/card_image_test.dart index 20b0e34..3aa4a82 100644 --- a/test/card_image_test.dart +++ b/test/card_image_test.dart @@ -79,8 +79,7 @@ void main() { // 纯 ASCII:UTF-8 解码即可正确还原(NUL 计入可打印率,两种编码都可能命中 UTF-8 路径) final ascii = Uint8List.fromList('HiTi CS-200e'.codeUnits); expect(PavoPrinter.decodeAuto(ascii).startsWith('HiTi CS-200e'), isTrue); - // 纯中文:UTF-8 解码得乱码(可打印率低),走 UTF-16 路径正确还原 - // (ASCII 混中文时 NUL 也计入可打印率,可能仍走 UTF-8 路径——与 C# 行为一致) + // 带中文:UTF-8 解码得乱码(可打印率低),走 UTF-16 路径正确还原 final name = '证卡打印机'; final bytes = []; for (final c in name.codeUnits) { @@ -89,6 +88,21 @@ void main() { expect(PavoPrinter.decodeAuto(Uint8List.fromList(bytes)), name); }); + test('decodeAuto 识别 UTF-16LE 设备路径(Windows 实测形态,整串不碎片化)', () { + // Win11 无驱动实测:枚举返回 UTF-16LE 的 \\?\usb#vid_xxxx&pid_xxxx#... 设备路径。 + // 奇数位字节全零特征必须命中 UTF-16 路径,否则整串会被 \0 拆成单字符碎片 + // (后续 API 拿 '\' 单字符调用全部 0x80)。 + const path = r'\\?\usb#vid_0d16&pid_0309#2ec511089913092#{28d78fad-5a12-11d1-ae5b-0000f803a8c2}'; + final bytes = []; + for (final c in path.codeUnits) { + bytes..add(c & 0xff)..add(c >> 8); + } + bytes..add(0)..add(0); // UTF-16 NUL 结尾 + final decoded = PavoPrinter.decodeAuto(Uint8List.fromList(bytes)); + expect(decoded, contains(r'\\?\usb#vid_0d16&pid_0309')); + expect(decoded.split('\x00').where((s) => s.isNotEmpty).first, path); + }); + test('statusTextOf 状态文案', () { expect(PavoPrinter.statusTextOf(0), '就绪'); expect(PavoPrinter.statusTextOf(PavoApi.dsOutOfRibbon), '色带已用尽,请更换色带');