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
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
|