templatePhysicalMm.ts
6.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/**
* 与 NativeTemplateCommandBuilder.toMillimeter 一致(px 按 96dpi 转 mm),
* 用于判断模板是否适合走 native-fast-printer 的 TSC 模板指令(常见标签幅宽约 4 英寸级)。
*/
import type { SystemLabelTemplate, SystemTemplateElementBase } from './types/printer'
const DESIGN_DPI = 96
const PX_PER_INCH = 96
const PX_PER_CM = 37.8
function toCanvasPx (value: number, unit: string): number {
const u = String(unit || 'inch').toLowerCase()
if (u === 'mm') return (value / 25.4) * PX_PER_INCH
if (u === 'cm') return value * PX_PER_CM
if (u === 'px') return value
return value * PX_PER_INCH
}
function fromCanvasPx (px: number, unit: string): number {
const u = String(unit || 'inch').toLowerCase()
if (u === 'mm') return (px * 25.4) / PX_PER_INCH
if (u === 'cm') return px / PX_PER_CM
if (u === 'px') return px
return px / PX_PER_INCH
}
function roundTemplateDim (value: number, unit: string): number {
const u = String(unit || 'inch').toLowerCase()
if (u === 'px') return Math.max(1, Math.round(value))
if (u === 'mm' || u === 'cm') return Math.round(value * 10) / 10
return Math.round(value * 1000) / 1000
}
function elementBottomPx (el: SystemTemplateElementBase): number {
const y = Number(el.y) || 0
const h = Math.max(0, Number(el.height) || 0)
let bottom = y + h
const type = String(el.type || '').toUpperCase()
const cfg = (el.config || {}) as Record<string, unknown>
if (type === 'BARCODE') {
const showText = String(cfg.showText ?? cfg.ShowText ?? 'true').toLowerCase() !== 'false'
if (showText) bottom += 28
else bottom += 4
}
if (type === 'QRCODE') bottom += 4
return bottom
}
/**
* 原生 printTemplate 的 SIZE 高度仅取自模板根 height,不会按元素自动增高;
* 条码/日期贴在底部时易被裁掉。与 systemTemplateAdapter.buildTscTemplate 增高逻辑对齐。
*/
/** 设计 px:元素最底边 + 留白,且不超过模板根 height(用于光栅出纸高度,避免整纸 5cm 空白) */
export function templateContentHeightPx (
template: Pick<SystemLabelTemplate, 'unit' | 'width' | 'height' | 'elements'>,
extraBottomPx = 14,
): number {
const unit = String(template.unit || 'inch')
const canvasH = Math.max(40, Math.round(toCanvasPx(Number(template.height) || 0, unit)))
const elements = template.elements || []
if (!elements.length) return canvasH
let maxBottom = 0
for (const el of elements) {
maxBottom = Math.max(maxBottom, elementBottomPx(el))
}
return Math.max(40, Math.min(canvasH, maxBottom + extraBottomPx))
}
export function ensureTemplateHeightCoversElements (
template: SystemLabelTemplate,
extraBottomPx = 14
): SystemLabelTemplate {
const unit = String(template.unit || 'inch')
const elements = template.elements || []
if (!elements.length) return template
let maxBottom = 0
for (const el of elements) {
maxBottom = Math.max(maxBottom, elementBottomPx(el))
}
const currentHpx = toCanvasPx(Number(template.height) || 0, unit)
const neededPx = maxBottom + extraBottomPx
if (neededPx <= currentHpx + 1) return template
return {
...template,
height: roundTemplateDim(fromCanvasPx(neededPx, unit), unit),
}
}
/** 常见 4″ 标签机安全上限(mm),略放宽 */
const NATIVE_FAST_MAX_WIDTH_MM = 112
const NATIVE_FAST_MAX_HEIGHT_MM = 320
/** 打印/预览统一:模板根 width/height + unit → 物理毫米(与 NativeTemplateCommandBuilder.toMillimeter 一致) */
export function getTemplatePhysicalSizeMm (
template: Pick<SystemLabelTemplate, 'unit' | 'width' | 'height'>
): { widthMm: number; heightMm: number; unit: string } {
const unit = String(template.unit || 'inch')
const w = Number(template.width) || 0
const h = Number(template.height) || 0
const { widthMm, heightMm } = templateSizeToMillimeters(unit, w, h)
return { widthMm, heightMm, unit }
}
export function templateSizeToMillimeters (
unit: string | undefined,
width: number,
height: number
): { widthMm: number; heightMm: number } {
const u = String(unit || 'inch').toLowerCase()
let widthMm = 0
let heightMm = 0
if (u === 'mm') {
widthMm = width
heightMm = height
} else if (u === 'cm') {
widthMm = width * 10
heightMm = height * 10
} else if (u === 'px') {
widthMm = (width / DESIGN_DPI) * 25.4
heightMm = (height / DESIGN_DPI) * 25.4
} else {
widthMm = width * 25.4
heightMm = height * 25.4
}
return { widthMm, heightMm }
}
/** 设计画布像素(96dpi 坐标系)→ 物理毫米,与 Web unitToPx / App toCanvasPx 一致 */
export function designCanvasPxToMillimeters (
cw: number,
ch: number,
): { widthMm: number; heightMm: number } {
return {
widthMm: Math.round(((cw / DESIGN_DPI) * 25.4) * 10) / 10,
heightMm: Math.round(((ch / DESIGN_DPI) * 25.4) * 10) / 10,
}
}
/** 光栅出纸像素(printDpi)→ 物理毫米,SIZE 与位图一致,各机型比例统一 */
export function rasterDotsToMillimeters (
widthDots: number,
heightDots: number,
printDpi: number,
): { widthMm: number; heightMm: number } {
const dpi = Math.max(1, printDpi)
return {
widthMm: Math.round(((widthDots / dpi) * 25.4) * 10) / 10,
heightMm: Math.round(((heightDots / dpi) * 25.4) * 10) / 10,
}
}
/**
* 预览 / 打印共用设计画布尺寸(与后台 LabelCanvas unitToPx 同一坐标系)。
* 默认用模板根 height,与后台预览一致;不再对 ESC 单独裁 content 高度。
*/
export function resolveLabelDesignCanvasPx (
template: Pick<SystemLabelTemplate, 'unit' | 'width' | 'height'>,
): { cw: number; ch: number } {
const unit = String(template.unit || 'inch')
const cw = Math.max(40, Math.round(toCanvasPx(Number(template.width) || 2, unit)))
const ch = Math.max(40, Math.round(toCanvasPx(Number(template.height) || 2, unit)))
return { cw, ch }
}
export function isTemplateWithinNativeFastPrintBounds (
template: Pick<SystemLabelTemplate, 'unit' | 'width' | 'height'>
): boolean {
const w = Number(template.width) || 0
const h = Number(template.height) || 0
if (w <= 0 || h <= 0) return false
const { widthMm, heightMm } = templateSizeToMillimeters(template.unit, w, h)
return widthMm <= NATIVE_FAST_MAX_WIDTH_MM && heightMm <= NATIVE_FAST_MAX_HEIGHT_MM
}