/** * 画布尺寸计算工具 * 根据纸张物理尺寸、DPI、缩放比例等计算画布显示尺寸 */ // 标准DPI配置 export const DPI_CONFIGS = { 152: { name: '152 DPI', description: '低分辨率打印机' }, 203: { name: '203 DPI', description: '标准热敏打印机' }, 300: { name: '300 DPI', description: '高分辨率打印机' }, 600: { name: '600 DPI', description: '超高分辨率打印机' } } // 默认显示配置 export const DISPLAY_CONFIG = { defaultDPI: 203, // 默认DPI maxCanvasWidth: 800, // 画布区域最大宽度 maxCanvasHeight: 600, // 画布区域最大高度 minCanvasWidth: 200, // 画布区域最小宽度 minCanvasHeight: 150, // 画布区域最小高度 padding: 40, // 画布周围的内边距 scaleFactor: 1.0 // 默认缩放因子 } /** * 毫米转像素 * @param {number} mm - 毫米值 * @param {number} dpi - DPI值 * @returns {number} 像素值 */ export function mmToPixels(mm, dpi = DISPLAY_CONFIG.defaultDPI) { return Math.round(mm * dpi / 25.4) } /** * 像素转毫米 * @param {number} pixels - 像素值 * @param {number} dpi - DPI值 * @returns {number} 毫米值 */ export function pixelsToMm(pixels, dpi = DISPLAY_CONFIG.defaultDPI) { return Math.round((pixels * 25.4 / dpi) * 10) / 10 } /** * 根据纸张物理尺寸计算精确的像素尺寸 * @param {string} paperType - 纸张类型 (如 '60x40' 或 '4x6') * @param {number} dpi - DPI值 * @param {string} orientation - 打印方向 * @returns {Object} 精确的像素尺寸 */ export function calculateExactPixelSize(paperType, dpi = DISPLAY_CONFIG.defaultDPI, orientation = 'portrait') { let widthMm, heightMm // 检查是否为英寸制纸张(如4x6) if (isInchPaper(paperType)) { // 英寸制纸张,直接从英寸转换为像素 const [widthInch, heightInch] = paperType.split('x').map(Number) if (!widthInch || !heightInch) { throw new Error(`Invalid inch paper type: ${paperType}`) } // 英寸直接转像素 let pixelWidth = Math.round(widthInch * dpi) let pixelHeight = Math.round(heightInch * dpi) // 计算对应的毫米值(用于显示) widthMm = Math.round(widthInch * 25.4 * 10) / 10 heightMm = Math.round(heightInch * 25.4 * 10) / 10 // 横向打印时交换宽高 if (orientation === 'landscape') { [pixelWidth, pixelHeight] = [pixelHeight, pixelWidth] ;[widthMm, heightMm] = [heightMm, widthMm] } return { width: pixelWidth, height: pixelHeight, widthMm, heightMm, dpi, orientation } } else { // 毫米制纸张,从毫米转换 const [widthMmParsed, heightMmParsed] = paperType.split('x').map(Number) if (!widthMmParsed || !heightMmParsed) { throw new Error(`Invalid paper type: ${paperType}`) } widthMm = widthMmParsed heightMm = heightMmParsed // 计算精确像素尺寸 let pixelWidth = mmToPixels(widthMm, dpi) let pixelHeight = mmToPixels(heightMm, dpi) // 横向打印时交换宽高 if (orientation === 'landscape') { [pixelWidth, pixelHeight] = [pixelHeight, pixelWidth] ;[widthMm, heightMm] = [heightMm, widthMm] } return { width: pixelWidth, height: pixelHeight, widthMm, heightMm, dpi, orientation } } } /** * 判断是否为英寸制纸张 * @param {string} paperType - 纸张类型 * @returns {boolean} 是否为英寸制 */ function isInchPaper(paperType) { // 常见的英寸制纸张规格 const inchPapers = ['4x6', '4x4', '4x2', '3x2', '2x1', '2x3', '2x4', '6x4'] return inchPapers.includes(paperType) } /** * 计算画布显示尺寸和缩放比例 * @param {Object} exactSize - 精确像素尺寸 * @param {Object} containerSize - 容器尺寸 {width, height} * @returns {Object} 显示配置 */ export function calculateDisplaySize(exactSize, containerSize) { const { width: exactWidth, height: exactHeight } = exactSize const { width: containerWidth, height: containerHeight } = containerSize // 计算可用空间(减去内边距) const availableWidth = containerWidth - DISPLAY_CONFIG.padding * 2 const availableHeight = containerHeight - DISPLAY_CONFIG.padding * 2 // 计算缩放比例 const scaleX = availableWidth / exactWidth const scaleY = availableHeight / exactHeight const scale = Math.min(scaleX, scaleY, 1) // 不超过1倍缩放 // 计算显示尺寸 const displayWidth = Math.round(exactWidth * scale) const displayHeight = Math.round(exactHeight * scale) // 确保不小于最小尺寸 const finalWidth = Math.max(displayWidth, DISPLAY_CONFIG.minCanvasWidth) const finalHeight = Math.max(displayHeight, DISPLAY_CONFIG.minCanvasHeight) return { // 显示尺寸 displayWidth: finalWidth, displayHeight: finalHeight, // 精确尺寸 exactWidth, exactHeight, // 缩放信息 scale: finalWidth / exactWidth, isScaled: scale < 1, // 适配信息 fitsInContainer: exactWidth <= availableWidth && exactHeight <= availableHeight, // 居中位置 offsetX: Math.max(0, (containerWidth - finalWidth) / 2), offsetY: Math.max(0, (containerHeight - finalHeight) / 2) } } /** * 获取纸张的物理信息 * @param {string} paperType - 纸张类型 * @returns {Object} 纸张物理信息 */ export function getPaperPhysicalInfo(paperType) { if (paperType === 'custom') { return { name: '自定义', category: 'custom', isStandard: false } } // 检查是否为英寸制纸张 if (isInchPaper(paperType)) { const [widthInch, heightInch] = paperType.split('x').map(Number) // 转换为毫米用于计算 const widthMm = Math.round(widthInch * 25.4 * 10) / 10 const heightMm = Math.round(heightInch * 25.4 * 10) / 10 // 纸张分类 let category = 'shipping' // 英寸制纸张通常用于物流 if (widthInch <= 2 || heightInch <= 2) category = 'barcode' if (widthInch >= 6 || heightInch >= 6) category = 'industrial' return { name: `${widthInch}×${heightInch}英寸 (${widthMm}×${heightMm}mm)`, widthMm, heightMm, widthInch, heightInch, category, isStandard: true, isInchPaper: true, area: widthMm * heightMm, aspectRatio: Math.round((widthMm / heightMm) * 100) / 100 } } else { // 毫米制纸张 const [widthMm, heightMm] = paperType.split('x').map(Number) // 纸张分类 let category = 'standard' if (heightMm <= 30) category = 'barcode' if (widthMm === 57 || widthMm === 80) category = 'thermal' return { name: `${widthMm}×${heightMm}mm`, widthMm, heightMm, category, isStandard: true, isInchPaper: false, area: widthMm * heightMm, aspectRatio: Math.round((widthMm / heightMm) * 100) / 100 } } } /** * 计算完整的画布配置 * @param {string} paperType - 纸张类型 * @param {string} orientation - 打印方向 * @param {number} dpi - DPI值 * @param {Object} containerSize - 容器尺寸 * @param {Object} customSize - 自定义尺寸 (仅当paperType为'custom'时使用) * @returns {Object} 完整的画布配置 */ export function calculateCanvasConfig(paperType, orientation = 'portrait', dpi = DISPLAY_CONFIG.defaultDPI, containerSize = null, customSize = null) { let exactSize if (paperType === 'custom' && customSize) { // 自定义尺寸处理 const widthMm = pixelsToMm(customSize.width, dpi) const heightMm = pixelsToMm(customSize.height, dpi) exactSize = { width: orientation === 'landscape' ? customSize.height : customSize.width, height: orientation === 'landscape' ? customSize.width : customSize.height, widthMm: orientation === 'landscape' ? heightMm : widthMm, heightMm: orientation === 'landscape' ? widthMm : heightMm, dpi, orientation } } else { // 标准纸张尺寸处理 exactSize = calculateExactPixelSize(paperType, dpi, orientation) } // 计算显示配置 const defaultContainer = { width: DISPLAY_CONFIG.maxCanvasWidth, height: DISPLAY_CONFIG.maxCanvasHeight } const displayConfig = calculateDisplaySize(exactSize, containerSize || defaultContainer) // 获取纸张物理信息 const physicalInfo = getPaperPhysicalInfo(paperType) return { // 基本信息 paperType, orientation, dpi, // 物理信息 physical: physicalInfo, // 精确尺寸(用于ZPL生成和坐标转换) exact: exactSize, // 显示尺寸(用于画布渲染) display: displayConfig, // 元数据 meta: { timestamp: Date.now(), isCustom: paperType === 'custom', description: `${physicalInfo.name} (${orientation === 'landscape' ? '横向' : '纵向'}) @ ${dpi}DPI` } } } /** * 根据纸张ID计算画布配置(新增方法) * @param {number} paperId - 纸张ID * @param {string} orientation - 打印方向 * @param {number} dpi - DPI值 * @param {Object} containerSize - 容器尺寸 * @returns {Object} 完整的画布配置 */ export function calculateCanvasConfigById(paperId, orientation = 'portrait', dpi = DISPLAY_CONFIG.defaultDPI, containerSize = null) { // 延迟导入避免循环依赖 const { default: dynamicPaperConfig } = require('./paperConfigDynamic.js') const paper = dynamicPaperConfig.getPaperById(paperId) if (!paper) { throw new Error(`Paper with ID ${paperId} not found`) } // 使用纸张的毫米尺寸计算精确像素尺寸 const exactSize = { width: Math.round(paper.widthMm * dpi / 25.4), height: Math.round(paper.heightMm * dpi / 25.4), widthMm: paper.widthMm, heightMm: paper.heightMm, dpi, orientation } // 横向打印时交换宽高 if (orientation === 'landscape') { [exactSize.width, exactSize.height] = [exactSize.height, exactSize.width] ;[exactSize.widthMm, exactSize.heightMm] = [exactSize.heightMm, exactSize.widthMm] } // 计算显示配置 const defaultContainer = { width: DISPLAY_CONFIG.maxCanvasWidth, height: DISPLAY_CONFIG.maxCanvasHeight } const displayConfig = calculateDisplaySize(exactSize, containerSize || defaultContainer) // 构建物理信息 const physicalInfo = { name: paper.name, widthMm: paper.widthMm, heightMm: paper.heightMm, description: paper.description || '', isStandard: true, isCustom: false, area: paper.widthMm * paper.heightMm, aspectRatio: Math.round((paper.widthMm / paper.heightMm) * 100) / 100 } return { // 基本信息 paperId, paperType: paper.name, // 兼容性 orientation, dpi, // 物理信息 physical: physicalInfo, // 精确尺寸(用于ZPL生成和坐标转换) exact: exactSize, // 显示尺寸(用于画布渲染) display: displayConfig, // 纸张对象 paper, // 元数据 meta: { timestamp: Date.now(), isCustom: false, isDynamic: true, description: `${paper.name} (${orientation === 'landscape' ? '横向' : '纵向'}) @ ${dpi}DPI` } } } /** * 获取推荐的DPI值 * @param {string} paperType - 纸张类型 * @returns {number} 推荐的DPI值 */ export function getRecommendedDPI(paperType) { const physicalInfo = getPaperPhysicalInfo(paperType) // 根据纸张类型推荐DPI switch (physicalInfo.category) { case 'barcode': return 300 // 条码标签需要高精度 case 'thermal': return 203 // 热敏纸标准DPI case 'shipping': return 203 // 物流标签标准DPI case 'industrial': return 300 // 工业标签推荐高精度 default: return 203 // 标准标签DPI } } export default { DPI_CONFIGS, DISPLAY_CONFIG, mmToPixels, pixelsToMm, calculateExactPixelSize, calculateDisplaySize, getPaperPhysicalInfo, calculateCanvasConfig, calculateCanvasConfigById, getRecommendedDPI }