tscLabelBuilder.ts 3.57 KB
/**
 * TSC 标签指令构建(纯 TypeScript,无 require/import 外部 CJS 模块)
 * 使用 UTF-8 编码。对于纯英文/ASCII 内容,UTF-8 与 GB18030 字节一致,佳博打印机可正常打印。
 * 若需中文等非 ASCII,可考虑运行时动态加载 tsc.js(仅 APP 端)。
 */

/** 将字符串转为 UTF-8 字节数组(不依赖 TextEncoder) */
function stringToUtf8Bytes (str: string): number[] {
  const out: number[] = []
  for (let i = 0; i < str.length; i++) {
    let c = str.charCodeAt(i)
    if (c < 0x80) {
      out.push(c)
    } else if (c < 0x800) {
      out.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f))
    } else if (c < 0xd800 || c >= 0xe000) {
      out.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f))
    } else {
      i++
      const c2 = str.charCodeAt(i)
      const u = ((c & 0x3ff) << 10) + (c2 & 0x3ff) + 0x10000
      out.push(
        0xf0 | (u >> 18),
        0x80 | ((u >> 12) & 0x3f),
        0x80 | ((u >> 6) & 0x3f),
        0x80 | (u & 0x3f)
      )
    }
  }
  return out
}

function addCommandBytes (out: number[], str: string) {
  const bytes = stringToUtf8Bytes(str)
  for (let i = 0; i < bytes.length; i++) out.push(bytes[i])
}

function escapeTscString (s: string): string {
  return String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"')
}

/**
 * 构建标签打印指令字节数组(TSC)
 * 与官方 Demo createLabel 格式一致(SIZE 100x30, GAP 0)
 */
export function buildTscLabel (options: {
  productName: string
  labelId: string
  printQty?: number
  widthMm?: number
  heightMm?: number
  category?: string
  extraLine?: string
}): number[] {
  const {
    productName,
    labelId,
    printQty = 1,
    widthMm = 100,
    heightMm = 30,
    category = '',
    extraLine = '',
  } = options

  const out: number[] = []
  const add = (s: string) => addCommandBytes(out, s + '\r\n')
  let y = 10

  add(`SIZE ${widthMm} mm,${heightMm} mm`)
  add('GAP 0 mm,0 mm')
  add('CLS')
  add(`TEXT 50,${y},"TSS24.BF2",0,1,1,"${escapeTscString(productName)}"`)
  y += 35
  if (category) {
    add(`TEXT 50,${y},"TSS24.BF2",0,1,1,"Category: ${escapeTscString(category)}"`)
    y += 35
  }
  add(`TEXT 50,${y},"TSS24.BF2",0,1,1,"ID: ${escapeTscString(labelId)}"`)
  y += 35
  if (extraLine) {
    add(`TEXT 50,${y},"TSS24.BF2",0,1,1,"${escapeTscString(extraLine)}"`)
    y += 35
  }
  add(`QRCODE 50,${y},L,5,A,0,"${escapeTscString(labelId)}"`)
  add(`PRINT 1,${printQty}`)
  add('FEED 1')

  return out
}

/**
 * 食品营养成分标签测试打印(Nutrition Facts 格式)
 */
export function buildTestTscLabel (): number[] {
  const out: number[] = []
  const add = (s: string) => addCommandBytes(out, s + '\r\n')
  let y = 10

  add('SIZE 100 mm,70 mm')
  add('GAP 0 mm,0 mm')
  add('CLS')

  add(`TEXT 50,${y},"TSS24.BF2",0,1,1,"NUTRITION FACTS"`)
  y += 28
  add(`TEXT 50,${y},"TSS24.BF2",0,1,1,"Grilled Chicken Breast"`)
  y += 28
  add(`TEXT 50,${y},"TSS24.BF2",0,1,1,"Serving Size 1 piece (100g)"`)
  y += 32
  add(`TEXT 50,${y},"TSS24.BF2",0,1,1,"Calories 165"`)
  y += 28
  add(`TEXT 50,${y},"TSS24.BF2",0,1,1,"Total Fat 4g"`)
  y += 24
  add(`TEXT 50,${y},"TSS24.BF2",0,1,1,"Saturated Fat 1g"`)
  y += 24
  add(`TEXT 50,${y},"TSS24.BF2",0,1,1,"Cholesterol 85mg"`)
  y += 24
  add(`TEXT 50,${y},"TSS24.BF2",0,1,1,"Sodium 75mg"`)
  y += 24
  add(`TEXT 50,${y},"TSS24.BF2",0,1,1,"Total Carb 0g"`)
  y += 24
  add(`TEXT 50,${y},"TSS24.BF2",0,1,1,"Protein 31g"`)
  y += 28
  add(`TEXT 50,${y},"TSS24.BF2",0,1,1,"Exp: 01/17/25  ID: TEST-251227"`)
  y += 35
  add(`QRCODE 50,${y},L,4,A,0,"TEST-251227-001"`)
  add('PRINT 1,1')
  add('FEED 1')

  return out
}