You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

510 lines
15 KiB

import { CoordinateTransformer } from './coordinateTransform.js'
/**
* ZPL代码生成器
* 统一处理不同打印方向的ZPL代码生成
*/
export class ZPLGenerator {
constructor(orientation = 'portrait', paperType = '60x40', customSize = null, paperId = null, dpi = 300) {
this.orientation = orientation
this.paperType = paperType
this.customSize = customSize
this.paperId = paperId // 新增纸张ID支持
this.dpi = dpi // 新增DPI支持
this.transformer = new CoordinateTransformer(orientation, paperType, customSize, paperId)
this.config = this.getConfig()
}
/**
* 更新纸张类型
*/
updatePaperType(paperType, customSize = null, paperId = null) {
this.paperType = paperType
this.customSize = customSize
this.paperId = paperId
this.transformer.updatePaperType(paperType, customSize, paperId)
}
/**
* 更新纸张ID(新增方法)
*/
updatePaperId(paperId) {
this.paperId = paperId
this.transformer.updatePaperId(paperId)
}
/**
* 更新DPI(新增方法)
*/
updateDPI(dpi) {
this.dpi = dpi
}
/**
* 获取配置参数
*/
getConfig() {
const configs = {
portrait: {
fieldOrientation: '^FWN', // 正常方向
barcodeOrientation: 'N', // 正常条码
qrcodeOrientation: 'N' // 正常二维码
},
landscape: {
fieldOrientation: '^FWB', // 旋转90度
barcodeOrientation: 'B', // 旋转90度条码
qrcodeOrientation: 'B' // 旋转90度二维码
}
}
return configs[this.orientation] || configs.portrait
}
/**
* 生成完整的ZPL代码
* @param {Array} elements - 设计元素数组
* @returns {string} ZPL代码
*/
generate(elements) {
if (!elements || elements.length === 0) {
return ''
}
const zpl = ['^XA'] // ZPL开始标记
zpl.push(this.config.fieldOrientation) // 设置打印方向
// 处理每个元素
elements.forEach(element => {
const elementZPL = this.generateElementZPL(element)
if (elementZPL) {
zpl.push(elementZPL)
}
})
zpl.push('^XZ') // ZPL结束标记
return zpl.join('\n')
}
/**
* 生成单个元素的ZPL代码
* @param {Object} element - 设计元素
* @returns {string} 元素的ZPL代码
*/
generateElementZPL(element) {
const { x, y } = this.transformer.toZPL(element.x, element.y)
switch (element.type) {
case 'text':
return this.generateTextZPL(element, x, y)
case 'onecode':
return this.generateOnecodeZPL(element, x, y)
case 'qrcode':
return this.generateQRCodeZPL(element, x, y)
case 'pic':
return this.generateImageZPL(element, x, y)
case 'hLine':
return this.generateHorizontalLineZPL(element, x, y)
case 'vLine':
return this.generateVerticalLineZPL(element, x, y)
case 'serialNumber':
return this.generateSerialNumberZPL(element, x, y)
default:
return ''
}
}
/**
* 生成文本ZPL代码
*/
generateTextZPL(element, x, y) {
const zpl = []
// 生成字体命令
const fontCommand = this.generateFontCommand(element)
zpl.push(fontCommand)
if (element.isChecked) {
zpl.push(`^FO${x-60},${y}^GB45,45,2,B,0^FS ^FO${x-50},${y+10}^AJN,35,35^FD√^FS `)
}
const lineWidth = Math.max(1, Math.round((element.lineWidth || 200) * this.dpi / 25.4))
// 处理文本对齐
const alignmentParam = this.getTextAlignmentParam(element.textAlign)
// 基础文本
const textCommand = element.newline
? `^FO${x},${y}^FB${lineWidth},${element.lineRows},0,${alignmentParam}^CFJ,${element.fontSize}^FD${element.data}^FS`
: `^FO${x},${y}^FD${element.data}^FS`
zpl.push(textCommand)
// 加粗效果(通过偏移重复打印实现)
if (element.bold) {
const boldCommands = element.newline ? [
`^FO${x + 1},${y}^FB${lineWidth},${element.lineRows},0,${alignmentParam}^CFJ,${element.fontSize}^FD${element.data}^FS`,
`^FO${x},${y + 1}^FB${lineWidth},${element.lineRows},0,${alignmentParam}^CFJ,${element.fontSize}^FD${element.data}^FS`,
`^FO${x + 1},${y + 1}^FB${lineWidth},${element.lineRows},0,${alignmentParam}^CFJ,${element.fontSize}^FD${element.data}^FS`
] : [
`^FO${x + 1},${y}^FD${element.data}^FS`,
`^FO${x},${y + 1}^FD${element.data}^FS`,
`^FO${x + 1},${y + 1}^FD${element.data}^FS`
]
zpl.push(...boldCommands)
}
// 下划线效果(通过线条实现)
if (element.fontUnderline) {
const textWidth = this.estimateTextWidth(element.data, element.fontSize)
const underlineY = y + element.fontSize + 2
zpl.push(`^FO${x},${underlineY}^GB${textWidth},2,2,B^FS`)
}
return zpl.join('\n')
}
/**
* 生成字体命令
*/
generateFontCommand(element) {
let fontCmd = '^CI28' // 设置字符编码
// 根据字体族选择字体文件
const fontFamily = element.fontFamily
if (fontFamily && fontFamily !== 'default') {
const fontFile = this.mapFontFamilyToFile(fontFamily)
if (fontFile) {
fontCmd += ` ^CWJ,E:${fontFile}`
console.log(`ZPL字体映射: ${fontFamily} -> ${fontFile}`)
} else {
fontCmd += ' ^CWJ,E:MSYH.TTF' // 默认字体
console.warn(`未找到字体映射: ${fontFamily},使用默认字体`)
}
} else {
fontCmd += ' ^CWJ,E:MSYH.TTF' // 默认字体
console.log('使用默认字体: MSYH.TTF')
}
// 设置字体大小
const fontSize = element.fontSize || 30
fontCmd += ` ^CFJ,${fontSize}`
// 添加调试信息
console.log(`生成字体命令: ${fontCmd}`)
console.log(`字体族: ${fontFamily}, 字体大小: ${fontSize}`)
return fontCmd
}
/**
* 映射字体族到字体文件
*/
mapFontFamilyToFile(fontFamily) {
const fontMapping = {
// 中文字体
'Microsoft YaHei': 'MSYH.TTF',
'微软雅黑': 'MSYH.TTF',
'SimSun': 'SIMSUN.TTC',
'宋体': 'SIMSUN.TTC',
'SimHei': 'SIMHEI.TTF',
'黑体': 'SIMHEI.TTF',
'KaiTi': 'KAITI.TTF',
'楷体': 'KAITI.TTF',
'FangSong': 'SIMFANG.TTF',
'仿宋': 'SIMFANG.TTF',
'Microsoft JhengHei': 'MSJH.TTF',
'微軟正黑體': 'MSJH.TTF',
'PMingLiU': 'PMINGLIU.TTF',
'新細明體': 'PMINGLIU.TTF',
'DFKai-SB': 'DFKAI.TTF',
'標楷體': 'DFKAI.TTF',
// 英文字体
'Arial': 'ARIAL.TTF',
'Times New Roman': 'TIMES.TTF',
'Courier New': 'COUR.TTF',
'Helvetica': 'HELV.TTF',
'Verdana': 'VERDANA.TTF',
'Georgia': 'GEORGIA.TTF',
'Tahoma': 'TAHOMA.TTF',
'Trebuchet MS': 'TREBUC.TTF',
'Lucida Console': 'LUCON.TTF',
'Impact': 'IMPACT.TTF',
'Comic Sans MS': 'COMIC.TTF',
'Palatino': 'PALA.TTF',
'Garamond': 'GARA.TTF',
'Bookman': 'BOOKOS.TTF',
'Century Gothic': 'CENTURY.TTF',
'Franklin Gothic': 'FRANKLIN.TTF',
'Gill Sans': 'GILLSANS.TTF',
'Optima': 'OPTIMA.TTF',
'Futura': 'FUTURA.TTF',
'Bodoni': 'BODONI.TTF',
'Didot': 'DIDOT.TTF',
// 等宽字体
'Consolas': 'CONSOLAS.TTF',
'Monaco': 'MONACO.TTF',
'Menlo': 'MENLO.TTF',
'Source Code Pro': 'SOURCECODE.TTF',
'Fira Code': 'FIRACODE.TTF',
'JetBrains Mono': 'JETBRAINS.TTF',
// 装饰字体
'Brush Script MT': 'BRUSHSCRIPT.TTF',
'Papyrus': 'PAPYRUS.TTF',
'Chalkboard': 'CHALKBOARD.TTF',
'Marker Felt': 'MARKERFELT.TTF',
'Zapfino': 'ZAPFINO.TTF',
'Snell Roundhand': 'SNELLROUND.TTF',
// 默认字体
'default': 'MSYH.TTF',
'Default': 'MSYH.TTF'
}
// 如果找不到精确匹配,尝试模糊匹配
if (!fontMapping[fontFamily]) {
const lowerFontFamily = fontFamily.toLowerCase()
for (const [key, value] of Object.entries(fontMapping)) {
if (key.toLowerCase().includes(lowerFontFamily) || lowerFontFamily.includes(key.toLowerCase())) {
return value
}
}
}
return fontMapping[fontFamily] || 'MSYH.TTF' // 默认返回微软雅黑
}
/**
* 获取文本对齐参数
*/
getTextAlignmentParam(textAlign) {
switch (textAlign) {
case 'center': return 'C'
case 'right': return 'R'
case 'left':
default: return 'L'
}
}
/**
* 估算文本宽度(用于下划线)
*/
estimateTextWidth(text, fontSize) {
if (!text) return 0
// 简单估算:中文字符按字体大小计算,英文字符按字体大小的0.6倍计算
let chineseCount = 0
let englishCount = 0
for (let i = 0; i < text.length; i++) {
const char = text.charAt(i)
if (char.match(/[\u4e00-\u9fff]/)) {
chineseCount++
} else {
englishCount++
}
}
return Math.round(chineseCount * fontSize + englishCount * fontSize * 0.6)
}
/**
* 生成一维码ZPL代码
*/
generateOnecodeZPL(element, x, y) {
const orientation = this.config.barcodeOrientation
const barcodeType = element.barcodeType || 'CODE128'
const showContent = element.showContent !== false // 默认显示内容
// 将毫米转换为ZPL单位
// 宽度:毫米转换为ZPL宽度倍数 (范围1-10)
const width = Math.min(10, Math.round((element.width || 0.33) * this.dpi / 25.4)) // 0.33mm≈13mil 常用
// 高度:毫米转换为点数,使用精确的DPI转换公式
const height = Math.max(1, Math.round((element.height || 15) * this.dpi / 25.4))
// 根据条码类型选择ZPL指令
let zplCommand = ''
switch (barcodeType) {
case 'CODE128':
zplCommand = `^BC${orientation}`
break
case 'CODE39':
zplCommand = `^B3${orientation}`
break
case 'CODE93':
zplCommand = `^BA${orientation}`
break
case 'EAN13':
zplCommand = `^BE${orientation}`
break
case 'EAN8':
zplCommand = `^B8${orientation}`
break
case 'UPCA':
zplCommand = `^BU${orientation}`
break
case 'UPCE':
zplCommand = `^B9${orientation}`
break
default:
zplCommand = `^BC${orientation}` // 默认使用CODE128
}
// 构建完整的ZPL指令
const contentParam = showContent ? 'Y' : 'N'
const additionalParams = orientation === 'B' ? `,${contentParam},N,N` : `,${contentParam}`
return `^FO${x},${y}^BY${width}${zplCommand},${height}${additionalParams}^FD${element.data}^FS`
}
/**
* 生成二维码ZPL代码
*/
generateQRCodeZPL(element, x, y) {
const orientation = this.config.qrcodeOrientation
const data = element.data || ''
// 将毫米转换为ZPL尺寸单位,使用精确的DPI转换公式
// 二维码的尺寸参数是模块大小,通常1-10
const sizeInDots = Math.max(1, Math.round((element.height || 10) * this.dpi / 25.4))
// 将点数转换为ZPL模块大小 (大约每20-30个点为1个模块)
let size = Math.max(1, Math.min(10, Math.round(sizeInDots / 25)))
// 根据数据长度自动调整最小尺寸,确保能容纳数据
if (data.length > 300) {
size = Math.max(size, 8) // 超长数据需要更大的尺寸
} else if (data.length > 200) {
size = Math.max(size, 6) // 长数据需要更大的尺寸
} else if (data.length > 100) {
size = Math.max(size, 4) // 中等数据需要中等尺寸
} else {
size = Math.max(size, 2) // 短数据使用较小尺寸
}
// 尝试使用更明确的二维码格式
// 对于长数字内容,使用数字模式可能更有效
if (/^\d+$/.test(data)) {
// 纯数字内容,使用数字模式
return `^FO${x},${y}^BQ${orientation},2,${size}^FDMN,${data}^FS`
} else {
// 混合内容,使用自动模式
return `^FO${x},${y}^BQ${orientation},2,${size}^FDMA,${data}^FS`
}
}
/**
* 生成图片ZPL代码
*/
generateImageZPL(element, x, y) {
if (!element.data) return ''
return `^FO${x},${y}^GFA,${element.data}`
}
/**
* 生成横线ZPL代码
*/
generateHorizontalLineZPL(element, x, y) {
if (this.orientation === 'landscape') {
// 横向打印时需要特殊处理
const adjustedY = Math.round(1200 - element.x) - element.width
return `^FO${x},${adjustedY}^FWR^GB${element.height},${element.width},3,B^FS`
} else {
return `^FO${x},${y}^GB${element.width},${element.height},3,B^FS`
}
}
/**
* 生成竖线ZPL代码
*/
generateVerticalLineZPL(element, x, y) {
if (this.orientation === 'landscape') {
return `^FO${x},${y}^FWR^GB${element.height},${element.width},3,B^FS`
} else {
return `^FO${x},${y}^GB1,${element.height},${element.width},B^FS`
}
}
/**
* 生成流水号ZPL代码
*/
generateSerialNumberZPL(element, x, y) {
const zpl = []
// 生成字体命令
const fontCommand = this.generateFontCommand(element)
zpl.push(fontCommand)
const bold = element.bold || false
const base = `^FO${x},${y}^FD${element.data}^FS`
zpl.push(base)
// 可选:加粗效果(重复打印)
if (bold) {
zpl.push(`^FO${x + 1},${y}^FD${element.data}^FS`)
zpl.push(`^FO${x},${y + 1}^FD${element.data}^FS`)
zpl.push(`^FO${x + 1},${y + 1}^FD${element.data}^FS`)
}
return zpl.join('\n')
}
}
/**
* 创建ZPL生成器实例
* @param {string} orientation - 打印方向
* @param {string} paperType - 纸张类型(兼容性)
* @param {Object} customSize - 自定义尺寸(兼容性)
* @param {number} paperId - 纸张ID(新增)
* @param {number} dpi - DPI(新增)
* @returns {ZPLGenerator} 生成器实例
*/
export function createZPLGenerator(orientation, paperType = null, customSize = null, paperId = null, dpi = 300) {
return new ZPLGenerator(orientation, paperType, customSize, paperId, dpi)
}
/**
* 根据纸张ID创建ZPL生成器实例(新增方法)
* @param {number} paperId - 纸张ID
* @param {string} orientation - 打印方向
* @param {number} dpi - DPI(新增)
* @returns {ZPLGenerator} 生成器实例
*/
export function createZPLGeneratorById(paperId, orientation = 'portrait', dpi = 300) {
return new ZPLGenerator(orientation, null, null, paperId, dpi)
}
/**
* 快速生成ZPL代码
* @param {Array} elements - 设计元素
* @param {string} orientation - 打印方向
* @param {string} paperType - 纸张类型(兼容性)
* @param {number} paperId - 纸张ID(新增)
* @param {number} dpi - DPI(新增)
* @returns {string} ZPL代码
*/
export function generateZPL(elements, orientation = 'portrait', paperType = null, paperId = null, dpi = 300) {
const generator = new ZPLGenerator(orientation, paperType, null, paperId, dpi)
return generator.generate(elements)
}
/**
* 根据纸张ID快速生成ZPL代码(新增方法)
* @param {Array} elements - 设计元素
* @param {number} paperId - 纸张ID
* @param {string} orientation - 打印方向
* @param {number} dpi - DPI(新增)
* @returns {string} ZPL代码
*/
export function generateZPLById(elements, paperId, orientation = 'portrait', dpi = 300) {
const generator = new ZPLGenerator(orientation, null, null, paperId, dpi)
return generator.generate(elements)
}
export default ZPLGenerator