@@ -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 < ui . Image > _renderLabelBitmap ( {
required String shop ,
@@ -199,10 +200,13 @@ Future<ui.Image> _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 ) ) ;
@@ -236,8 +240,8 @@ Future<ui.Image> _renderLabelBitmap({
final codeText = code . isNotEmpty ? ' 编号: $ code ' : ' ' ;
final rows = < ( String , double , bool ) > [
if ( shop . isNotEmpty ) ( shop , kShopSize , true ) ,
if ( name . isNotEmpty ) ( name , nameSize , true ) ,
if ( shop . isNotEmpty ) ( shop , kShopSize , false ) , // 店名细体,突出商品名
if ( name . isNotEmpty ) ( name , nameSize , true ) , // 商品名粗体
if ( codeText . isNotEmpty ) ( codeText , kSmallSize , false ) ,
if ( degSpec . isNotEmpty ) ( degSpec , kSmallSize , false ) ,
if ( date . isNotEmpty ) ( date , kSmallSize , false ) ,
@@ -258,7 +262,7 @@ Future<ui.Image> _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<bool> _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<bool> _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 < 128 ) row [ xx > > 3 ] & = ~ ( 0x80 > > ( xx & 7 ) ) ; // 黑点 -> bit0
}
out . add ( row ) ;
}