From 097e46637227bbf660e88da597165ba4c3213808 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Mon, 15 Jun 2026 18:20:42 +0800 Subject: [PATCH] =?UTF-8?q?fix(client):=20=E6=A0=87=E7=AD=BE=E6=89=93?= =?UTF-8?q?=E5=8D=B0=202=C3=97=20=E8=B6=85=E9=87=87=E6=A0=B7=20+=20?= =?UTF-8?q?=E9=98=88=E5=80=BC=20160=EF=BC=8C=E6=B6=88=E9=99=A4=E7=83=AD?= =?UTF-8?q?=E6=95=8F=E5=AD=97=E4=BD=93=E6=A8=A1=E7=B3=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 5 ++++ client/lib/core/utils/print_util_stub.dart | 32 +++++++++++++++------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89e6f33..641a299 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.50] - 2026-06-15 + +### 改进 +- 标签打印字体更清晰:采用 2× 超采样渲染,消除热敏打印时反锯齿导致的模糊笔划 + ## [1.0.49] - 2026-06-15 ### 改进 diff --git a/client/lib/core/utils/print_util_stub.dart b/client/lib/core/utils/print_util_stub.dart index b41436e..cc309ed 100644 --- a/client/lib/core/utils/print_util_stub.dart +++ b/client/lib/core/utils/print_util_stub.dart @@ -189,7 +189,8 @@ double _fitFont(String s, double maxW, double maxH, bool bold, double cap) { return size; } -/// 用 dart:ui 把一张标签绘制成 320×160 位图(热敏标签实际尺寸 40×20mm @203dpi)。 +/// 用 dart:ui 把一张标签绘制成位图(热敏标签实际尺寸 40×20mm @203dpi)。 +/// 内部以 2× 超采样(640×320)绘制,转 TSPL 时再降采样,消除反锯齿模糊。 /// 供 TSPL 裸发和预览渲染共用同一套画布逻辑,确保预览即实际输出。 Future _renderLabelBitmap({ required String shop, @@ -199,10 +200,13 @@ Future _renderLabelBitmap({ required String date, required Uint8List qrBytes, }) async { - const w = 320, h = 160; // 40×20mm @203dpi + const w = 320, h = 160; // 40×20mm @203dpi(逻辑尺寸) + const scale = 2; // 2× 超采样,内部以 640×320 绘制 + const sw = w * scale, sh = h * scale; final recorder = ui.PictureRecorder(); final canvas = ui.Canvas( - recorder, ui.Rect.fromLTWH(0, 0, w.toDouble(), h.toDouble())); + recorder, ui.Rect.fromLTWH(0, 0, sw.toDouble(), sh.toDouble())); + canvas.scale(scale.toDouble(), scale.toDouble()); // 坐标系放大,后续坐标不变 canvas.drawRect(ui.Rect.fromLTWH(0, 0, w.toDouble(), h.toDouble()), ui.Paint()..color = const ui.Color(0xFFFFFFFF)); @@ -258,7 +262,7 @@ Future _renderLabelBitmap({ yy += r.$2 * kLineH + gap; } - return recorder.endRecording().toImage(w, h); + return recorder.endRecording().toImage(sw, sh); // 返回 2× 图(640×320) } /// 桌面端把「扁平标签」直接画成单色位图,TSPL BITMAP 裸发到热敏机。 @@ -279,11 +283,13 @@ Future _printFlatLabelThermal({ debugPrint('[label] thermal printer = $printer'); if (printer == null) return false; + // img 是 2× 超采样图(640×320);TSPL 输出仍是 320×160 const w = 320, h = 160; final img = await _renderLabelBitmap( shop: shop, name: name, code: code, degSpec: degSpec, date: date, qrBytes: qrBytes); final bd = await img.toByteData(format: ui.ImageByteFormat.rawRgba); final rgba = bd!.buffer.asUint8List(); + final sw = img.width; // 640(2×宽) // 打包 TSPL(SIZE 用整数,避免固件不认小数导致多走纸) const wbytes = (w + 7) >> 3; @@ -294,12 +300,18 @@ Future _printFlatLabelThermal({ for (int yy = 0; yy < h; yy++) { final row = Uint8List(wbytes)..fillRange(0, wbytes, 0xFF); // 默认白 bit=1 for (int xx = 0; xx < w; xx++) { - final i = (yy * w + xx) * 4; - final a = rgba[i + 3]; - final lum = a < 128 - ? 255.0 - : 0.299 * rgba[i] + 0.587 * rgba[i + 1] + 0.114 * rgba[i + 2]; - if (lum < 128) row[xx >> 3] &= ~(0x80 >> (xx & 7)); // 黑点 -> bit0 + // 2×2 四像素平均后阈值,消除反锯齿模糊(阈值 160 = 偏灰的边缘算黑) + double lum = 0; + for (int dy = 0; dy < 2; dy++) { + for (int dx = 0; dx < 2; dx++) { + final i = ((yy * 2 + dy) * sw + (xx * 2 + dx)) * 4; + final a = rgba[i + 3]; + lum += a < 128 + ? 255.0 + : 0.299 * rgba[i] + 0.587 * rgba[i + 1] + 0.114 * rgba[i + 2]; + } + } + if (lum / 4 < 160) row[xx >> 3] &= ~(0x80 >> (xx & 7)); // 黑点 -> bit0 } out.add(row); }