I’m working on converting an existing CodeIgniter-based web pawning management system into a desktop app using Electron. My requirements are:
- PDF Preview: When printing, the user should see a preview of the
PDF, but should NOT be able to save or download it.
- Silent Printing: The pawn ticket (PDF) should be printed directly to
the default physical printer, with no print dialog shown.
- Block Virtual Printers: Virtual printers (like Microsoft Print to
PDF, XPS, OneNote, etc.) must be blocked—only real/physical printers
should be selectable.
What I’ve tried:
- I can print HTML content silently using Electron (my test print
works).
- The actual ticket data comes as a PDF generated by CodeIgniter (using
TCPDF).
- When I try to print the PDF silently, nothing is printed, and I see
errors like Printer settings invalid … content size is empty.
I have code to filter out virtual printers, but the main issue is reliably printing the PDF silently and showing a preview without allowing save/download.
Questions:
- How can I show a PDF preview in Electron but prevent the user from
saving/downloading the file?
- What’s the best way to print a PDF silently to a physical printer in
Electron (or another desktop framework), especially when the PDF is
generated by a web backend?
- How can I ensure only physical printers are used (block all virtual
printers) in the print dialog or silent print?
Any code samples, libraries, or architectural suggestions are appreciated!
The backend is CodeIgniter, generating PDFs with TCPDF.
I’m open to using other frameworks if Electron can’t do this reliably.
Start of main.js
const { app, BrowserWindow, ipcMain, shell } =
require('electron');
const path = require('path');
const fs = require('fs');
const os = require('os');
const { net } = require('electron');
const DEBUG_MODE = true; // Set to false in production
let mainWindow;
// Create the main application window
function createWindow() {
mainWindow = new BrowserWindow({
width: 1280,
height: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
sandbox: false,
preload: path.join(__dirname, 'preload.js')
},
autoHideMenuBar: true
});
// Load your web application
mainWindow.loadURL('http://192.168.1.17/pawn/pawn_sew/');
// Open external links in default browser
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
if (!url.startsWith('http://192.168.1.17')) {
shell.openExternal(url);
return { action: 'deny' };
}
return { action: 'allow' };
});
}
// Main app initialization
app.whenReady().then(() => {
createWindow();
// FIXED: Using correct API to get printers in newer Electron
versions
setTimeout(() => {
try {
// In newer versions of Electron, getPrinters is on
webContents.getPrintersAsync()
// or mainWindow.webContents.print.getPrinterList() depending
on version
if (mainWindow.webContents.getPrinters) {
const printers = mainWindow.webContents.getPrinters();
logPrinters(printers);
} else if (mainWindow.webContents.getPrintersAsync) {
mainWindow.webContents.getPrintersAsync().then(logPrinters);
} else if (mainWindow.webContents.print &&
mainWindow.webContents.print.getPrinterList) {
mainWindow.webContents.print.getPrinterList().then(logPrinters);
}
} catch (err) {
console.error('Failed to get printers:', err);
}
}, 2000); // Wait for window to be ready
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
Codes related to printer selection
// Update the logPrinters function to show virtual printer status
function logPrinters(printers) {
console.log('Available printers:');
printers.forEach((printer, index) => {
const isVirtual = isVirtualPrinter(printer.name);
console.log(
`${index + 1}. ${printer.name} (Default: ${printer.isDefault}, Virtual: ${isVirtual ? 'Yes' : 'No'})`
);
});
}
// Add this function to identify virtual printers
function isVirtualPrinter(printerName) {
// Common virtual printer names (case-insensitive)
const virtualPrinterPatterns = [
/pdf/i,
/microsoft/i,
/adobe/i,
/xps/i,
/document writer/i,
/onedrive/i,
/fax/i,
/virtual/i,
/print to file/i,
/cloud/i,
/scan/i,
/onenote/i, // Added for OneNote
/anydesk/i, // Added for AnyDesk
/remote/i, // Common for remote printing solutions
/universal/i, // Often used in virtual print drivers
/bullzip/i, // Another common PDF printer
/cutepdf/i, // Common PDF printer
/foxit/i, // Foxit PDF printer
/doro/i // DoroEasy PDF
];
return virtualPrinterPatterns.some(pattern => pattern.test(printerName));
}
// Add this helper function to determine if a printer is suitable for pawn tickets
function isSuitablePrinter(printerName) {
const lowerName = printerName.toLowerCase();
// Skip POS/thermal printers that aren't suitable for pawn tickets
if (lowerName.includes('pos') ||
lowerName.includes('thermal') ||
lowerName.includes('receipt') ||
lowerName.includes('dot matrix')) {
console.log(`Skipping printer ${printerName} as it appears to be a thermal/POS printer`);
return false;
}
// Include standard printers
return true;
}
// Quit when all windows are closed (except on macOS)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
Pdf print related code (I used Chatgpt)
async function printWithWindows(pdfPath, printerName) {
return new Promise((resolve, reject) => {
const { exec } = require('child_process');
// Use the Windows 'print' command (works for some printers, not all)
// Note: This is a basic fallback and may not support all PDF features
const command = `PRINT /D:"${printerName}" "${pdfPath}"`;
exec(command, (error, stdout, stderr) => {
if (error) {
console.error(`Direct print error: ${error.message}`);
reject(error);
} else {
console.log('Direct Windows print completed successfully');
resolve(true);
}
});
});
}
// Update the silent-print-ticket handler
ipcMain.handle('silent-print-ticket', async (event, ticketUrl) => {
console.log('Received print request for:', ticketUrl);
const DEBUG_MODE = true; // Set to false in production
let tempPdfPath;
let printWindow;
try {
// 1. Download PDF
tempPdfPath = await downloadPdf(ticketUrl);
// Verify file exists before printing
if (!fs.existsSync(tempPdfPath)) {
throw new Error(`PDF file does not exist at ${tempPdfPath}`);
}
const fileStats = fs.statSync(tempPdfPath);
console.log(`PDF file size: ${fileStats.size} bytes`);
// 2. Create a window with proper PDF display capabilities
printWindow = new BrowserWindow({
width: 800,
height: 1100,
show: DEBUG_MODE,
webPreferences: {
plugins: true
}
});
if (DEBUG_MODE) {
printWindow.webContents.openDevTools();
}
// 3. Load the PDF directly
const pdfUrl = `file://${tempPdfPath.replace(/\/g, '/')}`;
console.log(`Loading PDF from: ${pdfUrl}`);
await printWindow.loadURL(pdfUrl);
// Add event listeners to capture PDF loading issues
printWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription) => {
console.error(`Failed to load PDF: ${errorDescription} (${errorCode})`);
});
// 4. Give PDF time to render properly
await new Promise(resolve => setTimeout(resolve, 2000));
console.log('PDF loaded, preparing to print');
// 5. Get available printers
let printers = [];
try {
if (mainWindow.webContents.getPrinters) {
printers = mainWindow.webContents.getPrinters();
} else if (mainWindow.webContents.getPrintersAsync) {
printers = await mainWindow.webContents.getPrintersAsync();
} else if (mainWindow.webContents.print && mainWindow.webContents.print.getPrinterList) {
printers = await mainWindow.webContents.print.getPrinterList();
}
} catch (err) {
console.error('Failed to get printer list:', err);
throw new Error('Could not find any printers. Please check your printer connection and try again.');
}
// 6. Filter out virtual printers and thermal printers
const realPrinters = printers.filter(printer =>
!isVirtualPrinter(printer.name) && isSuitablePrinter(printer.name)
);
console.log('Suitable printers for pawn tickets:', realPrinters.map(p => p.name));
if (realPrinters.length === 0) {
throw new Error('No suitable printers found for pawn tickets. Please connect a standard printer.');
}
// 7. Sort printers prioritizing default printers
const sortedPrinters = [...realPrinters].sort((a, b) => {
return a.isDefault ? -1 : (b.isDefault ? 1 : 0);
});
console.log('Prioritized printer order:', sortedPrinters.map(p => p.name));
// 8. Try each printer until one succeeds - with standard Electron printing
let lastError = null;
for (const printer of sortedPrinters) {
try {
console.log(`Attempting to print to ${printer.name}...`);
// Check network printer access
if (printer.name.startsWith('\')) {
const hasAccess = await checkPrinterAccess(printer.name);
if (!hasAccess) {
console.log(`No access to network printer: ${printer.name}, skipping...`);
continue;
}
}
// Try standard Electron print with A4 half portrait settings
const printResult = await printWindow.webContents.print({
silent: true,
printBackground: true,
deviceName: printer.name,
pageSize: {
width: 210000, // A4 width in microns
height: 148500 // A4 height / 2 in microns
},
landscape: false,
margins: {
marginType: 'custom',
top: 5000,
bottom: 5000,
left: 5000,
right: 5000
},
dpi: {
horizontal: 300,
vertical: 300
}
});
if (printResult) {
console.log(`Successfully printed to ${printer.name}`);
if (printWindow && !printWindow.isDestroyed()) {
printWindow.close();
}
fs.unlink(tempPdfPath, () => {});
return {
success: true,
printerName: printer.name,
message: `Document successfully sent to printer "${printer.name}".`
};
} else {
console.log(`Print to ${printer.name} returned false`);
throw new Error('Printer returned unsuccessful status');
}
} catch (printerError) {
console.error(`Failed to print to ${printer.name}:`, printerError.message);
lastError = printerError;
// Continue to next printer
}
}
// 9. If Electron printing failed for all printers, try Windows direct printing
console.log("All Electron print attempts failed. Trying Windows direct printing...");
for (const printer of sortedPrinters) {
try {
console.log(`Attempting Windows direct print to ${printer.name}...`);
await printWithWindows(tempPdfPath, printer.name);
console.log(`Successfully printed to ${printer.name} using Windows direct printing`);
if (printWindow && !printWindow.isDestroyed()) {
printWindow.close();
}
return {
success: true,
printerName: printer.name,
message: `Document successfully printed to "${printer.name}" using Windows direct printing.`
};
} catch (winError) {
console.error(`Windows direct print to ${printer.name} failed:`, winError.message);
// Continue to next printer
}
}
// 10. Last resort: Show print dialog
if (DEBUG_MODE) {
console.log("All automatic print attempts failed. Trying with system dialog...");
const dialogResult = await printWindow.webContents.print();
if (dialogResult) {
console.log("Print succeeded with system dialog");
if (printWindow && !printWindow.isDestroyed()) {
printWindow.close();
}
fs.unlink(tempPdfPath, () => {});
return {
success: true,
message: "Document printed successfully using system dialog."
};
}
}
// If we get here, all methods failed
throw new Error('All print methods failed. Please check your printer connections and try again.');
} catch (error) {
console.error('Print error:', error.message);
if (printWindow && !printWindow.isDestroyed()) {
printWindow.close();
}
if (tempPdfPath) {
fs.unlink(tempPdfPath, () => {});
}
return {
success: false,
error: error.message,
userFriendlyMessage: `Printing failed: ${error.message}. Please check your printer connections and try again.`,
retryable: true
};
}
});
And this is the console outputs
Available printers:
- OneNote for Windows 10 (Default: false, Virtual: Yes)
- POS-80 (Default: false, Virtual: No)
- Microsoft XPS Document Writer (Default: false, Virtual: Yes)
- Microsoft Print to PDF (Default: false, Virtual: Yes)
- Fax (Default: false, Virtual: Yes)
- AnyDesk Printer (Default: false, Virtual: Yes)
- accounts-pcHP Ink Tank 310 series (Copy 1) (Default: true, Virtual: No) Received print request for:
http://192.168.1.17/pawn/pawn_sew/swe_ticket.pdf PDF downloaded
successfully (8038 bytes) PDF file size: 8038 bytes Loading PDF from:
file://C:/Users/ADMINI~1/AppData/Local/Temp/ticket_1745819614508.pdf
[10416:0428/112335.468:ERROR:CONSOLE(1)] “Request Autofill.enable
failed. {“code”:-32601,”message”:”‘Autofill.enable’ wasn’t found”}”,
source:
devtools://devtools/bundled/core/protocol_client/protocol_client.js
(1) [10416:0428/112335.468:ERROR:CONSOLE(1)] “Request
Autofill.setAddresses failed.
{“code”:-32601,”message”:”‘Autofill.setAddresses’ wasn’t found”}”,
source:
devtools://devtools/bundled/core/protocol_client/protocol_client.js
(1) PDF loaded, preparing to print Skipping printer POS-80 as it
appears to be a thermal/POS printer Suitable printers for pawn
tickets: [ ‘\accounts-pcHP Ink Tank 310 series (Copy 1)’ ]
Prioritized printer order: [ ‘\accounts-pcHP Ink Tank 310 series
(Copy 1)’ ] Attempting to print to accounts-pcHP Ink Tank 310
series (Copy 1)… Checking access to network path: accounts-pc
Successfully accessed network path: accounts-pc Print to
accounts-pcHP Ink Tank 310 series (Copy 1) returned false Failed to
print to accounts-pcHP Ink Tank 310 series (Copy 1): Printer
returned unsuccessful statused unsuccessful status All Electron print
attempts failed. Trying Windows direct printing… …
Attempting Windows direct print to accounts-pcHP Ink Tank 310
series (Copy 1)…
ows direct printing Direct Windows print completed successfully
er: print_view_manager_base.cc:384 Printer settings invalid for
accounts-pcH Successfully printed to accounts-pcHP Ink Tank 310
series (Copy 1) using Windows direct printing
[10416:0428/112337.075:ERROR:device_event_log_impl.cc(202)]
[11:23:37.076] Printer: print_view_manager_base.cc:384 Printer
settings invalid for accounts-pcHP Ink Tank 310 series (Copy 1)
(destination type kLocal): content size is empty PS D:Softmaster
ProjectsPawningpawn-electron-app>