@roshanlimbu/nepali-print-server
Version:
A Node.js server for printing receipts with support for Nepali Unicode text and cross-platform printing
291 lines (254 loc) • 9.4 kB
JavaScript
const { exec } = require("child_process");
const fs = require("fs");
const path = require("path");
const os = require("os");
const iconv = require('iconv-lite');
class CustomPrinter {
constructor(options = {}) {
this.defaultPrinter = options.defaultPrinter;
this.encoding = options.encoding || 'utf8';
this.tempDir = options.tempDir || os.tmpdir();
this.platform = os.platform();
}
// Helper method to detect Nepali text (Devanagari Unicode range)
containsNepaliText(text) {
return /[\u0900-\u097F]/.test(text);
}
// Get available printers (Windows/macOS/Linux)
async getAvailablePrinters() {
return new Promise((resolve, reject) => {
let command;
if (this.platform === 'win32') {
// Windows: Use PowerShell to get printers
command =
'powershell "Get-Printer | Select-Object Name | ForEach-Object { $_.Name }"';
} else {
// macOS/Linux: Use CUPS
command = 'lpstat -p';
}
exec(command, (error, stdout, stderr) => {
if (error) {
console.warn('Could not get printer list:', error.message);
resolve([]);
return;
}
let printers = [];
if (this.platform === 'win32') {
// Parse Windows PowerShell output
printers = stdout
.split('\n')
.map((line) => line.trim())
.filter((line) => line && !line.includes('---') && line !== 'Name')
.filter(Boolean);
} else {
// Parse CUPS output
printers = stdout
.split('\n')
.filter((line) => line.startsWith('printer'))
.map((line) => line.split(' ')[1])
.filter(Boolean);
}
resolve(printers);
});
});
}
// Print using Windows (PowerShell/CMD)
async printWithWindows(content, printerName = null) {
return new Promise((resolve, reject) => {
const tempFile = path.join(this.tempDir, `print_${Date.now()}.txt`);
// Convert content to proper encoding for Windows printing
let encodedContent;
try {
// For Nepali text, use UTF-8 with BOM for better Windows compatibility
if (this.containsNepaliText(content)) {
encodedContent = iconv.encode(content, 'utf8', { addBOM: true });
} else {
encodedContent = iconv.encode(content, this.encoding);
}
} catch (error) {
encodedContent = content; // fallback to original content
}
// Write content to temporary file with proper encoding
fs.writeFile(tempFile, encodedContent, (writeErr) => {
if (writeErr) {
reject(new Error(`Failed to write temp file: ${writeErr.message}`));
return;
}
const printer = printerName || this.defaultPrinter;
let command;
if (printer) {
// Print to specific printer using PowerShell with UTF-8 encoding
command = `powershell -Command "& {[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-Content -Path '${tempFile}' -Encoding UTF8 | Out-Printer -Name '${printer}'}"`;
} else {
// Print to default printer using simple print command
command = `print "${tempFile}"`;
}
exec(command, (error, stdout, stderr) => {
// Clean up temp file
fs.unlink(tempFile, () => {});
if (error) {
reject(new Error(`Windows print command failed: ${error.message}`));
return;
}
const jobId = `win_job_${Date.now()}`;
resolve({
jobId,
output: stdout.trim(),
message: `Printed to ${printer || 'default printer'}`,
});
});
});
});
}
// Print using system CUPS (macOS/Linux)
async printWithCUPS(content, printerName = null) {
return new Promise((resolve, reject) => {
const tempFile = path.join(this.tempDir, `print_${Date.now()}.txt`);
// Convert content to proper encoding for CUPS printing
let encodedContent;
try {
if (this.containsNepaliText(content)) {
// For Nepali text, ensure UTF-8 encoding
encodedContent = iconv.encode(content, 'utf8');
} else {
encodedContent = iconv.encode(content, this.encoding);
}
} catch (error) {
encodedContent = content; // fallback to original content
}
// Write content to temporary file with proper encoding
fs.writeFile(tempFile, encodedContent, (writeErr) => {
if (writeErr) {
reject(new Error(`Failed to write temp file: ${writeErr.message}`));
return;
}
const printer = printerName || this.defaultPrinter;
// Add charset option for better Unicode support
const command = printer
? `lp -d "${printer}" -o cpi=10 -o lpi=6 "${tempFile}"`
: `lp -o cpi=10 -o lpi=6 "${tempFile}"`;
exec(command, (error, stdout, stderr) => {
// Clean up temp file
fs.unlink(tempFile, () => {});
if (error) {
reject(new Error(`CUPS print command failed: ${error.message}`));
return;
}
// Extract job ID from output
const jobMatch = stdout.match(/request id is (\S+)/);
const jobId = jobMatch ? jobMatch[1] : `cups_job_${Date.now()}`;
resolve({ jobId, output: stdout.trim() });
});
});
});
}
// Print to file (for testing/debugging)
async printToFile(content, filename = null) {
return new Promise((resolve, reject) => {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const outputFile =
filename || path.join(this.tempDir, `print_output_${timestamp}.txt`);
// Convert content to proper encoding
let encodedContent;
try {
if (this.containsNepaliText(content)) {
// For Nepali text, use UTF-8 with BOM for better compatibility
encodedContent = iconv.encode(content, 'utf8', { addBOM: true });
} else {
encodedContent = iconv.encode(content, this.encoding);
}
} catch (error) {
encodedContent = content; // fallback to original content
}
fs.writeFile(outputFile, encodedContent, (error) => {
if (error) {
reject(new Error(`Failed to write to file: ${error.message}`));
return;
}
resolve({
jobId: `file_${Date.now()}`,
filePath: outputFile,
message: `Content saved to ${outputFile}`,
});
});
});
}
// Mock print (for development)
async mockPrint(content, printerName = null) {
return new Promise((resolve) => {
console.log('=== CUSTOM PRINTER START ===');
console.log(
`Printer: ${printerName || this.defaultPrinter || 'Default Printer'}`
);
console.log(`Encoding: ${this.encoding}`);
console.log(`Content:\n${content}`);
console.log('=== CUSTOM PRINTER END ===');
setTimeout(() => {
resolve({
jobId: `mock_${Date.now()}`,
message: 'Mock print completed successfully',
});
}, 100);
});
}
// Main print method with automatic fallback
async print(content, options = {}) {
const {
printer = null,
method = 'auto', // 'auto', 'windows', 'cups', 'file', 'mock'
filename = null,
} = options;
try {
switch (method) {
case 'windows':
return await this.printWithWindows(content, printer);
case 'cups':
return await this.printWithCUPS(content, printer);
case 'file':
return await this.printToFile(content, filename);
case 'mock':
return await this.mockPrint(content, printer);
case 'auto':
default:
// Auto-detect platform and use appropriate method
if (this.platform === 'win32') {
try {
const printers = await this.getAvailablePrinters();
if (printers.length > 0) {
return await this.printWithWindows(content, printer);
} else {
console.warn('No Windows printers found, using mock print');
return await this.mockPrint(content, printer);
}
} catch (winError) {
console.warn(
'Windows printing failed, using mock:',
winError.message
);
return await this.mockPrint(content, printer);
}
} else {
// macOS/Linux - use CUPS
try {
const printers = await this.getAvailablePrinters();
if (printers.length > 0) {
return await this.printWithCUPS(content, printer);
} else {
console.warn('No CUPS printers found, using mock print');
return await this.mockPrint(content, printer);
}
} catch (cupsError) {
console.warn(
'CUPS printing failed, using mock:',
cupsError.message
);
return await this.mockPrint(content, printer);
}
}
}
} catch (error) {
throw new Error(`Print operation failed: ${error.message}`);
}
}
}
module.exports = CustomPrinter;