Compare commits

...

2 Commits

Author SHA1 Message Date
wangjia bf36f7aa3f fix(client): 标签字体细化:阈值 128,店名细体
Deploy / build-linux-web (push) Successful in 46s
Deploy / build-windows (push) Successful in 1m43s
Deploy / build-macos (push) Successful in 1m24s
Deploy / build-android (push) Successful in 59s
Deploy / build-ios (push) Successful in 7s
Deploy / release-deploy (push) Successful in 1m53s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 19:04:32 +08:00
wangjia 097e466372 fix(client): 标签打印 2× 超采样 + 阈值 160,消除热敏字体模糊
Deploy / build-linux-web (push) Successful in 51s
Deploy / build-windows (push) Successful in 1m46s
Deploy / build-macos (push) Successful in 1m30s
Deploy / build-android (push) Successful in 1m0s
Deploy / build-ios (push) Successful in 7s
Deploy / release-deploy (push) Successful in 1m54s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 18:20:42 +08:00
2 changed files with 34 additions and 12 deletions
+10
View File
@@ -5,6 +5,16 @@ 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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.51] - 2026-06-15
### 改进
- 标签打印字体细化:阈值调回 128,店名改细体,商品名保持粗体
## [1.0.50] - 2026-06-15
### 改进
- 标签打印字体更清晰:采用 2× 超采样渲染,消除热敏打印时反锯齿导致的模糊笔划
## [1.0.49] - 2026-06-15 ## [1.0.49] - 2026-06-15
### 改进 ### 改进
+24 -12
View File
@@ -189,7 +189,8 @@ double _fitFont(String s, double maxW, double maxH, bool bold, double cap) {
return size; return size;
} }
/// 用 dart:ui 把一张标签绘制成 320×160 位图(热敏标签实际尺寸 40×20mm @203dpi)。 /// 用 dart:ui 把一张标签绘制成位图(热敏标签实际尺寸 40×20mm @203dpi)。
/// 内部以 2× 超采样(640×320)绘制,转 TSPL 时再降采样,消除反锯齿模糊。
/// 供 TSPL 裸发和预览渲染共用同一套画布逻辑,确保预览即实际输出。 /// 供 TSPL 裸发和预览渲染共用同一套画布逻辑,确保预览即实际输出。
Future<ui.Image> _renderLabelBitmap({ Future<ui.Image> _renderLabelBitmap({
required String shop, required String shop,
@@ -199,10 +200,13 @@ Future<ui.Image> _renderLabelBitmap({
required String date, required String date,
required Uint8List qrBytes, required Uint8List qrBytes,
}) async { }) 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 recorder = ui.PictureRecorder();
final canvas = ui.Canvas( 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()), canvas.drawRect(ui.Rect.fromLTWH(0, 0, w.toDouble(), h.toDouble()),
ui.Paint()..color = const ui.Color(0xFFFFFFFF)); ui.Paint()..color = const ui.Color(0xFFFFFFFF));
@@ -236,8 +240,8 @@ Future<ui.Image> _renderLabelBitmap({
final codeText = code.isNotEmpty ? '编号:$code' : ''; final codeText = code.isNotEmpty ? '编号:$code' : '';
final rows = <(String, double, bool)>[ final rows = <(String, double, bool)>[
if (shop.isNotEmpty) (shop, kShopSize, true), if (shop.isNotEmpty) (shop, kShopSize, false), // 店名细体,突出商品名
if (name.isNotEmpty) (name, nameSize, true), if (name.isNotEmpty) (name, nameSize, true), // 商品名粗体
if (codeText.isNotEmpty) (codeText, kSmallSize, false), if (codeText.isNotEmpty) (codeText, kSmallSize, false),
if (degSpec.isNotEmpty) (degSpec, kSmallSize, false), if (degSpec.isNotEmpty) (degSpec, kSmallSize, false),
if (date.isNotEmpty) (date, kSmallSize, false), if (date.isNotEmpty) (date, kSmallSize, false),
@@ -258,7 +262,7 @@ Future<ui.Image> _renderLabelBitmap({
yy += r.$2 * kLineH + gap; yy += r.$2 * kLineH + gap;
} }
return recorder.endRecording().toImage(w, h); return recorder.endRecording().toImage(sw, sh); // 返回 2× 图(640×320
} }
/// 桌面端把「扁平标签」直接画成单色位图,TSPL BITMAP 裸发到热敏机。 /// 桌面端把「扁平标签」直接画成单色位图,TSPL BITMAP 裸发到热敏机。
@@ -279,11 +283,13 @@ Future<bool> _printFlatLabelThermal({
debugPrint('[label] thermal printer = $printer'); debugPrint('[label] thermal printer = $printer');
if (printer == null) return false; if (printer == null) return false;
// img 是 2× 超采样图(640×320);TSPL 输出仍是 320×160
const w = 320, h = 160; const w = 320, h = 160;
final img = await _renderLabelBitmap( final img = await _renderLabelBitmap(
shop: shop, name: name, code: code, degSpec: degSpec, date: date, qrBytes: qrBytes); shop: shop, name: name, code: code, degSpec: degSpec, date: date, qrBytes: qrBytes);
final bd = await img.toByteData(format: ui.ImageByteFormat.rawRgba); final bd = await img.toByteData(format: ui.ImageByteFormat.rawRgba);
final rgba = bd!.buffer.asUint8List(); final rgba = bd!.buffer.asUint8List();
final sw = img.width; // 6402×宽)
// 打包 TSPL(SIZE 用整数,避免固件不认小数导致多走纸) // 打包 TSPL(SIZE 用整数,避免固件不认小数导致多走纸)
const wbytes = (w + 7) >> 3; const wbytes = (w + 7) >> 3;
@@ -294,12 +300,18 @@ Future<bool> _printFlatLabelThermal({
for (int yy = 0; yy < h; yy++) { for (int yy = 0; yy < h; yy++) {
final row = Uint8List(wbytes)..fillRange(0, wbytes, 0xFF); // 默认白 bit=1 final row = Uint8List(wbytes)..fillRange(0, wbytes, 0xFF); // 默认白 bit=1
for (int xx = 0; xx < w; xx++) { for (int xx = 0; xx < w; xx++) {
final i = (yy * w + xx) * 4; // 2×2 四像素平均后阈值,消除反锯齿模糊(阈值 160 = 偏灰的边缘算黑)
final a = rgba[i + 3]; double lum = 0;
final lum = a < 128 for (int dy = 0; dy < 2; dy++) {
? 255.0 for (int dx = 0; dx < 2; dx++) {
: 0.299 * rgba[i] + 0.587 * rgba[i + 1] + 0.114 * rgba[i + 2]; final i = ((yy * 2 + dy) * sw + (xx * 2 + dx)) * 4;
if (lum < 128) row[xx >> 3] &= ~(0x80 >> (xx & 7)); // 黑点 -> bit0 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 < 128) row[xx >> 3] &= ~(0x80 >> (xx & 7)); // 黑点 -> bit0
} }
out.add(row); out.add(row);
} }