/** * 与 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 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, 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 ): { 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 } } export function isTemplateWithinNativeFastPrintBounds ( template: Pick ): 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 }