1//========================================================================
2//
3// This file is licensed under the GPLv2 or later
4//
5// Copyright (C) 2014 Rodrigo Rivas Costa <rodrigorivascosta@gmail.com>
6// Copyright (C) 2014, 2017 Adrian Johnson <ajohnson@redneon.com>
7// Copyright (C) 2017, 2018, 2022 Albert Astals Cid <aacid@kde.org>
8//
9// To see a description of the changes please see the Changelog file that
10// came with your tarball or type make ChangeLog if you are building from git
11//
12//========================================================================
13
14#include <cairo.h>
15#ifdef CAIRO_HAS_WIN32_SURFACE
16
17# include <cairo-win32.h>
18
19# include "parseargs.h"
20# include "pdftocairo-win32.h"
21# include "Win32Console.h"
22
23# include <dlgs.h>
24# include <commctrl.h>
25# include <commdlg.h>
26# include <windowsx.h>
27# include <winspool.h>
28
29static HDC hdc;
30static HGLOBAL hDevmode = nullptr;
31static HGLOBAL hDevnames = nullptr;
32static DEVMODEA *devmode;
33static char *printerName;
34
35struct Win32Option
36{
37 const char *name;
38 int value;
39};
40
41static const Win32Option win32PaperSource[] = { { "upper", DMBIN_UPPER }, { "onlyone", DMBIN_ONLYONE },
42 { "lower", DMBIN_LOWER }, { "middle", DMBIN_MIDDLE },
43 { "manual", DMBIN_MANUAL }, { "envelope", DMBIN_ENVELOPE },
44 { "envmanual", DMBIN_ENVMANUAL }, { "auto", DMBIN_AUTO },
45 { "tractor", DMBIN_TRACTOR }, { "smallfmt", DMBIN_SMALLFMT },
46 { "largefmt", DMBIN_LARGEFMT }, { "largecapacity", DMBIN_LARGECAPACITY },
47 { "formsource", DMBIN_FORMSOURCE }, { nullptr, 0 } };
48
49static void parseSource(GooString *source)
50{
51 const Win32Option *option = win32PaperSource;
52 while (option->name) {
53 if (source->cmp(option->name) == 0) {
54 devmode->dmDefaultSource = option->value;
55 devmode->dmFields |= DM_DEFAULTSOURCE;
56 return;
57 }
58 option++;
59 }
60 fprintf(stderr, "Warning: Unknown paper source \"%s\"\n", source->c_str());
61}
62
63static const Win32Option win32DuplexMode[] = { { "off", DMDUP_SIMPLEX }, { "short", DMDUP_HORIZONTAL }, { "long", DMDUP_VERTICAL }, { nullptr, 0 } };
64
65static void parseDuplex(GooString *mode)
66{
67 const Win32Option *option = win32DuplexMode;
68 while (option->name) {
69 if (mode->cmp(option->name) == 0) {
70 devmode->dmDuplex = option->value;
71 devmode->dmFields |= DM_DUPLEX;
72 return;
73 }
74 option++;
75 }
76 fprintf(stderr, "Warning: Unknown duplex mode \"%s\"\n", mode->c_str());
77}
78
79static void fillCommonPrinterOptions(bool duplex)
80{
81 if (duplex) {
82 devmode->dmDuplex = DMDUP_HORIZONTAL;
83 devmode->dmFields |= DM_DUPLEX;
84 }
85}
86
87static void fillPagePrinterOptions(double w, double h)
88{
89 w *= 254.0 / 72.0; // units are 0.1mm
90 h *= 254.0 / 72.0;
91 if (w > h) {
92 devmode->dmOrientation = DMORIENT_LANDSCAPE;
93 devmode->dmPaperWidth = static_cast<short>(h);
94 devmode->dmPaperLength = static_cast<short>(w);
95 } else {
96 devmode->dmOrientation = DMORIENT_PORTRAIT;
97 devmode->dmPaperWidth = static_cast<short>(w);
98 devmode->dmPaperLength = static_cast<short>(h);
99 }
100 devmode->dmPaperSize = 0;
101 devmode->dmFields |= DM_ORIENTATION | DM_PAPERWIDTH | DM_PAPERLENGTH;
102}
103
104static void fillPrinterOptions(bool duplex, GooString *printOpt)
105{
106 // printOpt format is: <opt1>=<val1>,<opt2>=<val2>,...
107 const char *nextOpt = printOpt->c_str();
108 while (nextOpt && *nextOpt) {
109 const char *comma = strchr(nextOpt, ',');
110 GooString opt;
111 if (comma) {
112 opt.Set(nextOpt, static_cast<int>(comma - nextOpt));
113 nextOpt = comma + 1;
114 } else {
115 opt.Set(nextOpt);
116 nextOpt = NULL;
117 }
118 // here opt is "<optN>=<valN> "
119 const char *equal = strchr(opt.c_str(), '=');
120 if (!equal) {
121 fprintf(stderr, "Warning: unknown printer option \"%s\"\n", opt.c_str());
122 continue;
123 }
124 const int iequal = static_cast<int>(equal - opt.c_str());
125 GooString value(&opt, iequal + 1, opt.getLength() - iequal - 1);
126 opt.del(iequal, opt.getLength() - iequal);
127 // here opt is "<optN>" and value is "<valN>"
128
129 if (opt.cmp("source") == 0) {
130 parseSource(&value);
131 } else if (opt.cmp("duplex") == 0) {
132 if (duplex)
133 fprintf(stderr, "Warning: duplex mode is specified both as standalone and printer options\n");
134 else
135 parseDuplex(&value);
136 } else {
137 fprintf(stderr, "Warning: unknown printer option \"%s\"\n", opt.c_str());
138 }
139 }
140}
141
142static void getLocalPos(HWND wind, HWND dlg, RECT *rect)
143{
144 GetClientRect(wind, rect);
145 MapWindowPoints(wind, dlg, (LPPOINT)rect, 2);
146}
147
148static HWND createGroupBox(HWND parent, HINSTANCE hInstance, HMENU id, const char *label, RECT *rect)
149{
150 HWND hwnd = CreateWindowA(WC_BUTTONA, label, WS_CHILD | WS_VISIBLE | BS_GROUPBOX, rect->left, rect->top, rect->right - rect->left, rect->bottom - rect->top, parent, id, hInstance, NULL);
151 HFONT hFont = (HFONT)SendMessage(parent, WM_GETFONT, (WPARAM)0, (LPARAM)0);
152 if (hFont)
153 SendMessage(hwnd, WM_SETFONT, (WPARAM)hFont, (LPARAM)0);
154 return hwnd;
155}
156
157static HWND createCheckBox(HWND parent, HINSTANCE hInstance, HMENU id, const char *label, RECT *rect)
158{
159 HWND hwnd = CreateWindowA(WC_BUTTONA, label, WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX | WS_TABSTOP, rect->left, rect->top, rect->right - rect->left, rect->bottom - rect->top, parent, id, hInstance, NULL);
160 HFONT hFont = (HFONT)SendMessage(parent, WM_GETFONT, (WPARAM)0, (LPARAM)0);
161 if (hFont)
162 SendMessage(hwnd, WM_SETFONT, (WPARAM)hFont, (LPARAM)0);
163 return hwnd;
164}
165
166static HWND createStaticText(HWND parent, HINSTANCE hinstance, HMENU id, const char *text, RECT *rect)
167{
168 HWND hwnd = CreateWindowA(WC_STATICA, text, WS_CHILD | WS_VISIBLE | SS_LEFT, rect->left, rect->top, rect->right - rect->left, rect->bottom - rect->top, parent, id, hinstance, NULL);
169 HFONT hFont = (HFONT)SendMessage(parent, WM_GETFONT, (WPARAM)0, (LPARAM)0);
170 if (hFont)
171 SendMessage(hwnd, WM_SETFONT, (WPARAM)hFont, (LPARAM)0);
172 return hwnd;
173}
174
175static HWND createPageScaleComboBox(HWND parent, HINSTANCE hinstance, HMENU id, RECT *rect)
176{
177 HWND hwnd = CreateWindowA(WC_COMBOBOX, "", WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP | CBS_DROPDOWNLIST, rect->left, rect->top, rect->right - rect->left, rect->bottom - rect->top, parent, id, hinstance, NULL);
178 HFONT hFont = (HFONT)SendMessage(parent, WM_GETFONT, (WPARAM)0, (LPARAM)0);
179 if (hFont)
180 SendMessage(hwnd, WM_SETFONT, (WPARAM)hFont, (LPARAM)0);
181 ComboBox_AddString(hwnd, "None");
182 ComboBox_AddString(hwnd, "Shrink to Printable Area");
183 ComboBox_AddString(hwnd, "Fit to Printable Area");
184 return hwnd;
185}
186
187enum PageScale
188{
189 NONE = 0,
190 SHRINK = 1,
191 FIT = 2
192};
193
194// used to set/get option values in printDialogHookProc
195static PageScale pageScale;
196static bool centerPage;
197static bool useOrigPageSize;
198
199// PrintDlg callback to customize the print dialog with additional controls.
200static UINT_PTR CALLBACK printDialogHookProc(HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam)
201{
202 if (uiMsg == WM_INITDIALOG) {
203 // Get the extant controls. See dlgs.h and prnsetup.dlg for the PrintDlg control ids.
204 HWND printerGroupWind = GetDlgItem(hdlg, grp4);
205 HWND printerComboWind = GetDlgItem(hdlg, cmb4);
206 HWND nameLabelWind = GetDlgItem(hdlg, stc6);
207 HWND statusLabelWind = GetDlgItem(hdlg, stc8);
208 HWND printRangeGroupWind = GetDlgItem(hdlg, grp1);
209 HWND radio1Wind = GetDlgItem(hdlg, rad1);
210 HWND radio2Wind = GetDlgItem(hdlg, rad3);
211 HWND copiesGroupWind = GetDlgItem(hdlg, grp2);
212 HWND okWind = GetDlgItem(hdlg, IDOK);
213 HWND cancelWind = GetDlgItem(hdlg, IDCANCEL);
214 if (!printerGroupWind || !printerComboWind || !nameLabelWind || !statusLabelWind || !printRangeGroupWind || !radio1Wind || !radio2Wind || !copiesGroupWind || !okWind || !cancelWind)
215 return 0;
216
217 // Get the size and position of the above controls relative to the
218 // print dialog window
219 RECT printerGroupRect;
220 RECT printerComboRect;
221 RECT nameLabelRect;
222 RECT statusLabelRect;
223 RECT printRangeGroupRect;
224 RECT radio1Rect, radio2Rect;
225 RECT copiesGroupRect;
226 RECT okRect, cancelRect;
227 getLocalPos(printerGroupWind, hdlg, &printerGroupRect);
228 getLocalPos(printerComboWind, hdlg, &printerComboRect);
229 getLocalPos(nameLabelWind, hdlg, &nameLabelRect);
230 getLocalPos(statusLabelWind, hdlg, &statusLabelRect);
231 getLocalPos(printRangeGroupWind, hdlg, &printRangeGroupRect);
232 getLocalPos(radio1Wind, hdlg, &radio1Rect);
233 getLocalPos(radio2Wind, hdlg, &radio2Rect);
234 getLocalPos(copiesGroupWind, hdlg, &copiesGroupRect);
235 getLocalPos(okWind, hdlg, &okRect);
236 getLocalPos(cancelWind, hdlg, &cancelRect);
237
238 // Calc space required for new group
239 int interGroupSpace = printRangeGroupRect.top - printerGroupRect.bottom;
240 int groupHeight = statusLabelRect.top - printerGroupRect.top + printRangeGroupRect.bottom - radio1Rect.bottom;
241
242 // Increase dialog size
243 RECT dlgRect;
244 GetWindowRect(hdlg, &dlgRect);
245 SetWindowPos(hdlg, nullptr, dlgRect.left, dlgRect.top, dlgRect.right - dlgRect.left, dlgRect.bottom - dlgRect.top + interGroupSpace + groupHeight, SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
246
247 // Add new group and controls
248 HINSTANCE hinstance = (HINSTANCE)GetWindowLongPtr(hdlg, GWLP_HINSTANCE);
249 RECT pdfGroupBoxRect;
250 pdfGroupBoxRect.left = printRangeGroupRect.left;
251 pdfGroupBoxRect.right = copiesGroupRect.right;
252 pdfGroupBoxRect.top = printRangeGroupRect.bottom + interGroupSpace;
253 pdfGroupBoxRect.bottom = pdfGroupBoxRect.top + groupHeight;
254 createGroupBox(hdlg, hinstance, (HMENU)grp3, "PDF Print Options", &pdfGroupBoxRect);
255
256 RECT textRect;
257 textRect.left = nameLabelRect.left;
258 textRect.right = static_cast<LONG>(nameLabelRect.left + 1.8 * (printerComboRect.left - nameLabelRect.left));
259 textRect.top = pdfGroupBoxRect.top + nameLabelRect.top - printerGroupRect.top;
260 textRect.bottom = textRect.top + nameLabelRect.bottom - nameLabelRect.top;
261 createStaticText(hdlg, hinstance, (HMENU)stc1, "Page Scaling:", &textRect);
262
263 RECT comboBoxRect;
264 comboBoxRect.left = textRect.right;
265 comboBoxRect.right = comboBoxRect.left + printerComboRect.right - printerComboRect.left;
266 ;
267 comboBoxRect.top = pdfGroupBoxRect.top + printerComboRect.top - printerGroupRect.top;
268 comboBoxRect.bottom = textRect.top + 4 * (printerComboRect.bottom - printerComboRect.top);
269 HWND comboBoxWind = createPageScaleComboBox(hdlg, hinstance, (HMENU)cmb1, &comboBoxRect);
270
271 RECT checkBox1Rect;
272 checkBox1Rect.left = radio1Rect.left;
273 checkBox1Rect.right = pdfGroupBoxRect.right - 10;
274 checkBox1Rect.top = pdfGroupBoxRect.top + statusLabelRect.top - printerGroupRect.top;
275 checkBox1Rect.bottom = checkBox1Rect.top + radio1Rect.bottom - radio1Rect.top;
276 HWND checkBox1Wind = createCheckBox(hdlg, hinstance, (HMENU)chx3, "Center", &checkBox1Rect);
277
278 RECT checkBox2Rect;
279 checkBox2Rect.left = radio1Rect.left;
280 checkBox2Rect.right = pdfGroupBoxRect.right - 10;
281 checkBox2Rect.top = checkBox1Rect.top + radio2Rect.top - radio1Rect.top;
282 checkBox2Rect.bottom = checkBox2Rect.top + radio1Rect.bottom - radio1Rect.top;
283 HWND checkBox2Wind = createCheckBox(hdlg, hinstance, (HMENU)chx4, "Select page size using document page size", &checkBox2Rect);
284
285 // Move OK and Cancel buttons down ensuring they are last in the Z order
286 // so that the tab order is correct.
287 SetWindowPos(okWind, HWND_BOTTOM, okRect.left, okRect.top + interGroupSpace + groupHeight, 0, 0,
288 SWP_NOSIZE); // keep current size
289 SetWindowPos(cancelWind, HWND_BOTTOM, cancelRect.left, cancelRect.top + interGroupSpace + groupHeight, 0, 0,
290 SWP_NOSIZE); // keep current size
291
292 // Initialize control values
293 ComboBox_SetCurSel(comboBoxWind, pageScale);
294 Button_SetCheck(checkBox1Wind, centerPage ? BST_CHECKED : BST_UNCHECKED);
295 Button_SetCheck(checkBox2Wind, useOrigPageSize ? BST_CHECKED : BST_UNCHECKED);
296
297 } else if (uiMsg == WM_COMMAND) {
298 // Save settings
299 UINT id = LOWORD(wParam);
300 if (id == cmb1)
301 pageScale = (PageScale)ComboBox_GetCurSel(GetDlgItem(hdlg, cmb1));
302 if (id == chx3)
303 centerPage = IsDlgButtonChecked(hdlg, chx3);
304 if (id == chx4)
305 useOrigPageSize = IsDlgButtonChecked(hdlg, chx4);
306 }
307 return 0;
308}
309
310void win32SetupPrinter(GooString *printer, GooString *printOpt, bool duplex, bool setupdlg)
311{
312 if (printer->c_str()[0] == 0) {
313 DWORD size = 0;
314 GetDefaultPrinterA(nullptr, &size);
315 printerName = (char *)gmalloc(size);
316 GetDefaultPrinterA(printerName, &size);
317 } else {
318 printerName = copyString(printer->c_str(), printer->getLength());
319 }
320
321 // Query the size of the DEVMODE struct
322 LONG szProp = DocumentPropertiesA(nullptr, nullptr, printerName, nullptr, nullptr, 0);
323 if (szProp < 0) {
324 fprintf(stderr, "Error: Printer \"%s\" not found\n", printerName);
325 exit(99);
326 }
327 devmode = (DEVMODEA *)gmalloc(szProp);
328 memset(devmode, 0, szProp);
329 devmode->dmSize = sizeof(DEVMODEA);
330 devmode->dmSpecVersion = DM_SPECVERSION;
331 // Load the current default configuration for the printer into devmode
332 if (DocumentPropertiesA(nullptr, nullptr, printerName, devmode, devmode, DM_OUT_BUFFER) < 0) {
333 fprintf(stderr, "Error: Printer \"%s\" not found\n", printerName);
334 exit(99);
335 }
336
337 // Update devmode with selected print options
338 fillCommonPrinterOptions(duplex);
339 fillPrinterOptions(duplex, printOpt);
340
341 // Call DocumentProperties again so the driver can update its private data
342 // with the modified print options. This will also display the printer
343 // properties dialog if setupdlg is true.
344 int ret;
345 DWORD mode = DM_IN_BUFFER | DM_OUT_BUFFER;
346 if (setupdlg)
347 mode |= DM_IN_PROMPT;
348 ret = DocumentPropertiesA(nullptr, nullptr, printerName, devmode, devmode, mode);
349 if (ret < 0) {
350 fprintf(stderr, "Error: Printer \"%s\" not found\n", printerName);
351 exit(99);
352 }
353 if (setupdlg && ret == IDCANCEL)
354 exit(0);
355
356 hdc = CreateDCA(nullptr, printerName, nullptr, devmode);
357 if (!hdc) {
358 fprintf(stderr, "Error: Printer \"%s\" not found\n", printerName);
359 exit(99);
360 }
361}
362
363void win32ShowPrintDialog(bool *expand, bool *noShrink, bool *noCenter, bool *usePDFPageSize, bool *allPages, int *firstPage, int *lastPage, int maxPages)
364{
365 PRINTDLG pd;
366 memset(&pd, 0, sizeof(PRINTDLG));
367 pd.lStructSize = sizeof(PRINTDLG);
368 pd.Flags = PD_NOSELECTION | PD_ENABLEPRINTHOOK | PD_USEDEVMODECOPIESANDCOLLATE | PD_RETURNDC;
369 if (*allPages) {
370 pd.nFromPage = 1;
371 pd.nToPage = maxPages;
372 } else {
373 pd.Flags |= PD_PAGENUMS;
374 pd.nFromPage = *firstPage;
375 pd.nToPage = *lastPage;
376 }
377 pd.nCopies = 1;
378 pd.nMinPage = 1;
379 pd.nMaxPage = maxPages;
380 pd.lpfnPrintHook = printDialogHookProc;
381 if (!*expand && *noShrink)
382 pageScale = NONE;
383 else if (!*expand && !*noShrink)
384 pageScale = SHRINK;
385 else
386 pageScale = FIT;
387 centerPage = !*noCenter;
388 useOrigPageSize = *usePDFPageSize;
389
390 if (PrintDlgA(&pd)) {
391 // Ok
392 hDevnames = pd.hDevNames;
393 DEVNAMES *devnames = (DEVNAMES *)GlobalLock(hDevnames);
394 printerName = (char *)devnames + devnames->wDeviceOffset;
395 hDevmode = pd.hDevMode;
396 devmode = (DEVMODEA *)GlobalLock(hDevmode);
397 hdc = pd.hDC;
398 if (pd.Flags & PD_PAGENUMS) {
399 *allPages = false;
400 *firstPage = pd.nFromPage;
401 *lastPage = pd.nToPage;
402 } else {
403 *allPages = true;
404 }
405 if (pageScale == NONE) {
406 *expand = false;
407 *noShrink = true;
408 } else if (pageScale == SHRINK) {
409 *expand = false;
410 *noShrink = false;
411 } else {
412 *expand = true;
413 *noShrink = false;
414 }
415 *noCenter = !centerPage;
416 *usePDFPageSize = useOrigPageSize;
417 } else {
418 // Cancel
419 exit(0);
420 }
421}
422
423cairo_surface_t *win32BeginDocument(GooString *inputFileName, GooString *outputFileName)
424{
425 DOCINFOA docinfo;
426 memset(&docinfo, 0, sizeof(docinfo));
427 docinfo.cbSize = sizeof(docinfo);
428 if (inputFileName->cmp("fd://0") == 0)
429 docinfo.lpszDocName = "pdftocairo <stdin>";
430 else
431 docinfo.lpszDocName = inputFileName->c_str();
432 if (outputFileName)
433 docinfo.lpszOutput = outputFileName->c_str();
434 if (StartDocA(hdc, &docinfo) <= 0) {
435 fprintf(stderr, "Error: StartDoc failed\n");
436 exit(99);
437 }
438
439 return cairo_win32_printing_surface_create(hdc);
440}
441
442void win32BeginPage(double *w, double *h, bool changePageSize, bool useFullPage)
443{
444 if (changePageSize)
445 fillPagePrinterOptions(*w, *h);
446 if (DocumentPropertiesA(nullptr, nullptr, printerName, devmode, devmode, DM_IN_BUFFER | DM_OUT_BUFFER) < 0) {
447 fprintf(stderr, "Error: Printer \"%s\" not found\n", printerName);
448 exit(99);
449 }
450 ResetDCA(hdc, devmode);
451
452 // Get actual paper size or if useFullPage is false the printable area.
453 // Transform the hdc scale to points to be consistent with other cairo backends
454 int x_dpi = GetDeviceCaps(hdc, LOGPIXELSX);
455 int y_dpi = GetDeviceCaps(hdc, LOGPIXELSY);
456 int x_off = GetDeviceCaps(hdc, PHYSICALOFFSETX);
457 int y_off = GetDeviceCaps(hdc, PHYSICALOFFSETY);
458 if (useFullPage) {
459 *w = GetDeviceCaps(hdc, PHYSICALWIDTH) * 72.0 / x_dpi;
460 *h = GetDeviceCaps(hdc, PHYSICALHEIGHT) * 72.0 / y_dpi;
461 } else {
462 *w = GetDeviceCaps(hdc, HORZRES) * 72.0 / x_dpi;
463 *h = GetDeviceCaps(hdc, VERTRES) * 72.0 / y_dpi;
464 }
465 XFORM xform;
466 xform.eM11 = x_dpi / 72.0f;
467 xform.eM12 = 0;
468 xform.eM21 = 0;
469 xform.eM22 = y_dpi / 72.0f;
470 if (useFullPage) {
471 xform.eDx = static_cast<FLOAT>(-x_off);
472 xform.eDy = static_cast<FLOAT>(-y_off);
473 } else {
474 xform.eDx = 0;
475 xform.eDy = 0;
476 }
477 SetGraphicsMode(hdc, GM_ADVANCED);
478 SetWorldTransform(hdc, &xform);
479
480 StartPage(hdc);
481}
482
483void win32EndPage(GooString *imageFileName)
484{
485 EndPage(hdc);
486}
487
488void win32EndDocument()
489{
490 EndDoc(hdc);
491 DeleteDC(hdc);
492 if (hDevmode) {
493 GlobalUnlock(hDevmode);
494 GlobalFree(hDevmode);
495 } else {
496 gfree(devmode);
497 }
498 if (hDevnames) {
499 GlobalUnlock(hDevnames);
500 GlobalFree(hDevnames);
501 } else {
502 gfree(printerName);
503 }
504}
505
506#endif // CAIRO_HAS_WIN32_SURFACE
507

source code of poppler/utils/pdftocairo-win32.cc