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