templatePhysicalMm.ts 4.81 KB
/**
 * 与 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 }
}

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
}