1 /*
2 * Copyright (c) 1994-2019 Paul Mattes.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the names of Paul Mattes nor the names of his contributors
13 * may be used to endorse or promote products derived from this software
14 * without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY PAUL MATTES "AS IS" AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL PAUL MATTES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 /*
29 * gdi_print.c
30 * GDI screen printing functions.
31 */
32
33 #include "globals.h"
34
35 #include <windows.h>
36 #include <commdlg.h>
37 #include <winspool.h>
38 #include <assert.h>
39
40 #include "appres.h"
41 #include "3270ds.h"
42 #include "ctlr.h"
43
44 #include "ctlrc.h"
45
46 #include "resources.h"
47
48 #include "fprint_screen.h"
49 #include "gdi_print.h"
50 #include "nvt.h"
51 #include "popups.h"
52 #include "task.h"
53 #include "trace.h"
54 #include "unicodec.h"
55 #include "utils.h"
56 #include "w3misc.h"
57
58 /* Defines */
59 #define PPI 72 /* points per inch */
60
61 /* Typedefs */
62
63 /* Globals */
64
65 /* Statics */
66 typedef struct { /* user parameters: */
67 int orientation; /* orientation */
68 double hmargin; /* horizontal margin in inches */
69 double vmargin; /* vertical margin in inches */
70 const char *font_name; /* font name */
71 int font_size; /* font size in points */
72 int spp; /* screens per page */
73 bool done; /* done fetching values */
74 } uparm_t;
75 static uparm_t uparm;
76 static struct { /* printer characteristics: */
77 int ppiX, ppiY; /* points per inch */
78 int poffX, poffY; /* left, top physical offsets */
79 int horzres, vertres; /* resolution (usable area) */
80 int pwidth, pheight; /* physical width, height */
81 } pchar;
82 static struct { /* printer state */
83 bool active; /* is GDI printing active? */
84 char *caption; /* caption */
85 int out_row; /* next row to print to */
86 int screens; /* number of screens on current page */
87 PRINTDLG dlg; /* Windows print dialog */
88 FLOAT xptscale, yptscale; /* x, y point-to-LU scaling factors */
89 int hmargin_pixels, vmargin_pixels; /* margins, in pixels */
90 int usable_xpixels, usable_ypixels;/* usable area (pixels) */
91 int usable_cols, usable_rows;/* usable area (chars) */
92 HFONT font, bold_font, underscore_font, bold_underscore_font;
93 HFONT caption_font;
94 /* fonts */
95 SIZE space_size; /* size of a space character */
96 INT *dx; /* spacing array */
97
98 HANDLE thread; /* thread to run the print dialog */
99 HANDLE done_event; /* event to signal dialog is done */
100 bool cancel; /* true if dialog canceled */
101 void *wait_context; /* task wait context */
102 } pstate;
103 static bool pstate_initted = false;
104
105 /* Forward declarations. */
106 static void gdi_get_params(uparm_t *up);
107 static gdi_status_t gdi_init(const char *printer_name, unsigned opts,
108 const char **fail, void *wait_context);
109 static int gdi_screenful(struct ea *ea, unsigned short rows,
110 unsigned short cols, const char **fail);
111 static int gdi_done(const char **fail);
112 static void gdi_abort(void);
113 static BOOL get_printer_device(const char *printer_name, HGLOBAL *pdevnames,
114 HGLOBAL *pdevmode);
115
116 /*
117 * Initialize printing to a GDI printer.
118 */
119 gdi_status_t
gdi_print_start(const char * printer_name,unsigned opts,void * wait_context)120 gdi_print_start(const char *printer_name, unsigned opts, void *wait_context)
121 {
122 const char *fail = "";
123
124 if (!uparm.done) {
125 /* Set the defaults. */
126 uparm.orientation = 0;
127 uparm.hmargin = 0.5;
128 uparm.vmargin = 0.5;
129 uparm.font_name = NULL;
130 uparm.font_size = 0; /* auto */
131 uparm.spp = 1;
132
133 /* Gather up the parameters. */
134 gdi_get_params(&uparm);
135
136 /* Don't do this again. */
137 uparm.done = true;
138 }
139
140 /* Initialize the printer and pop up the dialog. */
141 switch (gdi_init(printer_name, opts, &fail, wait_context)) {
142 case GDI_STATUS_SUCCESS:
143 vtrace("[gdi] initialized\n");
144 break;
145 case GDI_STATUS_ERROR:
146 popup_an_error("Printer initialization error: %s", fail);
147 return GDI_STATUS_ERROR;
148 case GDI_STATUS_CANCEL:
149 vtrace("[gdi] canceled\n");
150 return GDI_STATUS_CANCEL;
151 case GDI_STATUS_WAIT:
152 vtrace("[gdi] waiting\n");
153 return GDI_STATUS_WAIT;
154 }
155
156 return GDI_STATUS_SUCCESS;
157 }
158
159 /* Finish printing to a GDI printer. */
160 gdi_status_t
gdi_print_finish(FILE * f,const char * caption)161 gdi_print_finish(FILE *f, const char *caption)
162 {
163 size_t nr;
164 struct ea *ea_tmp;
165 gdi_header_t h;
166 const char *fail = "";
167
168 /* Save the caption. */
169 if (caption != NULL) {
170 Replace(pstate.caption, NewString(caption));
171 } else {
172 Replace(pstate.caption, NULL);
173 }
174
175 /* Allocate the buffer. */
176 ea_tmp = Malloc((((maxROWS * maxCOLS) + 1) * sizeof(struct ea)));
177
178 /* Set up the fake fa in location -1. */
179 memset(&ea_tmp[0], '\0', sizeof(struct ea));
180 ea_tmp[0].fa = FA_PRINTABLE | FA_MODIFY;
181
182 /* Rewind the file. */
183 fflush(f);
184 rewind(f);
185
186 /* Read it back. */
187 while ((nr = fread(&h, sizeof(gdi_header_t), 1, f)) == 1) {
188 /* Check the signature. */
189 if (h.signature != GDI_SIGNATURE) {
190 popup_an_error("Corrupt temporary file (signature)");
191 goto abort;
192 }
193
194 /* Check the screen dimensions. */
195 if (h.rows > maxROWS || h.cols > maxCOLS) {
196 popup_an_error("Corrupt temporary file (screen size)");
197 goto abort;
198 }
199
200 /* Read the screen image in. */
201 if (fread(ea_tmp + 1, sizeof(struct ea), h.rows * h.cols, f) !=
202 h.rows * h.cols) {
203 popup_an_error("Truncated temporary file");
204 goto abort;
205 }
206
207 /* Process it. */
208 if (gdi_screenful(ea_tmp + 1, h.rows, h.cols, &fail) < 0) {
209 popup_an_error("Printing error: %s", fail);
210 goto abort;
211 }
212 }
213 if (gdi_done(&fail) < 0) {
214 popup_an_error("Final printing error: %s", fail);
215 goto abort;
216 }
217 Free(ea_tmp);
218
219 pstate.active = false;
220 return GDI_STATUS_SUCCESS;
221
222 abort:
223 Free(ea_tmp);
224 gdi_abort();
225 return GDI_STATUS_ERROR;
226 }
227
228 /*
229 * Validate and scale a margin value.
230 */
231 static double
parse_margin(char * s,const char * what)232 parse_margin(char *s, const char *what)
233 {
234 double d;
235 char *nextp;
236
237 d = strtod(s, &nextp);
238 if (d > 0.0) {
239 while (*nextp == ' ') {
240 nextp++;
241 }
242 if (*nextp == '\0' || *nextp == '"' ||
243 !strcasecmp(nextp, "in") ||
244 !strcasecmp(nextp, "inch") ||
245 !strcasecmp(nextp, "inches")) {
246 /* Do nothing. */
247 } else if (!strcasecmp(nextp, "mm")) {
248 d /= 25.4;
249 } else if (!strcasecmp(nextp, "cm")) {
250 d /= 2.54;
251 } else {
252 vtrace("gdi: unknown %s unit '%s'\n",
253 what, nextp);
254 }
255 } else {
256 vtrace("gdi: invalid %s '%s'\n", what, s);
257 return 0;
258 }
259 return d;
260 }
261
262 /*
263 * Gather the user parameters from resources.
264 */
265 static void
gdi_get_params(uparm_t * up)266 gdi_get_params(uparm_t *up)
267 {
268 char *s;
269 double d;
270 unsigned long l;
271 char *nextp;
272
273 /* Orientation. */
274 if ((s = get_resource(ResPrintTextOrientation)) != NULL) {
275 if (!strcasecmp(s, "portrait")) {
276 up->orientation = DMORIENT_PORTRAIT;
277 } else if (!strcasecmp(s, "landscape")) {
278 up->orientation = DMORIENT_LANDSCAPE;
279 } else {
280 vtrace("gdi: unknown orientation '%s'\n", s);
281 }
282 }
283
284 /* Horizontal margin. */
285 if ((s = get_resource(ResPrintTextHorizontalMargin)) != NULL) {
286 d = parse_margin(s, ResPrintTextHorizontalMargin);
287 if (d > 0) {
288 up->hmargin = d;
289 }
290 }
291
292 /* Vertical margin. */
293 if ((s = get_resource(ResPrintTextVerticalMargin)) != NULL) {
294 d = parse_margin(s, ResPrintTextVerticalMargin);
295 if (d > 0) {
296 up->vmargin = d;
297 }
298 }
299
300 /* Font name. */
301 if ((s = get_resource(ResPrintTextFont)) != NULL) {
302 up->font_name = s;
303 }
304
305 /* Font size. */
306 if ((s = get_resource(ResPrintTextSize)) != NULL) {
307 if (strcasecmp(s, "auto")) {
308 l = strtoul(s, &nextp, 0);
309 if (l > 0) {
310 up->font_size = (int)l;
311 } else {
312 vtrace("gdi: invalid %s '%s'\n", ResPrintTextSize, s);
313 }
314 }
315 }
316
317 /* Screens per page. */
318 if ((s = get_resource(ResPrintTextScreensPerPage)) != NULL) {
319 l = strtoul(s, &nextp, 0);
320 if (l > 0) {
321 up->spp = (int)l;
322 } else {
323 vtrace("gdi: invalid %s '%s'\n", ResPrintTextScreensPerPage, s);
324 }
325 }
326 }
327
328 /*
329 * Clean up fonts.
330 */
331 static void
cleanup_fonts(void)332 cleanup_fonts(void)
333 {
334 if (pstate.font) {
335 DeleteObject(pstate.font);
336 pstate.font = NULL;
337 }
338 if (pstate.bold_font) {
339 DeleteObject(pstate.bold_font);
340 pstate.bold_font = NULL;
341 }
342 if (pstate.underscore_font) {
343 DeleteObject(pstate.underscore_font);
344 pstate.underscore_font = NULL;
345 }
346 if (pstate.caption_font) {
347 DeleteObject(pstate.caption_font);
348 pstate.caption_font = NULL;
349 }
350
351 pstate.active = false;
352 }
353
354 /*
355 * Create a Roman font.
356 * Returns 0 for success, -1 for failure.
357 */
358 static int
create_roman_font(HDC dc,int fheight,int fwidth,const char ** fail)359 create_roman_font(HDC dc, int fheight, int fwidth, const char **fail)
360 {
361 char *w, *h;
362
363 w = fwidth? xs_buffer("%d", fwidth): NewString("(auto)");
364 h = fheight? xs_buffer("%d", fheight): NewString("(auto)");
365 vtrace("[gdi] requesting a font %sx%s logical units\n", w, h);
366 Free(w);
367 Free(h);
368
369 pstate.font = CreateFont(
370 fheight, /* height */
371 fwidth, /* width */
372 0, /* escapement */
373 0, /* orientation */
374 FW_NORMAL, /* weight */
375 FALSE, /* italic */
376 FALSE, /* underline */
377 FALSE, /* strikeout */
378 DEFAULT_CHARSET, /* character set */
379 OUT_OUTLINE_PRECIS, /* output precision */
380 CLIP_DEFAULT_PRECIS,/* clip precision */
381 DEFAULT_QUALITY, /* quality */
382 FIXED_PITCH|FF_DONTCARE,/* pitch and family */
383 uparm.font_name); /* face */
384 if (pstate.font == NULL) {
385 *fail = "CreateFont failed";
386 return -1;
387 }
388
389 /* Measure a space to find out the size we got. */
390 SelectObject(dc, pstate.font);
391 if (!GetTextExtentPoint32(dc, " ", 1, &pstate.space_size)) {
392 *fail = "GetTextExtentPoint32 failed";
393 return -1;
394 }
395 vtrace("[gdi] space character is %dx%d logical units\n",
396 (int)pstate.space_size.cx, (int)pstate.space_size.cy);
397 pstate.usable_cols = pstate.usable_xpixels / pstate.space_size.cx;
398 pstate.usable_rows = pstate.usable_ypixels / pstate.space_size.cy;
399 vtrace("[gdi] usable area is %dx%d characters\n",
400 pstate.usable_cols, pstate.usable_rows);
401 return 0;
402 }
403
404 /*
405 * Return the default printer name.
406 */
407 static char *
get_default_printer_name(char * errbuf,size_t errbuf_size)408 get_default_printer_name(char *errbuf, size_t errbuf_size)
409 {
410 DWORD size;
411 char *buf;
412
413 /* Figure out how much memory to allocate. */
414 size = 0;
415 GetDefaultPrinter(NULL, &size);
416 buf = Malloc(size);
417 if (GetDefaultPrinter(buf, &size) == 0) {
418 snprintf(errbuf, errbuf_size, "Cannot determine default printer");
419 return NULL;
420 }
421 return buf;
422 }
423
424 /* Thread to post the print dialog. */
425 static DWORD WINAPI
post_print_dialog(LPVOID lpParameter _is_unused)426 post_print_dialog(LPVOID lpParameter _is_unused)
427 {
428 if (!PrintDlg(&pstate.dlg)) {
429 pstate.cancel = true;
430 }
431 SetEvent(pstate.done_event);
432 return 0;
433 }
434
435 /* The print dialog is complete. */
436 static void
print_dialog_complete(iosrc_t fd _is_unused,ioid_t id _is_unused)437 print_dialog_complete(iosrc_t fd _is_unused, ioid_t id _is_unused)
438 {
439 vtrace("Printer dialog complete (%s)\n",
440 pstate.cancel? "cancel": "continue");
441 pstate.thread = INVALID_HANDLE_VALUE;
442 task_resume_xwait(pstate.wait_context, pstate.cancel,
443 "print dialog complete");
444 }
445
446 /*
447 * Initalize the named GDI printer. If the name is NULL, use the default
448 * printer.
449 */
450 static gdi_status_t
gdi_init(const char * printer_name,unsigned opts,const char ** fail,void * wait_context)451 gdi_init(const char *printer_name, unsigned opts, const char **fail,
452 void *wait_context)
453 {
454 char *default_printer_name = NULL;
455 LPDEVMODE devmode;
456 HDC dc;
457 DOCINFO docinfo;
458 DEVNAMES *devnames;
459 int rmargin, bmargin; /* right margin, bottom margin */
460 int maxphmargin, maxpvmargin;
461 int i;
462 static char get_fail[1024];
463 int fheight, fwidth;
464
465 if (!pstate_initted) {
466 pstate.thread = INVALID_HANDLE_VALUE;
467 pstate.done_event = INVALID_HANDLE_VALUE;
468 pstate_initted = true;
469 }
470
471 if (pstate.active) {
472 *fail = "Only one GDI document at a time";
473 goto failed;
474 }
475
476 if (pstate.thread != INVALID_HANDLE_VALUE) {
477 *fail = "Print dialog already pending";
478 goto failed;
479 }
480
481 if (!(opts & FPS_DIALOG_COMPLETE)) {
482 memset(&pstate.dlg, '\0', sizeof(pstate.dlg));
483 pstate.dlg.lStructSize = sizeof(pstate.dlg);
484 pstate.dlg.Flags = PD_RETURNDC | PD_NOPAGENUMS | PD_HIDEPRINTTOFILE |
485 PD_NOSELECTION;
486 }
487
488 if (printer_name == NULL || !*printer_name) {
489 default_printer_name = get_default_printer_name(get_fail,
490 sizeof(get_fail));
491 if (default_printer_name == NULL) {
492 *fail = get_fail;
493 goto failed;
494 }
495 printer_name = default_printer_name;
496 }
497 if (!get_printer_device(printer_name, &pstate.dlg.hDevNames,
498 &pstate.dlg.hDevMode)) {
499 snprintf(get_fail, sizeof(get_fail),
500 "GetPrinter(%s) failed: %s",
501 printer_name, win32_strerror(GetLastError()));
502 *fail = get_fail;
503 goto failed;
504 }
505 if (uparm.orientation) {
506 devmode = (LPDEVMODE)GlobalLock(pstate.dlg.hDevMode);
507 devmode->dmFields |= DM_ORIENTATION;
508 devmode->dmOrientation = uparm.orientation;
509 GlobalUnlock(devmode);
510 }
511
512 if (opts & FPS_NO_DIALOG) {
513 /* They don't want the print dialog. Allocate a DC for it. */
514 devmode = (LPDEVMODE)GlobalLock(pstate.dlg.hDevMode);
515 pstate.dlg.hDC = CreateDC("WINSPOOL", printer_name, NULL, devmode);
516 GlobalUnlock(devmode);
517 if (pstate.dlg.hDC == NULL) {
518 snprintf(get_fail, sizeof(get_fail), "Cannot create DC for "
519 "printer '%s'", printer_name);
520 *fail = get_fail;
521 goto failed;
522 }
523 } else if (!(opts & FPS_DIALOG_COMPLETE)) {
524 if (default_printer_name != NULL) {
525 Free(default_printer_name);
526 default_printer_name = NULL;
527 }
528
529 /* Pop up the dialog to get the printer characteristics. */
530 pstate.cancel = false;
531 pstate.wait_context = wait_context;
532 if (pstate.done_event == INVALID_HANDLE_VALUE) {
533 pstate.done_event = CreateEvent(NULL, FALSE, FALSE, NULL);
534 AddInput(pstate.done_event, print_dialog_complete);
535 } else {
536 ResetEvent(pstate.done_event); /* just in case */
537 }
538 pstate.cancel = false;
539 pstate.thread = CreateThread(NULL, 0, post_print_dialog, NULL, 0, NULL);
540 return GDI_STATUS_WAIT;
541 }
542 dc = pstate.dlg.hDC;
543
544 if (default_printer_name != NULL) {
545 Free(default_printer_name);
546 default_printer_name = NULL;
547 }
548
549 /* Find out the printer characteristics. */
550
551 /* LOGPIXELSX and LOGPIXELSY are the pixels-per-inch for the printer. */
552 pchar.ppiX = GetDeviceCaps(dc, LOGPIXELSX);
553 if (pchar.ppiX <= 0) {
554 *fail = "Can't get LOGPIXELSX";
555 goto failed;
556 }
557 pchar.ppiY = GetDeviceCaps(dc, LOGPIXELSY);
558 if (pchar.ppiY <= 0) {
559 *fail = "Can't get LOGPIXELSY";
560 goto failed;
561 }
562
563 /*
564 * PHYSICALOFFSETX and PHYSICALOFFSETY are the fixed top and left-hand
565 * margins, in pixels. Whatever you print is offset by these amounts, so
566 * you have to subtract them from your coordinates. You cannot print in
567 * these areas.
568 */
569 pchar.poffX = GetDeviceCaps(dc, PHYSICALOFFSETX);
570 if (pchar.poffX < 0) {
571 *fail = "Can't get PHYSICALOFFSETX";
572 goto failed;
573 }
574 pchar.poffY = GetDeviceCaps(dc, PHYSICALOFFSETY);
575 if (pchar.poffY < 0) {
576 *fail = "Can't get PHYSICALOFFSETY";
577 goto failed;
578 }
579
580 /*
581 * HORZRES and VERTRES are the size of the usable area of the page, in
582 * pixels. They implicitly give you the size of the right-hand and
583 * bottom physical offsets.
584 */
585 pchar.horzres = GetDeviceCaps(dc, HORZRES);
586 if (pchar.horzres <= 0) {
587 *fail = "Can't get HORZRES";
588 goto failed;
589 }
590 pchar.vertres = GetDeviceCaps(dc, VERTRES);
591 if (pchar.vertres <= 0) {
592 *fail = "Can't get VERTRES";
593 goto failed;
594 }
595
596 /*
597 * PHYSICALWIDTH and PHYSICALHEIGHT are the size of the entire area of
598 * the page, in pixels.
599 */
600 pchar.pwidth = GetDeviceCaps(dc, PHYSICALWIDTH);
601 if (pchar.pwidth <= 0) {
602 *fail = "Can't get PHYSICALWIDTH";
603 goto failed;
604 }
605 pchar.pheight = GetDeviceCaps(dc, PHYSICALHEIGHT);
606 if (pchar.pheight <= 0) {
607 *fail = "Can't get PHYSICALHEIGHT";
608 goto failed;
609 }
610
611 /* Trace the device characteristics. */
612 devnames = (DEVNAMES *)GlobalLock(pstate.dlg.hDevNames);
613 vtrace("[gdi] Printer '%s' capabilities:\n",
614 (char *)devnames + devnames->wDeviceOffset);
615 GlobalUnlock(devnames);
616 vtrace("[gdi] LOGPIXELSX %d LOGPIXELSY %d\n",
617 pchar.ppiX, pchar.ppiY);
618 vtrace("[gdi] PHYSICALOFFSETX %d PHYSICALOFFSETY %d\n",
619 pchar.poffX, pchar.poffY);
620 vtrace("[gdi] HORZRES %d VERTRES %d\n",
621 pchar.horzres, pchar.vertres);
622 vtrace("[gdi] PHYSICALWIDTH %d PHYSICALHEIGHT %d\n",
623 pchar.pwidth, pchar.pheight);
624
625 /* Compute the scale factors (points to pixels). */
626 pstate.xptscale = (FLOAT)pchar.ppiX / (FLOAT)PPI;
627 pstate.yptscale = (FLOAT)pchar.ppiY / (FLOAT)PPI;
628
629 /* Compute the implied right and bottom margins. */
630 rmargin = pchar.pwidth - pchar.horzres - pchar.poffX;
631 bmargin = pchar.pheight - pchar.vertres - pchar.poffY;
632 if (rmargin > pchar.poffX) {
633 maxphmargin = rmargin;
634 } else {
635 maxphmargin = pchar.poffX;
636 }
637 if (bmargin > pchar.poffY) {
638 maxpvmargin = bmargin;
639 } else {
640 maxpvmargin = pchar.poffY;
641 }
642 vtrace("[gdi] maxphmargin is %d, maxpvmargin is %d pixels\n",
643 maxphmargin, maxpvmargin);
644
645 /* Compute the margins in pixels. */
646 pstate.hmargin_pixels = (int)(uparm.hmargin * pchar.ppiX);
647 pstate.vmargin_pixels = (int)(uparm.vmargin * pchar.ppiY);
648
649 /* See if the margins are too small. */
650 if (pstate.hmargin_pixels < maxphmargin) {
651 pstate.hmargin_pixels = maxphmargin;
652 vtrace("[gdi] hmargin is too small, setting to %g\"\n",
653 (float)pstate.hmargin_pixels / pchar.ppiX);
654 }
655 if (pstate.vmargin_pixels < maxpvmargin) {
656 pstate.vmargin_pixels = maxpvmargin;
657 vtrace("[gdi] vmargin is too small, setting to %g\"\n",
658 (float)pstate.vmargin_pixels / pchar.ppiX);
659 }
660
661 /* See if the margins are too big. */
662 if (pstate.hmargin_pixels * 2 >= pchar.horzres) {
663 pstate.hmargin_pixels = pchar.ppiX;
664 vtrace("[gdi] hmargin is too big, setting to 1\"\n");
665 }
666 if (pstate.vmargin_pixels * 2 >= pchar.vertres) {
667 pstate.vmargin_pixels = pchar.ppiY;
668 vtrace("[gdi] vmargin is too big, setting to 1\"\n");
669 }
670
671 /*
672 * Compute the usable area in pixels. That's the physical page size
673 * less the margins, now that we know that the margins are reasonable.
674 */
675 pstate.usable_xpixels = pchar.pwidth - (2 * pstate.hmargin_pixels);
676 pstate.usable_ypixels = pchar.pheight - (2 * pstate.vmargin_pixels);
677 vtrace("[gdi] usable area is %dx%d pixels\n",
678 pstate.usable_xpixels, pstate.usable_ypixels);
679
680 /*
681 * Create the Roman font.
682 *
683 * If they specified a particular font size, use that as the height,
684 * and let the system pick the width.
685 *
686 * If they did not specify a font size, or chose "auto", then let the
687 * "screens per page" drive what to do. If "screens per page" is set,
688 * then divide the page Y pixels by the screens-per-page times the
689 * display height to get the font height, and let the system pick the
690 * width.
691 *
692 * Otherwise, divide the page X pixels by COLS to get the font width,
693 * and let the system pick the height.
694 */
695 if (uparm.font_size) {
696 /* User-specified fixed font size. */
697 fheight = (int)(uparm.font_size * pstate.yptscale);
698 fwidth = 0;
699 } else {
700 if (uparm.spp > 1) {
701 /*
702 * Scale the height so the specified number of screens will
703 * fit.
704 */
705 fheight = pstate.usable_ypixels /
706 (uparm.spp * maxROWS /* spp screens */
707 + (uparm.spp - 1) /* spaces between screens */
708 + 2 /* space and caption*/ );
709 fwidth = 0;
710 } else {
711 /*
712 * Scale the width so a screen will fit the page horizonally.
713 */
714 fheight = 0;
715 fwidth = pstate.usable_xpixels / maxCOLS;
716 }
717 }
718 if (create_roman_font(dc, fheight, fwidth, fail) < 0) {
719 goto failed;
720 }
721
722 /*
723 * If we computed the font size, see if the other dimension is too
724 * big. If it is, scale using the other dimension, which is guaranteed to
725 * make the original computed dimension no bigger.
726 *
727 * XXX: This needs more testing.
728 */
729 if (!uparm.font_size) {
730 if (fwidth == 0) {
731 /*
732 * We computed the height because spp > 1. See if the width
733 * overflows.
734 */
735 if (pstate.space_size.cx * maxCOLS > pstate.usable_xpixels) {
736 vtrace("[gdi] font too wide, retrying\n");
737 DeleteObject(pstate.font);
738 pstate.font = NULL;
739
740 fheight = 0;
741 fwidth = pstate.usable_xpixels / maxCOLS;
742 if (create_roman_font(dc, fheight, fwidth, fail) < 0) {
743 goto failed;
744 }
745 }
746 } else if (fheight == 0) {
747 /*
748 * We computed the width (spp <= 1). See if the height
749 * overflows.
750 */
751 if (pstate.space_size.cy * (maxROWS + 2) >
752 pstate.usable_xpixels) {
753 vtrace("[gdi] font too high, retrying\n");
754 DeleteObject(pstate.font);
755 pstate.font = NULL;
756
757 fheight = pstate.usable_xpixels / (maxROWS + 2);
758 fwidth = 0;
759 if (create_roman_font(dc, fheight, fwidth, fail) < 0) {
760 goto failed;
761 }
762 }
763 }
764 }
765
766 /* Create a bold font that is the same size, if possible. */
767 pstate.bold_font = CreateFont(
768 pstate.space_size.cy, /* height */
769 pstate.space_size.cx, /* width */
770 0, /* escapement */
771 0, /* orientation */
772 FW_BOLD, /* weight */
773 FALSE, /* italic */
774 FALSE, /* underline */
775 FALSE, /* strikeout */
776 ANSI_CHARSET, /* character set */
777 OUT_OUTLINE_PRECIS, /* output precision */
778 CLIP_DEFAULT_PRECIS, /* clip precision */
779 DEFAULT_QUALITY, /* quality */
780 FIXED_PITCH|FF_DONTCARE,/* pitch and family */
781 uparm.font_name); /* face */
782 if (pstate.bold_font == NULL) {
783 *fail = "CreateFont (bold) failed";
784 goto failed;
785 }
786
787 /* Create an underscore font that is the same size, if possible. */
788 pstate.underscore_font = CreateFont(
789 pstate.space_size.cy, /* height */
790 pstate.space_size.cx, /* width */
791 0, /* escapement */
792 0, /* orientation */
793 FW_NORMAL, /* weight */
794 FALSE, /* italic */
795 TRUE, /* underline */
796 FALSE, /* strikeout */
797 ANSI_CHARSET, /* character set */
798 OUT_OUTLINE_PRECIS, /* output precision */
799 CLIP_DEFAULT_PRECIS, /* clip precision */
800 DEFAULT_QUALITY, /* quality */
801 FIXED_PITCH|FF_DONTCARE,/* pitch and family */
802 uparm.font_name); /* face */
803 if (pstate.underscore_font == NULL) {
804 *fail = "CreateFont (underscore) failed";
805 goto failed;
806 }
807
808 /* Create a bold, underscore font that is the same size, if possible. */
809 pstate.bold_underscore_font = CreateFont(
810 pstate.space_size.cy, /* height */
811 pstate.space_size.cx, /* width */
812 0, /* escapement */
813 0, /* orientation */
814 FW_BOLD, /* weight */
815 FALSE, /* italic */
816 TRUE, /* underline */
817 FALSE, /* strikeout */
818 ANSI_CHARSET, /* character set */
819 OUT_OUTLINE_PRECIS, /* output precision */
820 CLIP_DEFAULT_PRECIS, /* clip precision */
821 DEFAULT_QUALITY, /* quality */
822 FIXED_PITCH|FF_DONTCARE,/* pitch and family */
823 uparm.font_name); /* face */
824 if (pstate.bold_underscore_font == NULL) {
825 *fail = "CreateFont (bold underscore) failed";
826 goto failed;
827 }
828
829 /* Create a caption font. */
830 pstate.caption_font = CreateFont(
831 pstate.space_size.cy, /* height */
832 0, /* width */
833 0, /* escapement */
834 0, /* orientation */
835 FW_NORMAL, /* weight */
836 TRUE, /* italic */
837 FALSE, /* underline */
838 FALSE, /* strikeout */
839 ANSI_CHARSET, /* character set */
840 OUT_OUTLINE_PRECIS, /* output precision */
841 CLIP_DEFAULT_PRECIS, /* clip precision */
842 DEFAULT_QUALITY, /* quality */
843 VARIABLE_PITCH|FF_DONTCARE,/* pitch and family */
844 "Times New Roman"); /* face */
845 if (pstate.bold_underscore_font == NULL) {
846 *fail = "CreateFont (bold underscore) failed";
847 goto failed;
848 }
849
850 /* Set up the manual spacing array. */
851 pstate.dx = Malloc(sizeof(INT) * maxCOLS);
852 for (i = 0; i < maxCOLS; i++) {
853 pstate.dx[i] = pstate.space_size.cx;
854 }
855
856 /* Fill in the document info. */
857 memset(&docinfo, '\0', sizeof(docinfo));
858 docinfo.cbSize = sizeof(docinfo);
859 docinfo.lpszDocName = "wc3270 screen";
860
861 /* Start the document. */
862 if (StartDoc(dc, &docinfo) <= 0) {
863 *fail = "StartDoc failed";
864 goto failed;
865 }
866
867 pstate.active = true;
868 return GDI_STATUS_SUCCESS;
869
870 failed:
871 /* Clean up what we can and return failure. */
872 if (default_printer_name != NULL) {
873 Free(default_printer_name);
874 }
875 cleanup_fonts();
876 return GDI_STATUS_ERROR;
877 }
878
879 /*
880 * Print one screeful to the GDI printer.
881 */
882 static int
gdi_screenful(struct ea * ea,unsigned short rows,unsigned short cols,const char ** fail)883 gdi_screenful(struct ea *ea, unsigned short rows, unsigned short cols,
884 const char **fail)
885 {
886 HDC dc = pstate.dlg.hDC;
887 LPDEVMODE devmode;
888 int row, col, baddr;
889 int rc = 0;
890 int status;
891 int fa_addr = find_field_attribute_ea(0, ea);
892 unsigned char fa = ea[fa_addr].fa;
893 bool fa_high, high;
894 bool fa_underline, underline;
895 bool fa_reverse, reverse;
896 ucs4_t uc;
897 int usable_rows;
898 HFONT got_font = NULL, want_font;
899 #if defined(GDI_DEBUG) /*[*/
900 const char *want_font_name;
901 #endif /*]*/
902 enum { COLOR_NONE, COLOR_NORMAL, COLOR_REVERSE } got_color = COLOR_NONE,
903 want_color;
904
905 devmode = (LPDEVMODE)GlobalLock(pstate.dlg.hDevMode);
906
907 /* Compute the usable rows, including the caption. */
908 usable_rows = pstate.usable_rows;
909 if (pstate.caption) {
910 usable_rows -= 2;
911 }
912
913 /*
914 * Does this screen fit?
915 * (Note that the first test, "pstate.out_row", is there so that if the
916 * font is so big the image won't fit at all, we still print as much
917 * of it as we can.)
918 */
919 if (pstate.out_row && pstate.out_row + ROWS > usable_rows) {
920 if (EndPage(dc) <= 0) {
921 *fail = "EndPage failed";
922 rc = -1;
923 goto done;
924 }
925 pstate.out_row = 0;
926 pstate.screens = 0;
927 }
928
929 /* If there is a caption, put it on the last line. */
930 if (pstate.out_row == 0 && pstate.caption != NULL) {
931 SelectObject(dc, pstate.caption_font);
932 status = ExtTextOut(dc,
933 pstate.hmargin_pixels - pchar.poffX,
934 pstate.vmargin_pixels +
935 ((pstate.usable_rows - 1) * pstate.space_size.cy) -
936 pchar.poffY,
937 0, NULL,
938 pstate.caption, (UINT)strlen(pstate.caption), NULL);
939 if (status <= 0) {
940 *fail = "ExtTextOut(caption) failed";
941 rc = -1;
942 goto done;
943 }
944 }
945
946 /* Draw a line separating the screens. */
947 if (pstate.out_row) {
948 HPEN pen;
949
950 pen = CreatePen(PS_SOLID, 3, RGB(0, 0, 0));
951 SelectObject(dc, pen);
952 status = MoveToEx(dc,
953 pstate.hmargin_pixels - pchar.poffX,
954 pstate.vmargin_pixels +
955 (pstate.out_row * pstate.space_size.cy) +
956 (pstate.space_size.cy / 2) - pchar.poffY,
957 NULL);
958 if (status == 0) {
959 *fail = "MoveToEx failed";
960 rc = -1;
961 goto done;
962 }
963 status = LineTo(dc,
964 pstate.hmargin_pixels - pchar.poffX + pstate.usable_xpixels,
965 pstate.vmargin_pixels +
966 (pstate.out_row * pstate.space_size.cy) +
967 (pstate.space_size.cy / 2) - pchar.poffY);
968 if (status == 0) {
969 *fail = "LineTo failed";
970 rc = -1;
971 goto done;
972 }
973 DeleteObject(pen);
974 }
975
976 /* Now dump out a screen's worth. */
977 if (ea[fa_addr].gr & GR_INTENSIFY) {
978 fa_high = true;
979 } else {
980 fa_high = FA_IS_HIGH(fa);
981 }
982 fa_reverse = ((ea[fa_addr].gr & GR_REVERSE) != 0);
983 fa_underline = ((ea[fa_addr].gr & GR_UNDERLINE) != 0);
984
985 for (baddr = 0, row = 0; row < ROWS; row++) {
986 if (pstate.out_row + row >= usable_rows) {
987 break;
988 }
989 for (col = 0; col < COLS; col++, baddr++) {
990 wchar_t w;
991 INT wdx;
992
993 if (ea[baddr].fa) {
994 fa = ea[baddr].fa;
995 if (ea[baddr].gr & GR_INTENSIFY) {
996 fa_high = true;
997 } else {
998 fa_high = FA_IS_HIGH(fa);
999 }
1000 fa_reverse = ((ea[fa_addr].gr & GR_REVERSE) != 0);
1001 fa_underline = ((ea[fa_addr].gr & GR_UNDERLINE) != 0);
1002
1003 /* Just skip it. */
1004 continue;
1005 }
1006 if (col >= pstate.usable_cols) {
1007 continue;
1008 }
1009 if (FA_IS_ZERO(fa)) {
1010 if (ctlr_dbcs_state_ea(baddr, ea) == DBCS_LEFT) {
1011 uc = 0x3000;
1012 } else {
1013 uc = ' ';
1014 }
1015 } else if (is_nvt(&ea[baddr], false, &uc)) {
1016 switch (ctlr_dbcs_state(baddr)) {
1017 case DBCS_NONE:
1018 case DBCS_SB:
1019 case DBCS_LEFT:
1020 break;
1021 case DBCS_RIGHT:
1022 /* skip altogether, we took care of it above */
1023 continue;
1024 default:
1025 uc = ' ';
1026 break;
1027 }
1028 } else {
1029 /* Convert EBCDIC to Unicode. */
1030 switch (ctlr_dbcs_state(baddr)) {
1031 case DBCS_NONE:
1032 case DBCS_SB:
1033 uc = ebcdic_to_unicode(ea[baddr].ec, ea[baddr].cs,
1034 EUO_NONE);
1035 if (uc == 0) {
1036 uc = ' ';
1037 }
1038 break;
1039 case DBCS_LEFT:
1040 uc = ebcdic_to_unicode((ea[baddr].ec << 8) |
1041 ea[baddr + 1].ec,
1042 CS_BASE, EUO_NONE);
1043 if (uc == 0) {
1044 uc = 0x3000;
1045 }
1046 break;
1047 case DBCS_RIGHT:
1048 /* skip altogether, we took care of it above */
1049 continue;
1050 default:
1051 uc = ' ';
1052 break;
1053 }
1054 }
1055
1056 /* Figure out the attributes of the current buffer position. */
1057 high = ((ea[baddr].gr & GR_INTENSIFY) != 0);
1058 if (!high) {
1059 high = fa_high;
1060 }
1061 reverse = ((ea[fa_addr].gr & GR_REVERSE) != 0);
1062 if (!reverse) {
1063 reverse = fa_reverse;
1064 }
1065 underline = ((ea[fa_addr].gr & GR_UNDERLINE) != 0);
1066 if (!underline) {
1067 underline = fa_underline;
1068 }
1069
1070 /* Set the bg/fg color and font. */
1071 if (reverse) {
1072 want_color = COLOR_REVERSE;
1073 } else {
1074 want_color = COLOR_NORMAL;
1075 }
1076 if (want_color != got_color) {
1077 switch (want_color) {
1078 case COLOR_REVERSE:
1079 SetTextColor(dc, 0xffffff);
1080 SetBkColor(dc, 0);
1081 SetBkMode(dc, OPAQUE);
1082 break;
1083 case COLOR_NORMAL:
1084 SetTextColor(dc, 0);
1085 SetBkColor(dc, 0xffffff);
1086 SetBkMode(dc, TRANSPARENT);
1087 break;
1088 default:
1089 break;
1090 }
1091 got_color = want_color;
1092 }
1093 if (!high && !underline) {
1094 want_font = pstate.font;
1095 #if defined(GDI_DEBUG) /*[*/
1096 want_font_name = "Roman";
1097 #endif /*]*/
1098 } else if (high && !underline) {
1099 want_font = pstate.bold_font;
1100 #if defined(GDI_DEBUG) /*[*/
1101 want_font_name = "Bold";
1102 #endif /*]*/
1103 } else if (!high && underline) {
1104 want_font = pstate.underscore_font;
1105 #if defined(GDI_DEBUG) /*[*/
1106 want_font_name = "Underscore";
1107 #endif /*]*/
1108 } else {
1109 want_font = pstate.bold_underscore_font;
1110 #if defined(GDI_DEBUG) /*[*/
1111 want_font_name = "Underscore";
1112 #endif /*]*/
1113 }
1114 if (want_font != got_font) {
1115 SelectObject(dc, want_font);
1116 got_font = want_font;
1117 #if defined(GDI_DEBUG) /*[*/
1118 vtrace("[gdi] selecting %s\n", want_font_name);
1119 #endif /*]*/
1120 }
1121
1122 /*
1123 * Handle spaces and DBCS spaces (U+3000).
1124 * If not reverse or underline, just skip over them.
1125 * Otherwise, print a space or two spaces, using the
1126 * right font and modes.
1127 */
1128 if (uc == ' ' || uc == 0x3000) {
1129 if (reverse || underline) {
1130 status = ExtTextOut(dc, pstate.hmargin_pixels +
1131 (col * pstate.space_size.cx) -
1132 pchar.poffX,
1133 pstate.vmargin_pixels +
1134 ((pstate.out_row + row + 1) *
1135 pstate.space_size.cy) -
1136 pchar.poffY,
1137 0, NULL,
1138 " ",
1139 (uc == 0x3000)? 2: 1,
1140 pstate.dx);
1141 if (status <= 0) {
1142 *fail = "ExtTextOut(space) failed";
1143 rc = -1;
1144 goto done;
1145 }
1146 }
1147 continue;
1148 }
1149
1150 /*
1151 * Emit one character at a time. This should be optimized to print
1152 * strings of characters with the same attributes.
1153 */
1154 #if defined(GDI_DEBUG) /*[*/
1155 if (uc != ' ') {
1156 vtrace("[gdi] row %d col %d x=%ld y=%ld uc=%lx\n",
1157 row, col,
1158 pstate.hmargin_pixels + (col * pstate.space_size.cx) -
1159 pchar.poffX,
1160 pstate.vmargin_pixels +
1161 ((pstate.out_row + row + 1) * pstate.space_size.cy) -
1162 pchar.poffY,
1163 uc);
1164 }
1165 #endif /*]*/
1166 w = (wchar_t)uc;
1167 wdx = pstate.space_size.cx;
1168 status = ExtTextOutW(dc,
1169 pstate.hmargin_pixels + (col * pstate.space_size.cx) -
1170 pchar.poffX,
1171 pstate.vmargin_pixels +
1172 ((pstate.out_row + row + 1) *
1173 pstate.space_size.cy) -
1174 pchar.poffY,
1175 0, NULL,
1176 &w, 1, &wdx);
1177 if (status <= 0) {
1178 *fail = "ExtTextOutW(image) failed";
1179 rc = -1;
1180 goto done;
1181 }
1182 }
1183 }
1184
1185 /* Tally the current screen and see if we need to go to a new page. */
1186 pstate.out_row += (row + 1); /* current screen plus a gap */
1187 pstate.screens++;
1188 if (pstate.out_row >= usable_rows || pstate.screens >= uparm.spp) {
1189 if (EndPage(dc) <= 0) {
1190 *fail = "EndPage failed";
1191 rc = -1;
1192 goto done;
1193 }
1194 pstate.out_row = 0;
1195 pstate.screens = 0;
1196 }
1197
1198 done:
1199 GlobalUnlock(devmode);
1200 return rc;
1201 }
1202
1203 /*
1204 * Finish the GDI print-out and clean up the data structures.
1205 */
1206 static int
gdi_done(const char ** fail)1207 gdi_done(const char **fail)
1208 {
1209 int rc = 0;
1210
1211 if (pstate.out_row) {
1212 if (EndPage(pstate.dlg.hDC) <= 0) {
1213 *fail = "EndPage failed";
1214 rc = -1;
1215 }
1216 pstate.out_row = 0;
1217 }
1218 if (EndDoc(pstate.dlg.hDC) <= 0) {
1219 *fail = "EndDoc failed";
1220 rc = -1;
1221 }
1222
1223 cleanup_fonts();
1224
1225 return rc;
1226 }
1227
1228 /*
1229 * Clean up the GDI data structures without attempting any more printing.
1230 */
1231 static void
gdi_abort(void)1232 gdi_abort(void)
1233 {
1234 if (pstate.out_row) {
1235 EndPage(pstate.dlg.hDC);
1236 pstate.out_row = 0;
1237 }
1238 EndDoc(pstate.dlg.hDC);
1239
1240 cleanup_fonts();
1241 }
1242
1243 /*
1244 * Get a DEVMODE and DEVNAMES from a printer name.
1245 *
1246 * Returns TRUE for success, FALSE for failure.
1247 */
1248 static BOOL
get_printer_device(const char * printer_name,HGLOBAL * pdevnames,HGLOBAL * pdevmode)1249 get_printer_device(const char *printer_name, HGLOBAL *pdevnames,
1250 HGLOBAL *pdevmode)
1251 {
1252 HANDLE h;
1253 DWORD len, len2;
1254 PRINTER_INFO_2 *pi;
1255 size_t dmsize;
1256 HGLOBAL gdm;
1257 char *dm;
1258 size_t ldn;
1259 size_t lpn;
1260 size_t ltn;
1261 HGLOBAL gdn;
1262 DEVNAMES *dn;
1263 size_t offset;
1264
1265 /* Gotta have something to return the values in. */
1266 if (pdevmode == NULL || pdevnames == NULL) {
1267 return FALSE;
1268 }
1269
1270 /* Open the printer. */
1271 h = NULL;
1272 if (!OpenPrinter((char *)printer_name, &h, NULL)) {
1273 return FALSE;
1274 }
1275
1276 /* Get a PRINTER_INFO_2 structure for the printer. */
1277 GetPrinter(h, 2, NULL, 0, &len);
1278 pi = (PRINTER_INFO_2 *)malloc(len);
1279 if (!GetPrinter(h, 2, (LPBYTE)pi, len, &len2)) {
1280 free(pi);
1281 ClosePrinter(h);
1282 return FALSE;
1283 }
1284 ClosePrinter(h);
1285 h = NULL;
1286
1287 /* Copy the DEVMODE from the PRINTER_INFO_2 into a global handle. */
1288 dmsize = sizeof(*pi->pDevMode) + pi->pDevMode->dmDriverExtra;
1289 gdm = GlobalAlloc(GHND, dmsize);
1290 assert(gdm);
1291 dm = (char *)GlobalLock(gdm);
1292 assert(dm);
1293 memcpy(dm, pi->pDevMode, dmsize);
1294 GlobalUnlock(gdm);
1295
1296 /*
1297 * Compute the size of the DEVNAMES structure from the fields in the
1298 * PRINTER_INFO_2.
1299 */
1300 ldn = strlen(pi->pDriverName) + 1;
1301 lpn = strlen(pi->pPrinterName) + 1;
1302 ltn = strlen(pi->pPortName) + 1;
1303
1304 /*
1305 * Construct a DEVNAMES from the PRINTER_INFO_2, allocated as a global
1306 * handle.
1307 */
1308 gdn = GlobalAlloc(GHND, sizeof(DEVNAMES) + ldn + lpn + ltn);
1309 assert(gdn);
1310 dn = (DEVNAMES *)GlobalLock(gdn);
1311 assert(dn);
1312 memset(dn, '\0', sizeof(DEVNAMES));
1313 offset = sizeof(DEVNAMES);
1314 dn->wDriverOffset = (WORD)offset;
1315 memcpy((char *)dn + offset, pi->pDriverName, ldn);
1316 offset += ldn;
1317 dn->wDeviceOffset = (WORD)offset;
1318 memcpy((char *)dn + offset, pi->pPrinterName, lpn);
1319 offset += lpn;
1320 dn->wOutputOffset = (WORD)offset;
1321 memcpy((char *)dn + offset, pi->pPortName, ltn);
1322 dn->wDefault = 0;
1323
1324 /* Done filling in dn. */
1325 GlobalUnlock(gdn);
1326
1327 /* Done with the PRINTER_INFO_2. */
1328 free(pi);
1329 pi = NULL;
1330
1331 /* Return the devmode and devnames. */
1332 *pdevmode = gdm;
1333 *pdevnames = gdn;
1334
1335 /* Success. */
1336 return TRUE;
1337 }
1338