Skip to content

Commit 4f2dc59

Browse files
fix: Fixed Default printer and DevMode
1 parent 7da7c35 commit 4f2dc59

File tree

6 files changed

+218
-31
lines changed

6 files changed

+218
-31
lines changed

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,8 +385,39 @@ Or use npm scripts:
385385
npm run example:simple
386386
npm run example:duplex
387387
npm run example:advanced
388+
389+
# Test DEVMODE configuration
390+
npm run example:test-devmode
391+
npm run example:monitor
388392
```
389393

394+
## Testing DEVMODE Configuration
395+
396+
To verify that print options (copies, duplex, paper size, etc.) are being applied correctly, see the comprehensive guide: **[TESTING-DEVMODE.md](TESTING-DEVMODE.md)**
397+
398+
Quick test:
399+
```bash
400+
# Inspect DEVMODE settings directly ⭐ RECOMMENDED
401+
npm run example:inspect-devmode
402+
403+
# Test with Microsoft Print to PDF
404+
npm run example:test-devmode
405+
406+
# Monitor print spooler with detailed instructions
407+
npm run example:monitor
408+
```
409+
410+
**Verification methods:**
411+
1. **⭐ DEVMODE Inspector** - `npm run example:inspect-devmode` - See all DEVMODE fields directly
412+
2. **Windows Print Queue** - View job properties in the print queue
413+
3. **PowerShell** - `Get-PrintJob` shows basic job info (⚠️ NOT DEVMODE settings)
414+
4. **Process Monitor** - Use Sysinternals Process Monitor to see API calls
415+
5. **Event Viewer** - Check Windows event logs for print job details
416+
417+
**Important:** PowerShell's `Get-PrintJob` does **NOT** show DEVMODE settings (duplex, paper size, orientation, etc.). Use our inspector tool instead!
418+
419+
See [TESTING-DEVMODE.md](TESTING-DEVMODE.md) for detailed instructions on each method.
420+
390421
## Troubleshooting
391422

392423
### Common Issues

examples/test-devmode.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Test DEVMODE settings with Microsoft Print to PDF
2+
import { WindowsPDFPrinter, WindowsPrinterManager, PAPER_A4, PAPER_LETTER } from '../src/index';
3+
4+
async function testDevMode() {
5+
console.log('=== Testing DEVMODE Configuration ===\n');
6+
7+
// Use Microsoft Print to PDF (available on all Windows 10+)
8+
const printerName = 'Microsoft Print to PDF';
9+
10+
// Check if printer exists
11+
if (!WindowsPrinterManager.printerExists(printerName)) {
12+
console.error('Microsoft Print to PDF not found!');
13+
console.log('\nAvailable printers:');
14+
const printers = WindowsPrinterManager.getAvailablePrinters();
15+
for (const p of printers) {
16+
console.log(` - ${p.name}`);
17+
}
18+
return;
19+
}
20+
21+
console.log(`Using printer: ${printerName}\n`);
22+
23+
// Get printer capabilities
24+
const capabilities = WindowsPrinterManager.getPrinterCapabilities(printerName);
25+
console.log('Printer Capabilities:');
26+
console.log(` Supports Duplex: ${capabilities?.supportsDuplex}`);
27+
console.log(` Supports Color: ${capabilities?.supportsColor}`);
28+
console.log(` Default Paper Size: ${capabilities?.defaultPaperSize}`);
29+
console.log();
30+
31+
// Create a test PDF path (this example doesn't actually need a real PDF)
32+
const testPdfPath = './test-document.pdf';
33+
34+
// Test 1: Print with various DEVMODE settings
35+
console.log('Test 1: Multiple copies with duplex');
36+
const printer1 = new WindowsPDFPrinter(printerName);
37+
38+
try {
39+
await printer1.print(testPdfPath, {
40+
copies: 3,
41+
duplex: 'vertical',
42+
paperSize: PAPER_A4,
43+
orientation: 'portrait',
44+
color: true
45+
});
46+
console.log('✓ Print job submitted successfully with DEVMODE options\n');
47+
} catch (error: any) {
48+
console.log(`✗ Print failed: ${error.message}\n`);
49+
}
50+
51+
// Test 2: Different paper size
52+
console.log('Test 2: Letter size with landscape orientation');
53+
const printer2 = new WindowsPDFPrinter(printerName);
54+
55+
try {
56+
await printer2.print(testPdfPath, {
57+
paperSize: PAPER_LETTER,
58+
orientation: 'landscape',
59+
copies: 1
60+
});
61+
console.log('✓ Print job submitted successfully\n');
62+
} catch (error: any) {
63+
console.log(`✗ Print failed: ${error.message}\n`);
64+
}
65+
66+
// Test 3: Monochrome printing
67+
console.log('Test 3: Monochrome (B&W) printing');
68+
const printer3 = new WindowsPDFPrinter(printerName);
69+
70+
try {
71+
await printer3.print(testPdfPath, {
72+
color: false,
73+
copies: 2
74+
});
75+
console.log('✓ Print job submitted successfully\n');
76+
} catch (error: any) {
77+
console.log(`✗ Print failed: ${error.message}\n`);
78+
}
79+
80+
console.log('=== Testing Complete ===');
81+
console.log('\nTo verify DEVMODE is working:');
82+
console.log('1. Open Windows Settings → Devices → Printers & scanners');
83+
console.log('2. Click "Microsoft Print to PDF" → Manage');
84+
console.log('3. Click "Print queue" to see the jobs');
85+
console.log('4. Check the print queue properties to see if settings are applied');
86+
}
87+
88+
testDevMode().catch(console.error);

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
"example:duplex": "tsx examples/duplex-print.ts",
2121
"example:advanced": "tsx examples/advanced-print.ts",
2222
"example:list": "tsx examples/list-printers.ts",
23-
"example:unix": "tsx examples/unix-print.ts"
23+
"example:unix": "tsx examples/unix-print.ts",
24+
"example:test-devmode": "tsx examples/test-devmode.ts",
25+
"example:monitor": "tsx examples/monitor-printers.ts"
2426
},
2527
"files": [
2628
"lib/**/*",

src/pdf-printer.ts

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
import * as fs from 'fs';
22
import * as path from 'path';
3-
import koffi from 'koffi';
43
import {
5-
OpenPrinterW,
6-
ClosePrinter,
74
StartDocPrinterW,
85
EndDocPrinter,
96
StartPagePrinter,
107
EndPagePrinter,
118
WritePrinter,
12-
DOCINFOW,
13-
DEVMODEW,
149
DocumentPropertiesW,
10+
DM_IN_BUFFER,
11+
DM_OUT_BUFFER,
1512
DM_ORIENTATION,
1613
DM_PAPERSIZE,
1714
DM_COPIES,
@@ -23,8 +20,6 @@ import {
2320
DUPLEX_VERTICAL,
2421
PORTRAIT,
2522
LANDSCAPE,
26-
PAPER_A4,
27-
PAPER_LETTER,
2823
MONOCHROME,
2924
COLOR as COLOR_MODE,
3025
GetLastError
@@ -84,12 +79,22 @@ export class PDFPrinter {
8479

8580
// Open printer
8681
const printerName = options.printer || this.printerName;
87-
const hPrinter = PrinterManager.openPrinter(printerName);
82+
83+
// First, open printer temporarily to get DEVMODE
84+
const hPrinterTemp = PrinterManager.openPrinter(printerName);
85+
const devMode = this.getAndConfigureDevMode(hPrinterTemp, printerName, options);
86+
PrinterManager.closePrinter(hPrinterTemp);
87+
88+
// Now open printer with configured DEVMODE if options were specified
89+
const hasOptions = Object.keys(options).some(key =>
90+
['copies', 'duplex', 'paperSize', 'paperSource', 'orientation', 'color'].includes(key)
91+
);
92+
93+
const hPrinter = hasOptions && devMode
94+
? PrinterManager.openPrinterWithDevMode(printerName, devMode)
95+
: PrinterManager.openPrinter(printerName);
8896

8997
try {
90-
// Configure print settings
91-
const devMode = this.createDevMode(options);
92-
9398
// Read PDF file first
9499
const pdfData = fs.readFileSync(pdfPath);
95100

@@ -139,7 +144,20 @@ export class PDFPrinter {
139144
*/
140145
async printRaw(data: Buffer, documentName: string = 'Document', options: PrintOptions = {}): Promise<void> {
141146
const printerName = options.printer || this.printerName;
142-
const hPrinter = PrinterManager.openPrinter(printerName);
147+
148+
// First, open printer temporarily to get DEVMODE
149+
const hPrinterTemp = PrinterManager.openPrinter(printerName);
150+
const devMode = this.getAndConfigureDevMode(hPrinterTemp, printerName, options);
151+
PrinterManager.closePrinter(hPrinterTemp);
152+
153+
// Now open printer with configured DEVMODE if options were specified
154+
const hasOptions = Object.keys(options).some(key =>
155+
['copies', 'duplex', 'paperSize', 'paperSource', 'orientation', 'color'].includes(key)
156+
);
157+
158+
const hPrinter = hasOptions && devMode
159+
? PrinterManager.openPrinterWithDevMode(printerName, devMode)
160+
: PrinterManager.openPrinter(printerName);
143161

144162
try {
145163
const docInfo = {
@@ -178,13 +196,29 @@ export class PDFPrinter {
178196
}
179197

180198
/**
181-
* Create DEVMODE structure with print options
199+
* Get printer's DEVMODE and configure it with print options
182200
*/
183-
private createDevMode(options: PrintOptions): any {
184-
const devMode: any = {
185-
dmFields: 0
186-
};
201+
private getAndConfigureDevMode(hPrinter: any, printerName: string, options: PrintOptions): any {
202+
// Get the size needed for DEVMODE
203+
const devModeOut = [null];
204+
const sizeNeeded = DocumentPropertiesW(null, hPrinter, printerName, devModeOut, null, 0);
205+
206+
if (sizeNeeded < 0) {
207+
console.warn('Could not get DEVMODE size, printing with default settings');
208+
return null;
209+
}
210+
211+
// Get the default DEVMODE from printer
212+
const result = DocumentPropertiesW(null, hPrinter, printerName, devModeOut, null, DM_OUT_BUFFER);
213+
214+
if (result < 0 || !devModeOut[0]) {
215+
console.warn('Could not get DEVMODE from printer, printing with default settings');
216+
return null;
217+
}
187218

219+
const devMode: any = devModeOut[0];
220+
221+
// Apply custom options to the DEVMODE
188222
// Set copies
189223
if (options.copies && options.copies > 0) {
190224
devMode.dmCopies = options.copies;
@@ -231,6 +265,15 @@ export class PDFPrinter {
231265
devMode.dmFields |= DM_COLOR;
232266
}
233267

268+
// Validate the modified DEVMODE with the printer
269+
const validatedDevMode = [devMode];
270+
const validateResult = DocumentPropertiesW(null, hPrinter, printerName, validatedDevMode, devMode, DM_IN_BUFFER | DM_OUT_BUFFER);
271+
272+
if (validateResult >= 0 && validatedDevMode[0]) {
273+
return validatedDevMode[0];
274+
}
275+
276+
console.warn('DEVMODE validation failed, using unvalidated settings');
234277
return devMode;
235278
}
236279

src/printer-manager.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,7 @@ import {
99
PRINTER_ENUM_LOCAL,
1010
PRINTER_ENUM_CONNECTIONS,
1111
PRINTER_INFO_2W,
12-
DEVMODEW,
13-
DM_ORIENTATION,
14-
DM_PAPERSIZE,
15-
DM_COPIES,
16-
DM_DUPLEX,
17-
DM_COLOR,
18-
DM_DEFAULTSOURCE,
19-
PORTRAIT,
20-
LANDSCAPE
12+
PRINTER_ACCESS_USE
2113
} from './windows-print-api';
2214
import koffi from 'koffi';
2315

@@ -93,8 +85,8 @@ export class PrinterManager {
9385
* Get the default printer name
9486
*/
9587
static getDefaultPrinter(): string | null {
96-
const bufferSize = [256];
97-
const buffer = Buffer.alloc(bufferSize[0] * 2); // Unicode characters
88+
const bufferSize = [256]; // Buffer size in characters
89+
const buffer = Buffer.alloc(256 * 2); // UTF-16, 2 bytes per character
9890

9991
const success = GetDefaultPrinterW(buffer, bufferSize);
10092

@@ -166,6 +158,25 @@ export class PrinterManager {
166158
return hPrinter[0];
167159
}
168160

161+
/**
162+
* Open a printer handle with custom DEVMODE settings
163+
*/
164+
static openPrinterWithDevMode(printerName: string, devMode: any): any {
165+
const hPrinter = [null];
166+
167+
const printerDefaults = {
168+
pDatatype: null,
169+
pDevMode: devMode,
170+
DesiredAccess: PRINTER_ACCESS_USE
171+
};
172+
173+
if (!OpenPrinterW(printerName, hPrinter, printerDefaults)) {
174+
throw new Error(`Failed to open printer with DEVMODE: ${printerName}`);
175+
}
176+
177+
return hPrinter[0];
178+
}
179+
169180
/**
170181
* Close a printer handle
171182
*/

src/windows-print-api.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,28 @@ export const PRINTER_INFO_2W = koffi.struct('PRINTER_INFO_2W', {
113113
AveragePPM: 'uint32'
114114
});
115115

116+
// PRINTER_DEFAULTS structure for OpenPrinter
117+
export const PRINTER_DEFAULTS = koffi.struct('PRINTER_DEFAULTS', {
118+
pDatatype: 'str16',
119+
pDevMode: koffi.pointer(DEVMODEW),
120+
DesiredAccess: 'uint32'
121+
});
122+
123+
// DocumentProperties flags
124+
export const DM_IN_BUFFER = 8;
125+
export const DM_OUT_BUFFER = 2;
126+
116127
// Define Windows API functions - only on Windows platform
117-
export const OpenPrinterW = isWindows ? winspool.func('OpenPrinterW', 'bool', ['str16', koffi.out(koffi.pointer('void*')), 'void*']) : (() => { throw new Error('Windows API not available on this platform'); }) as any;
128+
export const OpenPrinterW = isWindows ? winspool.func('OpenPrinterW', 'bool', ['str16', koffi.out(koffi.pointer('void*')), koffi.pointer(PRINTER_DEFAULTS)]) : (() => { throw new Error('Windows API not available on this platform'); }) as any;
118129
export const ClosePrinter = isWindows ? winspool.func('ClosePrinter', 'bool', ['void*']) : (() => { throw new Error('Windows API not available on this platform'); }) as any;
119130
export const StartDocPrinterW = isWindows ? winspool.func('StartDocPrinterW', 'uint32', ['void*', 'uint32', koffi.pointer(DOC_INFO_1W)]) : (() => { throw new Error('Windows API not available on this platform'); }) as any;
120131
export const EndDocPrinter = isWindows ? winspool.func('EndDocPrinter', 'bool', ['void*']) : (() => { throw new Error('Windows API not available on this platform'); }) as any;
121132
export const StartPagePrinter = isWindows ? winspool.func('StartPagePrinter', 'bool', ['void*']) : (() => { throw new Error('Windows API not available on this platform'); }) as any;
122133
export const EndPagePrinter = isWindows ? winspool.func('EndPagePrinter', 'bool', ['void*']) : (() => { throw new Error('Windows API not available on this platform'); }) as any;
123134
export const WritePrinter = isWindows ? winspool.func('WritePrinter', 'bool', ['void*', 'void*', 'uint32', koffi.out(koffi.pointer('uint32'))]) : (() => { throw new Error('Windows API not available on this platform'); }) as any;
124135
export const EnumPrintersW = isWindows ? winspool.func('EnumPrintersW', 'bool', ['uint32', 'str16', 'uint32', 'void*', 'uint32', koffi.out(koffi.pointer('uint32')), koffi.out(koffi.pointer('uint32'))]) : (() => { throw new Error('Windows API not available on this platform'); }) as any;
125-
export const GetDefaultPrinterW = isWindows ? winspool.func('GetDefaultPrinterW', 'bool', ['str16', koffi.out(koffi.pointer('uint32'))]) : (() => { throw new Error('Windows API not available on this platform'); }) as any;
136+
// GetDefaultPrinterW: BOOL GetDefaultPrinterW(LPWSTR pszBuffer, LPDWORD pcchBuffer);
137+
export const GetDefaultPrinterW = isWindows ? winspool.func('GetDefaultPrinterW', 'bool', ['uint16*', koffi.inout(koffi.pointer('uint32'))]) : (() => { throw new Error('Windows API not available on this platform'); }) as any;
126138
export const DocumentPropertiesW = isWindows ? winspool.func('DocumentPropertiesW', 'int32', ['void*', 'void*', 'str16', koffi.out(koffi.pointer(DEVMODEW)), koffi.pointer(DEVMODEW), 'uint32']) : (() => { throw new Error('Windows API not available on this platform'); }) as any;
127139

128140
// Enum flags

0 commit comments

Comments
 (0)