/** * Shared utility functions * Common functions used across the printing module */ import {Node} from '../compiler' import {IMAGE_WIDTH_PIXELS, PAPER_WIDTH_MM, TEXT_WIDTH_CHARS} from './constants' /** * Convert paper width (mm) to image width (pixels) * @param paperWidthMm Paper width in millimeters * @returns Image width in pixels */ export function paperToImageWidth(paperWidthMm: number): number { switch (paperWidthMm) { case PAPER_WIDTH_MM.LABEL_32: return IMAGE_WIDTH_PIXELS.MM_32 case PAPER_WIDTH_MM.RECEIPT_58: return IMAGE_WIDTH_PIXELS.MM_58 case PAPER_WIDTH_MM.RECEIPT_80: return IMAGE_WIDTH_PIXELS.MM_80 default: // Custom calculation: 8 dots/mm return Math.floor(paperWidthMm * 8) } } /** * Convert paper width (mm) to text width (characters) * @param paperWidthMm Paper width in millimeters * @param font Font type: 'A' (default, 12×24 dots) or 'B' (compact, 9×17 dots) * @returns Text width in characters * * Font A and B may have different typeface designs depending on printer manufacturer. * Font B uses smaller character matrix (9×17 vs 12×24), resulting in ~33% more * characters per line. Some printers also use condensed/narrow design for Font B. */ export function paperToTextWidth(paperWidthMm: number, font: 'A' | 'B' = 'A'): number { const fontTable = font === 'B' ? TEXT_WIDTH_CHARS.FONT_B : TEXT_WIDTH_CHARS.FONT_A switch (paperWidthMm) { case PAPER_WIDTH_MM.LABEL_32: return fontTable.MM_32 case PAPER_WIDTH_MM.RECEIPT_58: return fontTable.MM_58 case PAPER_WIDTH_MM.RECEIPT_80: return fontTable.MM_80 default: // Custom calculation based on font // Font A: ~0.6 chars/mm, Font B: ~0.8 chars/mm (~33% more) const charsPerMm = font === 'B' ? 0.8 : 0.6 return Math.floor(paperWidthMm * charsPerMm) } } /** * Auto-estimate gap (30mm→2mm, 50mm→3mm, 80mm→4mm) */ export function estimateLabelGap(labelHeightMm: number): number { const standardGaps: Record = { 30: 2, 40: 2, 50: 3, 60: 3, 80: 4, 100: 5, } if (standardGaps[labelHeightMm]) { return standardGaps[labelHeightMm] } if (labelHeightMm <= 40) return 2 if (labelHeightMm <= 55) return 3 if (labelHeightMm <= 70) return 3 if (labelHeightMm <= 90) return 4 return 5 } /** * Helper: Estimate document height in mm * Rough estimation for overflow detection * @param nodes Document nodes * @param lineSpacingUnits Line spacing in 1/180 inch units * @returns Estimated height in mm */ export function estimateDocumentHeight(nodes: Node[], lineSpacingUnits: number): number { // Convert line spacing to mm: 1/180 inch = 0.141111... mm const lineHeightMm = (lineSpacingUnits / 180) * 25.4 let totalMm = 0 for (const node of nodes) { switch (node.type) { case 'text': { // Count lines in text (split by \n) const lines = node.content.split('\n').length totalMm += lines * lineHeightMm break } case 'line': totalMm += lineHeightMm break case 'qr': // QR code: size * 0.35mm per module (rough estimate) totalMm += (node.size || 6) * 0.35 * 25 break case 'barcode': // Barcode: height in px / 8 = mm totalMm += (node.heightPx || 100) / 8 break case 'image': // Image: Assume proportional to width (rough) totalMm += 30 // Default ~30mm for images break case 'feed': totalMm += (node.lines || 1) * lineHeightMm break case 'table': { // Table: estimate rows * line height const rowCount = (node.headers ? 1 : 0) + (node.rows?.length || 0) totalMm += rowCount * lineHeightMm * 1.5 // 1.5x for borders break } case 'spacer': totalMm += (node.height || 1) * lineHeightMm break default: // Unknown nodes: add small padding totalMm += lineHeightMm } } return Math.ceil(totalMm) }