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.
 
 
 
 
 

420 lines
12 KiB

/**
* 画布尺寸计算工具
* 根据纸张物理尺寸、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
}