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.

419 lines
12 KiB

​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
  1. /**
  2. * 画布尺寸计算工具
  3. * 根据纸张物理尺寸、DPI、缩放比例等计算画布显示尺寸
  4. */
  5. // 标准DPI配置
  6. export const DPI_CONFIGS = {
  7. 152: { name: '152 DPI', description: '低分辨率打印机' },
  8. 203: { name: '203 DPI', description: '标准热敏打印机' },
  9. 300: { name: '300 DPI', description: '高分辨率打印机' },
  10. 600: { name: '600 DPI', description: '超高分辨率打印机' }
  11. }
  12. // 默认显示配置
  13. export const DISPLAY_CONFIG = {
  14. defaultDPI: 203, // 默认DPI
  15. maxCanvasWidth: 800, // 画布区域最大宽度
  16. maxCanvasHeight: 600, // 画布区域最大高度
  17. minCanvasWidth: 200, // 画布区域最小宽度
  18. minCanvasHeight: 150, // 画布区域最小高度
  19. padding: 40, // 画布周围的内边距
  20. scaleFactor: 1.0 // 默认缩放因子
  21. }
  22. /**
  23. * 毫米转像素
  24. * @param {number} mm - 毫米值
  25. * @param {number} dpi - DPI值
  26. * @returns {number} 像素值
  27. */
  28. export function mmToPixels(mm, dpi = DISPLAY_CONFIG.defaultDPI) {
  29. return Math.round(mm * dpi / 25.4)
  30. }
  31. /**
  32. * 像素转毫米
  33. * @param {number} pixels - 像素值
  34. * @param {number} dpi - DPI值
  35. * @returns {number} 毫米值
  36. */
  37. export function pixelsToMm(pixels, dpi = DISPLAY_CONFIG.defaultDPI) {
  38. return Math.round((pixels * 25.4 / dpi) * 10) / 10
  39. }
  40. /**
  41. * 根据纸张物理尺寸计算精确的像素尺寸
  42. * @param {string} paperType - 纸张类型 (如 '60x40' 或 '4x6')
  43. * @param {number} dpi - DPI值
  44. * @param {string} orientation - 打印方向
  45. * @returns {Object} 精确的像素尺寸
  46. */
  47. export function calculateExactPixelSize(paperType, dpi = DISPLAY_CONFIG.defaultDPI, orientation = 'portrait') {
  48. let widthMm, heightMm
  49. // 检查是否为英寸制纸张(如4x6)
  50. if (isInchPaper(paperType)) {
  51. // 英寸制纸张,直接从英寸转换为像素
  52. const [widthInch, heightInch] = paperType.split('x').map(Number)
  53. if (!widthInch || !heightInch) {
  54. throw new Error(`Invalid inch paper type: ${paperType}`)
  55. }
  56. // 英寸直接转像素
  57. let pixelWidth = Math.round(widthInch * dpi)
  58. let pixelHeight = Math.round(heightInch * dpi)
  59. // 计算对应的毫米值(用于显示)
  60. widthMm = Math.round(widthInch * 25.4 * 10) / 10
  61. heightMm = Math.round(heightInch * 25.4 * 10) / 10
  62. // 横向打印时交换宽高
  63. if (orientation === 'landscape') {
  64. [pixelWidth, pixelHeight] = [pixelHeight, pixelWidth]
  65. ;[widthMm, heightMm] = [heightMm, widthMm]
  66. }
  67. return {
  68. width: pixelWidth,
  69. height: pixelHeight,
  70. widthMm,
  71. heightMm,
  72. dpi,
  73. orientation
  74. }
  75. } else {
  76. // 毫米制纸张,从毫米转换
  77. const [widthMmParsed, heightMmParsed] = paperType.split('x').map(Number)
  78. if (!widthMmParsed || !heightMmParsed) {
  79. throw new Error(`Invalid paper type: ${paperType}`)
  80. }
  81. widthMm = widthMmParsed
  82. heightMm = heightMmParsed
  83. // 计算精确像素尺寸
  84. let pixelWidth = mmToPixels(widthMm, dpi)
  85. let pixelHeight = mmToPixels(heightMm, dpi)
  86. // 横向打印时交换宽高
  87. if (orientation === 'landscape') {
  88. [pixelWidth, pixelHeight] = [pixelHeight, pixelWidth]
  89. ;[widthMm, heightMm] = [heightMm, widthMm]
  90. }
  91. return {
  92. width: pixelWidth,
  93. height: pixelHeight,
  94. widthMm,
  95. heightMm,
  96. dpi,
  97. orientation
  98. }
  99. }
  100. }
  101. /**
  102. * 判断是否为英寸制纸张
  103. * @param {string} paperType - 纸张类型
  104. * @returns {boolean} 是否为英寸制
  105. */
  106. function isInchPaper(paperType) {
  107. // 常见的英寸制纸张规格
  108. const inchPapers = ['4x6', '4x4', '4x2', '3x2', '2x1', '2x3', '2x4', '6x4']
  109. return inchPapers.includes(paperType)
  110. }
  111. /**
  112. * 计算画布显示尺寸和缩放比例
  113. * @param {Object} exactSize - 精确像素尺寸
  114. * @param {Object} containerSize - 容器尺寸 {width, height}
  115. * @returns {Object} 显示配置
  116. */
  117. export function calculateDisplaySize(exactSize, containerSize) {
  118. const { width: exactWidth, height: exactHeight } = exactSize
  119. const { width: containerWidth, height: containerHeight } = containerSize
  120. // 计算可用空间(减去内边距)
  121. const availableWidth = containerWidth - DISPLAY_CONFIG.padding * 2
  122. const availableHeight = containerHeight - DISPLAY_CONFIG.padding * 2
  123. // 计算缩放比例
  124. const scaleX = availableWidth / exactWidth
  125. const scaleY = availableHeight / exactHeight
  126. const scale = Math.min(scaleX, scaleY, 1) // 不超过1倍缩放
  127. // 计算显示尺寸
  128. const displayWidth = Math.round(exactWidth * scale)
  129. const displayHeight = Math.round(exactHeight * scale)
  130. // 确保不小于最小尺寸
  131. const finalWidth = Math.max(displayWidth, DISPLAY_CONFIG.minCanvasWidth)
  132. const finalHeight = Math.max(displayHeight, DISPLAY_CONFIG.minCanvasHeight)
  133. return {
  134. // 显示尺寸
  135. displayWidth: finalWidth,
  136. displayHeight: finalHeight,
  137. // 精确尺寸
  138. exactWidth,
  139. exactHeight,
  140. // 缩放信息
  141. scale: finalWidth / exactWidth,
  142. isScaled: scale < 1,
  143. // 适配信息
  144. fitsInContainer: exactWidth <= availableWidth && exactHeight <= availableHeight,
  145. // 居中位置
  146. offsetX: Math.max(0, (containerWidth - finalWidth) / 2),
  147. offsetY: Math.max(0, (containerHeight - finalHeight) / 2)
  148. }
  149. }
  150. /**
  151. * 获取纸张的物理信息
  152. * @param {string} paperType - 纸张类型
  153. * @returns {Object} 纸张物理信息
  154. */
  155. export function getPaperPhysicalInfo(paperType) {
  156. if (paperType === 'custom') {
  157. return {
  158. name: '自定义',
  159. category: 'custom',
  160. isStandard: false
  161. }
  162. }
  163. // 检查是否为英寸制纸张
  164. if (isInchPaper(paperType)) {
  165. const [widthInch, heightInch] = paperType.split('x').map(Number)
  166. // 转换为毫米用于计算
  167. const widthMm = Math.round(widthInch * 25.4 * 10) / 10
  168. const heightMm = Math.round(heightInch * 25.4 * 10) / 10
  169. // 纸张分类
  170. let category = 'shipping' // 英寸制纸张通常用于物流
  171. if (widthInch <= 2 || heightInch <= 2) category = 'barcode'
  172. if (widthInch >= 6 || heightInch >= 6) category = 'industrial'
  173. return {
  174. name: `${widthInch}×${heightInch}英寸 (${widthMm}×${heightMm}mm)`,
  175. widthMm,
  176. heightMm,
  177. widthInch,
  178. heightInch,
  179. category,
  180. isStandard: true,
  181. isInchPaper: true,
  182. area: widthMm * heightMm,
  183. aspectRatio: Math.round((widthMm / heightMm) * 100) / 100
  184. }
  185. } else {
  186. // 毫米制纸张
  187. const [widthMm, heightMm] = paperType.split('x').map(Number)
  188. // 纸张分类
  189. let category = 'standard'
  190. if (heightMm <= 30) category = 'barcode'
  191. if (widthMm === 57 || widthMm === 80) category = 'thermal'
  192. return {
  193. name: `${widthMm}×${heightMm}mm`,
  194. widthMm,
  195. heightMm,
  196. category,
  197. isStandard: true,
  198. isInchPaper: false,
  199. area: widthMm * heightMm,
  200. aspectRatio: Math.round((widthMm / heightMm) * 100) / 100
  201. }
  202. }
  203. }
  204. /**
  205. * 计算完整的画布配置
  206. * @param {string} paperType - 纸张类型
  207. * @param {string} orientation - 打印方向
  208. * @param {number} dpi - DPI值
  209. * @param {Object} containerSize - 容器尺寸
  210. * @param {Object} customSize - 自定义尺寸 (仅当paperType为'custom'时使用)
  211. * @returns {Object} 完整的画布配置
  212. */
  213. export function calculateCanvasConfig(paperType, orientation = 'portrait', dpi = DISPLAY_CONFIG.defaultDPI, containerSize = null, customSize = null) {
  214. let exactSize
  215. if (paperType === 'custom' && customSize) {
  216. // 自定义尺寸处理
  217. const widthMm = pixelsToMm(customSize.width, dpi)
  218. const heightMm = pixelsToMm(customSize.height, dpi)
  219. exactSize = {
  220. width: orientation === 'landscape' ? customSize.height : customSize.width,
  221. height: orientation === 'landscape' ? customSize.width : customSize.height,
  222. widthMm: orientation === 'landscape' ? heightMm : widthMm,
  223. heightMm: orientation === 'landscape' ? widthMm : heightMm,
  224. dpi,
  225. orientation
  226. }
  227. } else {
  228. // 标准纸张尺寸处理
  229. exactSize = calculateExactPixelSize(paperType, dpi, orientation)
  230. }
  231. // 计算显示配置
  232. const defaultContainer = {
  233. width: DISPLAY_CONFIG.maxCanvasWidth,
  234. height: DISPLAY_CONFIG.maxCanvasHeight
  235. }
  236. const displayConfig = calculateDisplaySize(exactSize, containerSize || defaultContainer)
  237. // 获取纸张物理信息
  238. const physicalInfo = getPaperPhysicalInfo(paperType)
  239. return {
  240. // 基本信息
  241. paperType,
  242. orientation,
  243. dpi,
  244. // 物理信息
  245. physical: physicalInfo,
  246. // 精确尺寸(用于ZPL生成和坐标转换)
  247. exact: exactSize,
  248. // 显示尺寸(用于画布渲染)
  249. display: displayConfig,
  250. // 元数据
  251. meta: {
  252. timestamp: Date.now(),
  253. isCustom: paperType === 'custom',
  254. description: `${physicalInfo.name} (${orientation === 'landscape' ? '横向' : '纵向'}) @ ${dpi}DPI`
  255. }
  256. }
  257. }
  258. /**
  259. * 根据纸张ID计算画布配置(新增方法)
  260. * @param {number} paperId - 纸张ID
  261. * @param {string} orientation - 打印方向
  262. * @param {number} dpi - DPI值
  263. * @param {Object} containerSize - 容器尺寸
  264. * @returns {Object} 完整的画布配置
  265. */
  266. export function calculateCanvasConfigById(paperId, orientation = 'portrait', dpi = DISPLAY_CONFIG.defaultDPI, containerSize = null) {
  267. // 延迟导入避免循环依赖
  268. const { default: dynamicPaperConfig } = require('./paperConfigDynamic.js')
  269. const paper = dynamicPaperConfig.getPaperById(paperId)
  270. if (!paper) {
  271. throw new Error(`Paper with ID ${paperId} not found`)
  272. }
  273. // 使用纸张的毫米尺寸计算精确像素尺寸
  274. const exactSize = {
  275. width: Math.round(paper.widthMm * dpi / 25.4),
  276. height: Math.round(paper.heightMm * dpi / 25.4),
  277. widthMm: paper.widthMm,
  278. heightMm: paper.heightMm,
  279. dpi,
  280. orientation
  281. }
  282. // 横向打印时交换宽高
  283. if (orientation === 'landscape') {
  284. [exactSize.width, exactSize.height] = [exactSize.height, exactSize.width]
  285. ;[exactSize.widthMm, exactSize.heightMm] = [exactSize.heightMm, exactSize.widthMm]
  286. }
  287. // 计算显示配置
  288. const defaultContainer = {
  289. width: DISPLAY_CONFIG.maxCanvasWidth,
  290. height: DISPLAY_CONFIG.maxCanvasHeight
  291. }
  292. const displayConfig = calculateDisplaySize(exactSize, containerSize || defaultContainer)
  293. // 构建物理信息
  294. const physicalInfo = {
  295. name: paper.name,
  296. widthMm: paper.widthMm,
  297. heightMm: paper.heightMm,
  298. description: paper.description || '',
  299. isStandard: true,
  300. isCustom: false,
  301. area: paper.widthMm * paper.heightMm,
  302. aspectRatio: Math.round((paper.widthMm / paper.heightMm) * 100) / 100
  303. }
  304. return {
  305. // 基本信息
  306. paperId,
  307. paperType: paper.name, // 兼容性
  308. orientation,
  309. dpi,
  310. // 物理信息
  311. physical: physicalInfo,
  312. // 精确尺寸(用于ZPL生成和坐标转换)
  313. exact: exactSize,
  314. // 显示尺寸(用于画布渲染)
  315. display: displayConfig,
  316. // 纸张对象
  317. paper,
  318. // 元数据
  319. meta: {
  320. timestamp: Date.now(),
  321. isCustom: false,
  322. isDynamic: true,
  323. description: `${paper.name} (${orientation === 'landscape' ? '横向' : '纵向'}) @ ${dpi}DPI`
  324. }
  325. }
  326. }
  327. /**
  328. * 获取推荐的DPI值
  329. * @param {string} paperType - 纸张类型
  330. * @returns {number} 推荐的DPI值
  331. */
  332. export function getRecommendedDPI(paperType) {
  333. const physicalInfo = getPaperPhysicalInfo(paperType)
  334. // 根据纸张类型推荐DPI
  335. switch (physicalInfo.category) {
  336. case 'barcode':
  337. return 300 // 条码标签需要高精度
  338. case 'thermal':
  339. return 203 // 热敏纸标准DPI
  340. case 'shipping':
  341. return 203 // 物流标签标准DPI
  342. case 'industrial':
  343. return 300 // 工业标签推荐高精度
  344. default:
  345. return 203 // 标准标签DPI
  346. }
  347. }
  348. export default {
  349. DPI_CONFIGS,
  350. DISPLAY_CONFIG,
  351. mmToPixels,
  352. pixelsToMm,
  353. calculateExactPixelSize,
  354. calculateDisplaySize,
  355. getPaperPhysicalInfo,
  356. calculateCanvasConfig,
  357. calculateCanvasConfigById,
  358. getRecommendedDPI
  359. }