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
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
|
|
}
|