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

7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
  1. import { CoordinateTransformer } from './coordinateTransform.js'
  2. /**
  3. * ZPL代码生成器
  4. * 统一处理不同打印方向的ZPL代码生成
  5. */
  6. export class ZPLGenerator {
  7. constructor(orientation = 'portrait', paperType = '60x40', customSize = null, paperId = null, dpi = 300) {
  8. this.orientation = orientation
  9. this.paperType = paperType
  10. this.customSize = customSize
  11. this.paperId = paperId // 新增纸张ID支持
  12. this.dpi = dpi // 新增DPI支持
  13. this.transformer = new CoordinateTransformer(orientation, paperType, customSize, paperId)
  14. this.config = this.getConfig()
  15. }
  16. /**
  17. * 更新纸张类型
  18. */
  19. updatePaperType(paperType, customSize = null, paperId = null) {
  20. this.paperType = paperType
  21. this.customSize = customSize
  22. this.paperId = paperId
  23. this.transformer.updatePaperType(paperType, customSize, paperId)
  24. }
  25. /**
  26. * 更新纸张ID新增方法
  27. */
  28. updatePaperId(paperId) {
  29. this.paperId = paperId
  30. this.transformer.updatePaperId(paperId)
  31. }
  32. /**
  33. * 更新DPI新增方法
  34. */
  35. updateDPI(dpi) {
  36. this.dpi = dpi
  37. }
  38. /**
  39. * 获取配置参数
  40. */
  41. getConfig() {
  42. const configs = {
  43. portrait: {
  44. fieldOrientation: '^FWN', // 正常方向
  45. barcodeOrientation: 'N', // 正常条码
  46. qrcodeOrientation: 'N' // 正常二维码
  47. },
  48. landscape: {
  49. fieldOrientation: '^FWB', // 旋转90度
  50. barcodeOrientation: 'B', // 旋转90度条码
  51. qrcodeOrientation: 'B' // 旋转90度二维码
  52. }
  53. }
  54. return configs[this.orientation] || configs.portrait
  55. }
  56. /**
  57. * 生成完整的ZPL代码
  58. * @param {Array} elements - 设计元素数组
  59. * @returns {string} ZPL代码
  60. */
  61. generate(elements) {
  62. if (!elements || elements.length === 0) {
  63. return ''
  64. }
  65. const zpl = ['^XA'] // ZPL开始标记
  66. zpl.push(this.config.fieldOrientation) // 设置打印方向
  67. // 处理每个元素
  68. elements.forEach(element => {
  69. const elementZPL = this.generateElementZPL(element)
  70. if (elementZPL) {
  71. zpl.push(elementZPL)
  72. }
  73. })
  74. zpl.push('^XZ') // ZPL结束标记
  75. return zpl.join('\n')
  76. }
  77. /**
  78. * 生成单个元素的ZPL代码
  79. * @param {Object} element - 设计元素
  80. * @returns {string} 元素的ZPL代码
  81. */
  82. generateElementZPL(element) {
  83. const { x, y } = this.transformer.toZPL(element.x, element.y)
  84. switch (element.type) {
  85. case 'text':
  86. return this.generateTextZPL(element, x, y)
  87. case 'onecode':
  88. return this.generateOnecodeZPL(element, x, y)
  89. case 'qrcode':
  90. return this.generateQRCodeZPL(element, x, y)
  91. case 'pic':
  92. return this.generateImageZPL(element, x, y)
  93. case 'hLine':
  94. return this.generateHorizontalLineZPL(element, x, y)
  95. case 'vLine':
  96. return this.generateVerticalLineZPL(element, x, y)
  97. case 'serialNumber':
  98. return this.generateSerialNumberZPL(element, x, y)
  99. default:
  100. return ''
  101. }
  102. }
  103. /**
  104. * 生成文本ZPL代码
  105. */
  106. generateTextZPL(element, x, y) {
  107. const zpl = []
  108. // 生成字体命令
  109. const fontCommand = this.generateFontCommand(element)
  110. zpl.push(fontCommand)
  111. if (element.isChecked) {
  112. zpl.push(`^FO${x-60},${y}^GB45,45,2,B,0^FS ^FO${x-50},${y+10}^AJN,35,35^FD√^FS `)
  113. }
  114. const lineWidth = Math.max(1, Math.round((element.lineWidth || 200) * this.dpi / 25.4))
  115. // 处理文本对齐
  116. const alignmentParam = this.getTextAlignmentParam(element.textAlign)
  117. // 基础文本
  118. const textCommand = element.newline
  119. ? `^FO${x},${y}^FB${lineWidth},${element.lineRows},0,${alignmentParam}^CFJ,${element.fontSize}^FD${element.data}^FS`
  120. : `^FO${x},${y}^FD${element.data}^FS`
  121. zpl.push(textCommand)
  122. // 加粗效果(通过偏移重复打印实现)
  123. if (element.bold) {
  124. const boldCommands = element.newline ? [
  125. `^FO${x + 1},${y}^FB${lineWidth},${element.lineRows},0,${alignmentParam}^CFJ,${element.fontSize}^FD${element.data}^FS`,
  126. `^FO${x},${y + 1}^FB${lineWidth},${element.lineRows},0,${alignmentParam}^CFJ,${element.fontSize}^FD${element.data}^FS`,
  127. `^FO${x + 1},${y + 1}^FB${lineWidth},${element.lineRows},0,${alignmentParam}^CFJ,${element.fontSize}^FD${element.data}^FS`
  128. ] : [
  129. `^FO${x + 1},${y}^FD${element.data}^FS`,
  130. `^FO${x},${y + 1}^FD${element.data}^FS`,
  131. `^FO${x + 1},${y + 1}^FD${element.data}^FS`
  132. ]
  133. zpl.push(...boldCommands)
  134. }
  135. // 下划线效果(通过线条实现)
  136. if (element.fontUnderline) {
  137. const textWidth = this.estimateTextWidth(element.data, element.fontSize)
  138. const underlineY = y + element.fontSize + 2
  139. zpl.push(`^FO${x},${underlineY}^GB${textWidth},2,2,B^FS`)
  140. }
  141. return zpl.join('\n')
  142. }
  143. /**
  144. * 生成字体命令
  145. */
  146. generateFontCommand(element) {
  147. let fontCmd = '^CI28' // 设置字符编码
  148. // 根据字体族选择字体文件
  149. const fontFamily = element.fontFamily
  150. if (fontFamily && fontFamily !== 'default') {
  151. const fontFile = this.mapFontFamilyToFile(fontFamily)
  152. if (fontFile) {
  153. fontCmd += ` ^CWJ,E:${fontFile}`
  154. console.log(`ZPL字体映射: ${fontFamily} -> ${fontFile}`)
  155. } else {
  156. fontCmd += ' ^CWJ,E:MSYH.TTF' // 默认字体
  157. console.warn(`未找到字体映射: ${fontFamily},使用默认字体`)
  158. }
  159. } else {
  160. fontCmd += ' ^CWJ,E:MSYH.TTF' // 默认字体
  161. console.log('使用默认字体: MSYH.TTF')
  162. }
  163. // 设置字体大小
  164. const fontSize = element.fontSize || 30
  165. fontCmd += ` ^CFJ,${fontSize}`
  166. // 添加调试信息
  167. console.log(`生成字体命令: ${fontCmd}`)
  168. console.log(`字体族: ${fontFamily}, 字体大小: ${fontSize}`)
  169. return fontCmd
  170. }
  171. /**
  172. * 映射字体族到字体文件
  173. */
  174. mapFontFamilyToFile(fontFamily) {
  175. const fontMapping = {
  176. // 中文字体
  177. 'Microsoft YaHei': 'MSYH.TTF',
  178. '微软雅黑': 'MSYH.TTF',
  179. 'SimSun': 'SIMSUN.TTC',
  180. '宋体': 'SIMSUN.TTC',
  181. 'SimHei': 'SIMHEI.TTF',
  182. '黑体': 'SIMHEI.TTF',
  183. 'KaiTi': 'KAITI.TTF',
  184. '楷体': 'KAITI.TTF',
  185. 'FangSong': 'SIMFANG.TTF',
  186. '仿宋': 'SIMFANG.TTF',
  187. 'Microsoft JhengHei': 'MSJH.TTF',
  188. '微軟正黑體': 'MSJH.TTF',
  189. 'PMingLiU': 'PMINGLIU.TTF',
  190. '新細明體': 'PMINGLIU.TTF',
  191. 'DFKai-SB': 'DFKAI.TTF',
  192. '標楷體': 'DFKAI.TTF',
  193. // 英文字体
  194. 'Arial': 'ARIAL.TTF',
  195. 'Times New Roman': 'TIMES.TTF',
  196. 'Courier New': 'COUR.TTF',
  197. 'Helvetica': 'HELV.TTF',
  198. 'Verdana': 'VERDANA.TTF',
  199. 'Georgia': 'GEORGIA.TTF',
  200. 'Tahoma': 'TAHOMA.TTF',
  201. 'Trebuchet MS': 'TREBUC.TTF',
  202. 'Lucida Console': 'LUCON.TTF',
  203. 'Impact': 'IMPACT.TTF',
  204. 'Comic Sans MS': 'COMIC.TTF',
  205. 'Palatino': 'PALA.TTF',
  206. 'Garamond': 'GARA.TTF',
  207. 'Bookman': 'BOOKOS.TTF',
  208. 'Century Gothic': 'CENTURY.TTF',
  209. 'Franklin Gothic': 'FRANKLIN.TTF',
  210. 'Gill Sans': 'GILLSANS.TTF',
  211. 'Optima': 'OPTIMA.TTF',
  212. 'Futura': 'FUTURA.TTF',
  213. 'Bodoni': 'BODONI.TTF',
  214. 'Didot': 'DIDOT.TTF',
  215. // 等宽字体
  216. 'Consolas': 'CONSOLAS.TTF',
  217. 'Monaco': 'MONACO.TTF',
  218. 'Menlo': 'MENLO.TTF',
  219. 'Source Code Pro': 'SOURCECODE.TTF',
  220. 'Fira Code': 'FIRACODE.TTF',
  221. 'JetBrains Mono': 'JETBRAINS.TTF',
  222. // 装饰字体
  223. 'Brush Script MT': 'BRUSHSCRIPT.TTF',
  224. 'Papyrus': 'PAPYRUS.TTF',
  225. 'Chalkboard': 'CHALKBOARD.TTF',
  226. 'Marker Felt': 'MARKERFELT.TTF',
  227. 'Zapfino': 'ZAPFINO.TTF',
  228. 'Snell Roundhand': 'SNELLROUND.TTF',
  229. // 默认字体
  230. 'default': 'MSYH.TTF',
  231. 'Default': 'MSYH.TTF'
  232. }
  233. // 如果找不到精确匹配,尝试模糊匹配
  234. if (!fontMapping[fontFamily]) {
  235. const lowerFontFamily = fontFamily.toLowerCase()
  236. for (const [key, value] of Object.entries(fontMapping)) {
  237. if (key.toLowerCase().includes(lowerFontFamily) || lowerFontFamily.includes(key.toLowerCase())) {
  238. return value
  239. }
  240. }
  241. }
  242. return fontMapping[fontFamily] || 'MSYH.TTF' // 默认返回微软雅黑
  243. }
  244. /**
  245. * 获取文本对齐参数
  246. */
  247. getTextAlignmentParam(textAlign) {
  248. switch (textAlign) {
  249. case 'center': return 'C'
  250. case 'right': return 'R'
  251. case 'left':
  252. default: return 'L'
  253. }
  254. }
  255. /**
  256. * 估算文本宽度用于下划线
  257. */
  258. estimateTextWidth(text, fontSize) {
  259. if (!text) return 0
  260. // 简单估算:中文字符按字体大小计算,英文字符按字体大小的0.6倍计算
  261. let chineseCount = 0
  262. let englishCount = 0
  263. for (let i = 0; i < text.length; i++) {
  264. const char = text.charAt(i)
  265. if (char.match(/[\u4e00-\u9fff]/)) {
  266. chineseCount++
  267. } else {
  268. englishCount++
  269. }
  270. }
  271. return Math.round(chineseCount * fontSize + englishCount * fontSize * 0.6)
  272. }
  273. /**
  274. * 生成一维码ZPL代码
  275. */
  276. generateOnecodeZPL(element, x, y) {
  277. const orientation = this.config.barcodeOrientation
  278. const barcodeType = element.barcodeType || 'CODE128'
  279. const showContent = element.showContent !== false // 默认显示内容
  280. // 将毫米转换为ZPL单位
  281. // 宽度:毫米转换为ZPL宽度倍数 (范围1-10)
  282. const width = Math.min(10, Math.round((element.width || 0.33) * this.dpi / 25.4)) // 0.33mm≈13mil 常用
  283. // 高度:毫米转换为点数,使用精确的DPI转换公式
  284. const height = Math.max(1, Math.round((element.height || 15) * this.dpi / 25.4))
  285. // 根据条码类型选择ZPL指令
  286. let zplCommand = ''
  287. switch (barcodeType) {
  288. case 'CODE128':
  289. zplCommand = `^BC${orientation}`
  290. break
  291. case 'CODE39':
  292. zplCommand = `^B3${orientation}`
  293. break
  294. case 'CODE93':
  295. zplCommand = `^BA${orientation}`
  296. break
  297. case 'EAN13':
  298. zplCommand = `^BE${orientation}`
  299. break
  300. case 'EAN8':
  301. zplCommand = `^B8${orientation}`
  302. break
  303. case 'UPCA':
  304. zplCommand = `^BU${orientation}`
  305. break
  306. case 'UPCE':
  307. zplCommand = `^B9${orientation}`
  308. break
  309. default:
  310. zplCommand = `^BC${orientation}` // 默认使用CODE128
  311. }
  312. // 构建完整的ZPL指令
  313. const contentParam = showContent ? 'Y' : 'N'
  314. const additionalParams = orientation === 'B' ? `,${contentParam},N,N` : `,${contentParam}`
  315. return `^FO${x},${y}^BY${width}${zplCommand},${height}${additionalParams}^FD${element.data}^FS`
  316. }
  317. /**
  318. * 生成二维码ZPL代码
  319. */
  320. generateQRCodeZPL(element, x, y) {
  321. const orientation = this.config.qrcodeOrientation
  322. const data = element.data || ''
  323. // 将毫米转换为ZPL尺寸单位,使用精确的DPI转换公式
  324. // 二维码的尺寸参数是模块大小,通常1-10
  325. const sizeInDots = Math.max(1, Math.round((element.height || 10) * this.dpi / 25.4))
  326. // 将点数转换为ZPL模块大小 (大约每20-30个点为1个模块)
  327. let size = Math.max(1, Math.min(10, Math.round(sizeInDots / 25)))
  328. // 根据数据长度自动调整最小尺寸,确保能容纳数据
  329. if (data.length > 300) {
  330. size = Math.max(size, 8) // 超长数据需要更大的尺寸
  331. } else if (data.length > 200) {
  332. size = Math.max(size, 6) // 长数据需要更大的尺寸
  333. } else if (data.length > 100) {
  334. size = Math.max(size, 4) // 中等数据需要中等尺寸
  335. } else {
  336. size = Math.max(size, 2) // 短数据使用较小尺寸
  337. }
  338. // 尝试使用更明确的二维码格式
  339. // 对于长数字内容,使用数字模式可能更有效
  340. if (/^\d+$/.test(data)) {
  341. // 纯数字内容,使用数字模式
  342. return `^FO${x},${y}^BQ${orientation},2,${size}^FDMN,${data}^FS`
  343. } else {
  344. // 混合内容,使用自动模式
  345. return `^FO${x},${y}^BQ${orientation},2,${size}^FDMA,${data}^FS`
  346. }
  347. }
  348. /**
  349. * 生成图片ZPL代码
  350. */
  351. generateImageZPL(element, x, y) {
  352. if (!element.data) return ''
  353. return `^FO${x},${y}^GFA,${element.data}`
  354. }
  355. /**
  356. * 生成横线ZPL代码
  357. */
  358. generateHorizontalLineZPL(element, x, y) {
  359. if (this.orientation === 'landscape') {
  360. // 横向打印时需要特殊处理
  361. const adjustedY = Math.round(1200 - element.x) - element.width
  362. return `^FO${x},${adjustedY}^FWR^GB${element.height},${element.width},3,B^FS`
  363. } else {
  364. return `^FO${x},${y}^GB${element.width},${element.height},3,B^FS`
  365. }
  366. }
  367. /**
  368. * 生成竖线ZPL代码
  369. */
  370. generateVerticalLineZPL(element, x, y) {
  371. if (this.orientation === 'landscape') {
  372. return `^FO${x},${y}^FWR^GB${element.height},${element.width},3,B^FS`
  373. } else {
  374. return `^FO${x},${y}^GB1,${element.height},${element.width},B^FS`
  375. }
  376. }
  377. /**
  378. * 生成流水号ZPL代码
  379. */
  380. generateSerialNumberZPL(element, x, y) {
  381. const zpl = []
  382. // 生成字体命令
  383. const fontCommand = this.generateFontCommand(element)
  384. zpl.push(fontCommand)
  385. const bold = element.bold || false
  386. const base = `^FO${x},${y}^FD${element.data}^FS`
  387. zpl.push(base)
  388. // 可选:加粗效果(重复打印)
  389. if (bold) {
  390. zpl.push(`^FO${x + 1},${y}^FD${element.data}^FS`)
  391. zpl.push(`^FO${x},${y + 1}^FD${element.data}^FS`)
  392. zpl.push(`^FO${x + 1},${y + 1}^FD${element.data}^FS`)
  393. }
  394. return zpl.join('\n')
  395. }
  396. }
  397. /**
  398. * 创建ZPL生成器实例
  399. * @param {string} orientation - 打印方向
  400. * @param {string} paperType - 纸张类型兼容性
  401. * @param {Object} customSize - 自定义尺寸兼容性
  402. * @param {number} paperId - 纸张ID新增
  403. * @param {number} dpi - DPI新增
  404. * @returns {ZPLGenerator} 生成器实例
  405. */
  406. export function createZPLGenerator(orientation, paperType = null, customSize = null, paperId = null, dpi = 300) {
  407. return new ZPLGenerator(orientation, paperType, customSize, paperId, dpi)
  408. }
  409. /**
  410. * 根据纸张ID创建ZPL生成器实例新增方法
  411. * @param {number} paperId - 纸张ID
  412. * @param {string} orientation - 打印方向
  413. * @param {number} dpi - DPI新增
  414. * @returns {ZPLGenerator} 生成器实例
  415. */
  416. export function createZPLGeneratorById(paperId, orientation = 'portrait', dpi = 300) {
  417. return new ZPLGenerator(orientation, null, null, paperId, dpi)
  418. }
  419. /**
  420. * 快速生成ZPL代码
  421. * @param {Array} elements - 设计元素
  422. * @param {string} orientation - 打印方向
  423. * @param {string} paperType - 纸张类型兼容性
  424. * @param {number} paperId - 纸张ID新增
  425. * @param {number} dpi - DPI新增
  426. * @returns {string} ZPL代码
  427. */
  428. export function generateZPL(elements, orientation = 'portrait', paperType = null, paperId = null, dpi = 300) {
  429. const generator = new ZPLGenerator(orientation, paperType, null, paperId, dpi)
  430. return generator.generate(elements)
  431. }
  432. /**
  433. * 根据纸张ID快速生成ZPL代码新增方法
  434. * @param {Array} elements - 设计元素
  435. * @param {number} paperId - 纸张ID
  436. * @param {string} orientation - 打印方向
  437. * @param {number} dpi - DPI新增
  438. * @returns {string} ZPL代码
  439. */
  440. export function generateZPLById(elements, paperId, orientation = 'portrait', dpi = 300) {
  441. const generator = new ZPLGenerator(orientation, null, null, paperId, dpi)
  442. return generator.generate(elements)
  443. }
  444. export default ZPLGenerator