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