/** * Android NativeTemplateCommandBuilder 仅处理:TEXT_*、QRCODE、BARCODE、IMAGE、 * 以及 border=line 的 BLANK;其余类型在原生路径下会被静默跳过(与画布预览不一致)。 */ import type { LabelTemplateData, SystemLabelTemplate, SystemTemplateElementBase, } from './types/printer' import { formatBarcodeValueForTsc, normalizeBarcodeType } from '../barcodeFormat' import { applyTemplateData } from './templateRenderer' import { resolveElementDateTimeDisplay } from '../labelPreview/printInputOffset' function isElementHandledByNativeFastPrinter (el: SystemTemplateElementBase): boolean { const type = String(el.type || '').toUpperCase() if (type.startsWith('TEXT_')) return true if (type === 'NUTRITION') return true if (type === 'QRCODE' || type === 'IMAGE') return true if (type === 'BARCODE') return true if (type === 'BLANK') return true return false } /** 原生营养表:严格使用模板 height,打印时再按 dpi 缩放;勿在 JS 侧抬高 height 以免压住下方 DATE */ function prepareNutritionElementForNativePrint (el: SystemTemplateElementBase): SystemTemplateElementBase { const cfg = { ...(el.config || {}) } as Record const x = Math.max(0, Number(el.x) || 0) const y = Math.max(0, Number(el.y) || 0) const w = Math.max(40, Number(el.width) || 0) const h = Math.max(40, Number(el.height) || 72) cfg.nativePrintHeight = h cfg.NativePrintHeight = h cfg.nativePadLeft = Number(cfg.nativePadLeft ?? 2) cfg.nativePadRight = Number(cfg.nativePadRight ?? 4) cfg.nutritionTitleBold = false cfg.nutritionBodyBold = false return { ...el, x, y, width: w, height: h, config: cfg, } } /** 原生打印按 dpi 放大营养表后,保证 DATE/TIME/BARCODE 在营养表底边之下(与预览留白一致) */ function resolveNativeLayoutCollisions ( elements: SystemTemplateElementBase[], ): SystemTemplateElementBase[] { const nutrition = elements.find((el) => String(el.type || '').toUpperCase() === 'NUTRITION') if (!nutrition) return elements const nutY = Number(nutrition.y) || 0 const nutH = Number(nutrition.height) || 0 /** 设计 px 最小间距;预览里常见 8–12px */ const minGapPx = 10 const reservedBottom = nutY + nutH + minGapPx return elements.map((el) => { if (el.id === nutrition.id) return el const y = Number(el.y) || 0 if (y < reservedBottom && y >= nutY - 1) { return { ...el, y: reservedBottom } } return el }) } function prepareBarcodeElementForNativePrint (el: SystemTemplateElementBase): SystemTemplateElementBase { const cfg = { ...(el.config || {}) } as Record const barcodeType = normalizeBarcodeType(cfg.barcodeType ?? cfg.BarcodeType) const raw = String( cfg.data ?? cfg.Data ?? cfg.value ?? cfg.Value ?? cfg.barcodeData ?? cfg.BarcodeData ?? '' ).trim() const data = formatBarcodeValueForTsc(raw, barcodeType) /** 人读数字与预览一致(1234),编码串(A1234B)仅给原生画条用 */ cfg.barcodeDisplayText = raw cfg.BarcodeDisplayText = raw if (data) { cfg.data = data cfg.Data = data cfg.value = data cfg.Value = data } /** CODABAR 在 Virtual BT / 佳博上 TSC BARCODE 易失败,改走与预览一致的位图条 */ if (barcodeType === 'CODABAR') { cfg.nativeBarcodeBitmap = true cfg.NativeBarcodeBitmap = true } return { ...el, config: cfg } } /** * 将 WEIGHT / DATE / TIME / DURATION 转为 TEXT_STATIC(展示文案与合并后的 config.text 一致), * LOGO → IMAGE,使同一套模板可走 native printTemplate,避免仅因元素类型名而整页光栅(进度长期停在 ~12–14%)。 */ export function normalizeTemplateForNativeFastJob ( template: SystemLabelTemplate, data: LabelTemplateData ): SystemLabelTemplate { const now = new Date() const extras: SystemTemplateElementBase[] = [] const elements = (template.elements || []).map((el) => { const type = String(el.type || '').toUpperCase() const config = (el.config || {}) as Record if (type === 'LOGO') { return { ...el, type: 'IMAGE' as typeof el.type } } if (type === 'WEIGHT' || type === 'DATE' || type === 'TIME' || type === 'DURATION') { let text = '' if (type === 'DATE' || type === 'TIME' || type === 'DURATION') { const live = resolveElementDateTimeDisplay(el, now) if (live != null && live.trim()) { text = applyTemplateData(live, data) } } else if (type === 'WEIGHT') { let raw = String(config.text ?? config.Text ?? '').trim() if (!raw) raw = String(config.value ?? config.Value ?? '').trim() if (raw) text = applyTemplateData(raw, data) } if (!text && type === 'WEIGHT') { const v = String(config.value ?? config.Value ?? '') const u = String(config.unit ?? config.Unit ?? '') if (v && u && !v.endsWith(u)) text = `${v}${u}` else text = v || u } return { ...el, type: 'TEXT_STATIC' as typeof el.type, config: { ...config, text, nativeSourceType: type }, } } if (type === 'NUTRITION') { return prepareNutritionElementForNativePrint(el) } if (type === 'BARCODE') { const prepared = prepareBarcodeElementForNativePrint(el) const pcfg = { ...(prepared.config || {}) } as Record const human = String(pcfg.barcodeDisplayText ?? pcfg.BarcodeDisplayText ?? '').trim() const showHuman = String(pcfg.showText ?? pcfg.ShowText ?? 'true').toLowerCase() !== 'false' if (human && showHuman) { pcfg.showText = false pcfg.ShowText = false extras.push({ id: `${String(prepared.id || 'barcode')}_label`, type: 'TEXT_STATIC', x: Number(prepared.x) || 0, y: (Number(prepared.y) || 0) + (Number(prepared.height) || 40) + 6, width: Number(prepared.width) || 140, height: 20, rotation: prepared.rotation ?? 'horizontal', border: 'none', config: { text: human, fontSize: 12, textAlign: 'center', TextAlign: 'center', forceRasterText: true, }, } as SystemTemplateElementBase) } return { ...prepared, config: pcfg } } return el }) const merged = resolveNativeLayoutCollisions([...elements, ...extras]) return { ...template, elements: merged } } /** 存在任一原生不支持的元素时,预览打印应走光栅,避免「成功但缺内容/不出纸」与画布不一致 */ export function templateHasUnsupportedNativeFastElements (template: SystemLabelTemplate): boolean { for (const el of template.elements || []) { if (!isElementHandledByNativeFastPrinter(el)) return true } return false }