1 /*
2  * Copyright (c) 1994-2015, 2018-2020 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  *	fprint_screen.c
30  *		Screen printing functions.
31  */
32 
33 #include "globals.h"
34 
35 #include <assert.h>
36 
37 #include "appres.h"
38 #include "3270ds.h"
39 #include "ctlr.h"
40 
41 #include "ctlrc.h"
42 
43 #include "resources.h"
44 
45 #include "fprint_screen.h"
46 #if defined(_WIN32) /*[*/
47 # include "gdi_print.h"
48 #endif /*]*/
49 #include "nvt.h"
50 #include "trace.h"
51 #include "unicodec.h"
52 #include "utf8.h"
53 #include "utils.h"
54 #include "varbuf.h"
55 
56 /* Typedefs */
57 typedef struct {
58     ptype_t ptype;		/* Type P_XXX (text, html, rtf) */
59     unsigned opts;		/* FPS_XXX options */
60     bool need_separator;	/* Pending page indicator */
61     bool broken;		/* If set, output has failed already. */
62     int spp;			/* Screens per page. */
63     int screens;		/* Screen count this page. */
64     FILE *file;			/* Stream to write to */
65     char *caption;		/* Caption with %T% expanded */
66     char *printer_name;		/* Printer name (used by GDI) */
67 } real_fps_t;
68 
69 /* Globals */
70 
71 /* Statics */
72 
73 /*
74  * Map default 3279 colors.  This code is duplicated three times. ;-(
75  */
76 static int
color_from_fa(unsigned char fa)77 color_from_fa(unsigned char fa)
78 {
79     static int field_colors[4] = {
80 	HOST_COLOR_GREEN,        /* default */
81 	HOST_COLOR_RED,          /* intensified */
82 	HOST_COLOR_BLUE,         /* protected */
83 	HOST_COLOR_WHITE         /* protected, intensified */
84 #       define DEFCOLOR_MAP(f) \
85 	((((f) & FA_PROTECT) >> 4) | (((f) & FA_INT_HIGH_SEL) >> 3))
86     };
87 
88     if (mode.m3279) {
89 	return field_colors[DEFCOLOR_MAP(fa)];
90     } else {
91 	return HOST_COLOR_GREEN;
92     }
93 }
94 
95 /*
96  * Map 3279 colors onto HTML colors.
97  */
98 static char *
html_color(int color)99 html_color(int color)
100 {
101     static char *html_color_map[] = {
102 	"black",
103 	"deepSkyBlue",
104 	"red",
105 	"pink",
106 	"green",
107 	"turquoise",
108 	"yellow",
109 	"white",
110 	"black",
111 	"blue3",
112 	"orange",
113 	"purple",
114 	"paleGreen",
115 	"paleTurquoise2",
116 	"grey",
117 	"white"
118     };
119     if (color >= HOST_COLOR_NEUTRAL_BLACK && color <= HOST_COLOR_WHITE) {
120 	return html_color_map[color];
121     } else {
122 	return "black";
123     }
124 }
125 
126 /* Convert a caption string to UTF-8 RTF. */
127 static char *
rtf_caption(const char * caption)128 rtf_caption(const char *caption)
129 {
130     ucs4_t u;
131     int consumed;
132     enum me_fail error;
133     char mb[16];
134     varbuf_t r;
135 
136     vb_init(&r);
137 
138     while (*caption) {
139 	u = multibyte_to_unicode(caption, strlen(caption), &consumed, &error);
140 	if (u == 0) {
141 	    break;
142 	}
143 	if (u & ~0x7f) {
144 	    vb_appendf(&r, "\\u%u?", u);
145 	} else {
146 	    unicode_to_multibyte(u, mb, sizeof(mb));
147 	    if (mb[0] == '\\' || mb[0] == '{' || mb[0] == '}') {
148 		vb_appendf(&r, "\\%c", mb[0]);
149 	    } else if (mb[0] == '-') {
150 		vb_appends(&r, "\\_");
151 	    } else if (mb[0] == ' ') {
152 		vb_appends(&r, "\\~");
153 	    } else {
154 		vb_append(&r, &mb[0], 1);
155 	    }
156 	}
157 
158 	caption += consumed;
159     }
160     return vb_consume(&r);
161 }
162 
163 /* Convert a caption string to UTF-8 HTML. */
164 static char *
html_caption(const char * caption)165 html_caption(const char *caption)
166 {
167     ucs4_t u;
168     int consumed;
169     enum me_fail error;
170     char u8buf[16];
171     int nu8;
172     varbuf_t r;
173 
174     vb_init(&r);
175 
176     while (*caption) {
177 	u = multibyte_to_unicode(caption, strlen(caption), &consumed, &error);
178 	if (u == 0) {
179 	    break;
180 	}
181 	switch (u) {
182 	case '<':
183 	    vb_appends(&r, "&lt;");
184 	    break;
185 	case '>':
186 	    vb_appends(&r, "&gt;");
187 	    break;
188 	case '&':
189 	    vb_appends(&r, "&amp;");
190 	    break;
191 	default:
192 	    nu8 = unicode_to_utf8(u, u8buf);
193 	    vb_append(&r, u8buf, nu8);
194 	    break;
195 	}
196 	caption += consumed;
197     }
198     return vb_consume(&r);
199 }
200 
201 /*
202  * Write a screen trace header to a stream.
203  * Returns the context to use with subsequent calls.
204  */
205 fps_status_t
fprint_screen_start(FILE * f,ptype_t ptype,unsigned opts,const char * caption,const char * printer_name,fps_t * fps_ret,void * wait_context)206 fprint_screen_start(FILE *f, ptype_t ptype, unsigned opts, const char *caption,
207 	const char *printer_name, fps_t *fps_ret, void *wait_context)
208 {
209     real_fps_t *fps;
210     int rv = FPS_STATUS_SUCCESS;
211     char *pt_spp;
212 
213     /* Non-text types can always generate blank output. */
214     if (ptype != P_TEXT) {
215 	opts |= FPS_EVEN_IF_EMPTY;
216     }
217 
218     /* Reset and save the state. */
219     fps = (real_fps_t *)Malloc(sizeof(real_fps_t));
220     fps->ptype = ptype;
221     fps->opts = opts;
222     fps->need_separator = false;
223     fps->broken = false;
224     fps->spp = 1;
225     fps->screens = 0;
226     fps->file = f;
227 
228     if (caption != NULL) {
229 	char *xcaption;
230 	char *ts = strstr(caption, "%T%");
231 
232 	if (ts != NULL) {
233 	    time_t t = time(NULL);
234 	    struct tm *tm = localtime(&t);
235 
236 	    xcaption = xs_buffer("%.*s" "%04d-%02d-%02d %02d:%02d:%02d" "%s",
237 		    (int)(ts - caption), caption,
238 		    tm->tm_year + 1900,
239 		    tm->tm_mon + 1,
240 		    tm->tm_mday,
241 		    tm->tm_hour,
242 		    tm->tm_min,
243 		    tm->tm_sec,
244 		    ts + 3);
245 	} else {
246 	    xcaption = NewString(caption);
247 	}
248 	fps->caption = xcaption;
249     } else {
250 	fps->caption = NULL;
251     }
252 
253     if (printer_name != NULL && printer_name[0]) {
254 	fps->printer_name = NewString(printer_name);
255     } else {
256 	fps->printer_name = NULL;
257     }
258 
259     switch (ptype) {
260     case P_RTF: {
261 	char *pt_font = get_resource(ResPrintTextFont);
262 	char *pt_size = get_resource(ResPrintTextSize);
263 	int pt_nsize;
264 
265 	if (pt_font == NULL) {
266 	    pt_font = "Courier New";
267 	}
268 	if (pt_size == NULL) {
269 	    pt_size = "8";
270 	}
271 	pt_nsize = atoi(pt_size);
272 	if (pt_nsize <= 0) {
273 	    pt_nsize = 8;
274 	}
275 
276 	if (fprintf(f, "{\\rtf1\\ansi\\ansicpg%u\\deff0\\deflang1033{"
277 		    "\\fonttbl{\\f0\\fmodern\\fprq1\\fcharset0 %s;}}\n"
278 		    "\\viewkind4\\uc1\\pard\\f0\\fs%d ",
279 #if defined(_WIN32) /*[*/
280 		    GetACP(),
281 #else /*][*/
282 		    1252, /* the number doesn't matter */
283 #endif /*]*/
284 		    pt_font, pt_nsize * 2) < 0) {
285 	    rv = FPS_STATUS_ERROR;
286 	}
287 	if (rv == FPS_STATUS_SUCCESS && fps->caption != NULL) {
288 	    char *hcaption = rtf_caption(fps->caption);
289 
290 	    if (fprintf(f, "%s\\par\\par\n", hcaption) < 0) {
291 		rv = FPS_STATUS_ERROR;
292 	    }
293 	    Free(hcaption);
294 	}
295 	break;
296     }
297     case P_HTML: {
298 	char *hcaption = NULL;
299 
300 	/* Make the caption HTML-safe. */
301 	if (fps->caption != NULL) {
302 	    hcaption = html_caption(fps->caption);
303 	}
304 
305 	/* Print the preamble. */
306 	if (!(opts & FPS_NO_HEADER) &&
307 		fprintf(f, "<html>\n"
308 		   "<head>\n"
309 		   " <meta http-equiv=\"Content-Type\" "
310 		     "content=\"text/html; charset=UTF-8\">\n"
311 		   "</head>\n"
312 		   " <body>\n") < 0) {
313 	    rv = FPS_STATUS_ERROR;
314 	}
315 	if (rv == FPS_STATUS_SUCCESS && hcaption) {
316 	    if (fprintf(f, "<p>%s</p>\n", hcaption) < 0) {
317 		rv = FPS_STATUS_ERROR;
318 	    }
319 	    Free(hcaption);
320 	}
321 	break;
322     }
323     case P_TEXT:
324 	if (fps->caption != NULL) {
325 	    if (fprintf(f, "%s\n\n", fps->caption) < 0) {
326 		rv = FPS_STATUS_ERROR;
327 	    }
328 	}
329 	break;
330     case P_GDI:
331 #if defined(_WIN32) /*[*/
332 	switch (gdi_print_start(printer_name, opts, wait_context)) {
333 	case GDI_STATUS_SUCCESS:
334 	    break;
335 	case GDI_STATUS_ERROR:
336 	    rv = FPS_STATUS_ERROR;
337 	    break;
338 	case GDI_STATUS_CANCEL:
339 	    rv = FPS_STATUS_CANCEL;
340 	    break;
341 	case GDI_STATUS_WAIT:
342 	    rv = FPS_STATUS_WAIT;
343 	    break;
344 	}
345 #else /*][*/
346 	assert(ptype != P_GDI);
347 #endif /*]*/
348 	break;
349     case P_NONE:
350 	assert(ptype != P_NONE);
351 	break;
352     }
353 
354     /* Set up screens-per-page. */
355     pt_spp = get_resource(ResPrintTextScreensPerPage);
356     if (pt_spp != NULL) {
357 	fps->spp = atoi(pt_spp);
358 	if (fps->spp < 1 || fps->spp > 5) {
359 	    fps->spp = 1;
360 	}
361     }
362 
363     if (rv != FPS_STATUS_SUCCESS) {
364 	/* We've failed; there's no point in returning the context. */
365 	Free(fps->caption);
366 	Free(fps->printer_name);
367 	Free(fps);
368 	*fps_ret = NULL;
369     } else {
370 	*fps_ret = (fps_t)(void *)fps;
371     }
372 
373     return rv;
374 }
375 
376 #define FAIL do { \
377     rv = -1; \
378     goto done; \
379 } while(false)
380 
381 /*
382  * Add a screen image to a stream.
383  *
384  * Returns 0 for no screen written, 1 for screen written, -1 for error.
385  */
386 fps_status_t
fprint_screen_body(fps_t ofps)387 fprint_screen_body(fps_t ofps)
388 {
389     real_fps_t *fps = (real_fps_t *)(void *)ofps;
390     register int i;
391     ucs4_t uc;
392     int ns = 0;
393     int nr = 0;
394     bool any = false;
395     int fa_addr = find_field_attribute(0);
396     unsigned char fa = ea_buf[fa_addr].fa;
397     int fa_fg, current_fg;
398     int fa_bg, current_bg;
399     bool fa_high, current_high;
400     bool fa_ital, current_ital;
401     bool mi;
402 #if defined(_WIN32) /*[*/
403     gdi_header_t h;
404 #endif /*]*/
405     fps_status_t rv = FPS_STATUS_SUCCESS;
406 
407     /* Quick short-circuit. */
408     if (fps == NULL || fps->broken) {
409 	return FPS_STATUS_ERROR;
410     }
411 
412     mi = ((fps->opts & FPS_MODIFIED_ITALIC)) != 0;
413     if (ea_buf[fa_addr].fg) {
414 	fa_fg = ea_buf[fa_addr].fg & 0x0f;
415     } else {
416 	fa_fg = color_from_fa(fa);
417     }
418     current_fg = fa_fg;
419 
420     if (ea_buf[fa_addr].bg) {
421 	fa_bg = ea_buf[fa_addr].bg & 0x0f;
422     } else {
423 	fa_bg = HOST_COLOR_BLACK;
424     }
425     current_bg = fa_bg;
426 
427     if (ea_buf[fa_addr].gr & GR_INTENSIFY) {
428 	fa_high = true;
429     } else {
430 	fa_high = FA_IS_HIGH(fa);
431     }
432     current_high = fa_high;
433     fa_ital = mi && FA_IS_MODIFIED(fa);
434     current_ital = fa_ital;
435 
436     switch (fps->ptype) {
437     case P_RTF:
438 	if (fps->need_separator) {
439 	    if (fps->screens < fps->spp) {
440 		if (fprintf(fps->file, "\\par\n") < 0) {
441 		    FAIL;
442 		}
443 	    } else {
444 		if (fprintf(fps->file, "\n\\page\n") < 0) {
445 		    FAIL;
446 		}
447 		fps->screens = 0;
448 	    }
449 	}
450 	if (current_high) {
451 	    if (fprintf(fps->file, "\\b ") < 0) {
452 		FAIL;
453 	    }
454 	}
455 	break;
456     case P_HTML:
457 	if (fprintf(fps->file, "  <table border=0>"
458 	       "<tr bgcolor=black><td>"
459 	       "<pre><span style=\"color:%s;"
460 				   "background:%s;"
461 				   "font-weight:%s;"
462 				   "font-style:%s\">",
463 	       html_color(current_fg),
464 	       html_color(current_bg),
465 	       current_high? "bold": "normal",
466 	       current_ital? "italic": "normal") < 0) {
467 	    FAIL;
468 	}
469 	break;
470     case P_TEXT:
471 	if (fps->need_separator) {
472 	    if ((fps->opts & FPS_FF_SEP) && fps->screens >= fps->spp) {
473 		if (fputc('\f', fps->file) < 0) {
474 		    FAIL;
475 		}
476 		fps->screens = 0;
477 	    } else {
478 		for (i = 0; i < COLS; i++) {
479 		    if (fputc('=', fps->file) < 0) {
480 			FAIL;
481 		    }
482 		}
483 		if (fputc('\n', fps->file) < 0) {
484 		    FAIL;
485 		}
486 	    }
487 	}
488 	break;
489 #if defined(_WIN32) /*[*/
490     case P_GDI:
491 	/*
492 	 * Write the current screen buffer to the file.
493 	 * We will read it back and print it when we are done.
494 	 */
495 	h.signature = GDI_SIGNATURE;
496 	h.rows = ROWS;
497 	h.cols = COLS;
498 	if (fwrite(&h, sizeof(h), 1, fps->file) != 1) {
499 	    FAIL;
500 	}
501 	if (fwrite(ea_buf, sizeof(struct ea), ROWS * COLS, fps->file)
502 		    != ROWS * COLS) {
503 	    FAIL;
504 	}
505 	fflush(fps->file);
506 	rv = FPS_STATUS_SUCCESS_WRITTEN;
507 	goto done;
508 #endif /*]*/
509     default:
510 	break;
511     }
512 
513     fps->need_separator = false;
514 
515     for (i = 0; i < ROWS*COLS; i++) {
516 	char mb[16];
517 	int nmb;
518 
519 	uc = 0;
520 
521 	if (i && !(i % COLS)) {
522 	    if (fps->ptype == P_HTML) {
523 		if (fputc('\n', fps->file) < 0) {
524 		    FAIL;
525 		}
526 	    } else {
527 		nr++;
528 	    }
529 	    ns = 0;
530 	}
531 	if (ea_buf[i].fa) {
532 	    uc = ' ';
533 	    fa = ea_buf[i].fa;
534 	    if (ea_buf[i].fg) {
535 		fa_fg = ea_buf[i].fg & 0x0f;
536 	    } else {
537 		fa_fg = color_from_fa(fa);
538 	    }
539 	    if (ea_buf[i].bg) {
540 		fa_bg = ea_buf[i].bg & 0x0f;
541 	    } else {
542 		fa_bg = HOST_COLOR_BLACK;
543 	    }
544 	    if (ea_buf[i].gr & GR_INTENSIFY) {
545 		fa_high = true;
546 	    } else {
547 		fa_high = FA_IS_HIGH(fa);
548 	    }
549 	    fa_ital = mi && FA_IS_MODIFIED(fa);
550 	}
551 	if (FA_IS_ZERO(fa)) {
552 	    if (ctlr_dbcs_state(i) == DBCS_LEFT) {
553 		uc = 0x3000;
554 	    } else {
555 		uc = ' ';
556 	    }
557 	} else if (is_nvt(&ea_buf[i], false, &uc)) {
558 	    /* NVT-mode text. */
559 	    if (ctlr_dbcs_state(i) == DBCS_RIGHT) {
560 		continue;
561 	    }
562 	} else {
563 	    /* Convert EBCDIC to Unicode. */
564 	    switch (ctlr_dbcs_state(i)) {
565 	    case DBCS_NONE:
566 	    case DBCS_SB:
567 		uc = ebcdic_to_unicode(ea_buf[i].ec, ea_buf[i].cs, EUO_NONE);
568 		if (uc == 0) {
569 		    uc = ' ';
570 		}
571 		break;
572 	    case DBCS_LEFT:
573 		uc = ebcdic_to_unicode((ea_buf[i].ec << 8) | ea_buf[i + 1].ec,
574 			CS_BASE, EUO_NONE);
575 		if (uc == 0) {
576 		    uc = 0x3000;
577 		}
578 		break;
579 	    case DBCS_RIGHT:
580 		/* skip altogether, we took care of it above */
581 		continue;
582 	    default:
583 		uc = ' ';
584 		break;
585 	    }
586 	}
587 
588 	/* Translate to a type-specific format and write it out. */
589 	if (uc == ' ' && fps->ptype != P_HTML) {
590 	    ns++;
591 	} else if (uc == 0x3000) {
592 	    if (fps->ptype == P_HTML) {
593 		if (fprintf(fps->file, "  ") < 0) {
594 		    FAIL;
595 		}
596 	    } else {
597 		ns += 2;
598 	    }
599 	} else {
600 	    while (nr) {
601 		if (fps->ptype == P_RTF)
602 		    if (fprintf(fps->file, "\\par") < 0) {
603 			FAIL;
604 		    }
605 		if (fputc('\n', fps->file) < 0) {
606 		    FAIL;
607 		}
608 		nr--;
609 	    }
610 	    while (ns) {
611 		if (fps->ptype == P_RTF) {
612 		    if (fprintf(fps->file, "\\~") < 0) {
613 			FAIL;
614 		    }
615 		} else {
616 		    if (fputc(' ', fps->file) < 0) {
617 			FAIL;
618 		    }
619 		}
620 		ns--;
621 	    }
622 	    if (fps->ptype == P_RTF) {
623 		bool high;
624 
625 		if (ea_buf[i].gr & GR_INTENSIFY) {
626 		    high = true;
627 		} else {
628 		    high = fa_high;
629 		}
630 		if (high != current_high) {
631 		    if (high) {
632 			if (fprintf(fps->file, "\\b ") < 0) {
633 			    FAIL;
634 			}
635 		    } else {
636 			if (fprintf(fps->file, "\\b0 ") < 0) {
637 			    FAIL;
638 			}
639 		    }
640 		    current_high = high;
641 		}
642 	    }
643 	    if (fps->ptype == P_HTML) {
644 		int fg_color, bg_color;
645 		bool high;
646 
647 		if (ea_buf[i].fg) {
648 		    fg_color = ea_buf[i].fg & 0x0f;
649 		} else {
650 		    fg_color = fa_fg;
651 		}
652 		if (ea_buf[i].bg) {
653 		    bg_color = ea_buf[i].bg & 0x0f;
654 		} else {
655 		    bg_color = fa_bg;
656 		}
657 		if (ea_buf[i].gr & GR_REVERSE) {
658 		    int tmp;
659 
660 		    tmp = fg_color;
661 		    fg_color = bg_color;
662 		    bg_color = tmp;
663 		}
664 
665 		if (i == cursor_addr) {
666 		    fg_color = (bg_color == HOST_COLOR_RED)?
667 			HOST_COLOR_BLACK: bg_color;
668 		    bg_color = HOST_COLOR_RED;
669 		}
670 		if (ea_buf[i].gr & GR_INTENSIFY) {
671 		    high = true;
672 		} else {
673 		    high = fa_high;
674 		}
675 
676 		if (fg_color != current_fg ||
677 		    bg_color != current_bg ||
678 		    high != current_high ||
679 		    fa_ital != current_ital) {
680 		    if (fprintf(fps->file,
681 				"</span><span "
682 				"style=\"color:%s;"
683 				"background:%s;"
684 				"font-weight:%s;"
685 				"font-style:%s\">",
686 				html_color(fg_color),
687 				html_color(bg_color),
688 				high? "bold": "normal",
689 				fa_ital? "italic": "normal") < 0) {
690 			FAIL;
691 		    }
692 		    current_fg = fg_color;
693 		    current_bg = bg_color;
694 		    current_high = high;
695 		    current_ital = fa_ital;
696 		}
697 	    }
698 	    any = true;
699 	    if (fps->ptype == P_RTF) {
700 		if (uc & ~0x7f) {
701 		    if (fprintf(fps->file, "\\u%u?", uc) < 0) {
702 			FAIL;
703 		    }
704 		} else {
705 		    nmb = unicode_to_multibyte(uc, mb, sizeof(mb));
706 		    if (mb[0] == '\\' || mb[0] == '{' || mb[0] == '}') {
707 			if (fprintf(fps->file, "\\%c", mb[0]) < 0) {
708 			    FAIL;
709 			}
710 		    } else if (mb[0] == '-') {
711 			if (fprintf(fps->file, "\\_") < 0) {
712 			    FAIL;
713 			}
714 		    } else if (mb[0] == ' ') {
715 			if (fprintf(fps->file, "\\~") < 0) {
716 			    FAIL;
717 			}
718 		    } else {
719 			if (fputc(mb[0], fps->file) < 0) {
720 			    FAIL;
721 			}
722 		    }
723 		}
724 	    } else if (fps->ptype == P_HTML) {
725 		if (uc == '<') {
726 		    if (fprintf(fps->file, "&lt;") < 0) {
727 			FAIL;
728 		    }
729 		} else if (uc == '&') {
730 		    if (fprintf(fps->file, "&amp;") < 0) {
731 			FAIL;
732 		    }
733 		} else if (uc == '>') {
734 		    if (fprintf(fps->file, "&gt;") < 0) {
735 			FAIL;
736 		    }
737 		} else {
738 		    nmb = unicode_to_utf8(uc, mb);
739 		    {
740 			int k;
741 
742 			for (k = 0; k < nmb; k++) {
743 			    if (fputc(mb[k], fps->file) < 0) {
744 				FAIL;
745 			    }
746 			}
747 		    }
748 		}
749 	    } else {
750 		nmb = unicode_to_multibyte(uc, mb, sizeof(mb));
751 		if (fputs(mb, fps->file) < 0) {
752 		    FAIL;
753 		}
754 	    }
755 	}
756     }
757 
758     if (fps->ptype == P_HTML) {
759 	if (fputc('\n', fps->file) < 0) {
760 	    FAIL;
761 	}
762     } else {
763 	nr++;
764     }
765     if (!any && !(fps->opts & FPS_EVEN_IF_EMPTY) && fps->ptype == P_TEXT) {
766 	return FPS_STATUS_SUCCESS;
767     }
768     while (nr) {
769 	if (fps->ptype == P_RTF) {
770 	    if (fprintf(fps->file, "\\par") < 0) {
771 		FAIL;
772 	    }
773 	}
774 	if (fps->ptype == P_TEXT) {
775 	    if (fputc('\n', fps->file) < 0) {
776 		FAIL;
777 	    }
778 	}
779 	nr--;
780     }
781     if (fps->ptype == P_HTML) {
782 	if (fprintf(fps->file, "%s</span></pre></td></tr>\n  </table>\n",
783 		    current_high? "</b>": "") < 0) {
784 	    FAIL;
785 	}
786     }
787     fps->need_separator = true;
788     fps->screens++;
789     rv = FPS_STATUS_SUCCESS_WRITTEN; /* wrote a screen */
790 
791 done:
792     if (FPS_IS_ERROR(rv)) {
793 	fps->broken = true;
794     }
795     return rv;
796 }
797 
798 #undef FAIL
799 
800 /*
801  * Finish writing a multi-screen image.
802  * Returns 0 success, -1 for error. In either case, the context is freed.
803  */
804 fps_status_t
fprint_screen_done(fps_t * ofps)805 fprint_screen_done(fps_t *ofps)
806 {
807     real_fps_t *fps = (real_fps_t *)*(void **)ofps;
808     int rv = FPS_STATUS_SUCCESS;
809 
810     if (fps == NULL) {
811 	return FPS_STATUS_ERROR;
812     }
813 
814     if (!fps->broken) {
815 	switch (fps->ptype) {
816 	case P_RTF:
817 	    if (fprintf(fps->file, "\n}\n%c", 0) < 0) {
818 		rv = FPS_STATUS_ERROR;
819 	    }
820 	    break;
821 	case P_HTML:
822 	    if (!(fps->opts & FPS_NO_HEADER) &&
823 		    fprintf(fps->file, " </body>\n</html>\n") < 0) {
824 		rv = FPS_STATUS_ERROR;
825 	    }
826 	    break;
827 #if defined(_WIN32) /*[*/
828 	case P_GDI:
829 	    vtrace("Printing to GDI printer\n");
830 	    if (gdi_print_finish(fps->file, fps->caption) < 0) {
831 		rv = FPS_STATUS_ERROR;
832 	    }
833 	    break;
834 #endif /*]*/
835 	default:
836 	    break;
837 	}
838     }
839 
840     /* Done with the context. */
841     Free(fps->caption);
842     Free(fps->printer_name);
843     memset(fps, '\0', sizeof(*fps));
844     Free(*(void **)ofps);
845     *(void **)ofps = NULL;
846 
847     return rv;
848 }
849 
850 /*
851  * Write a header, screen image, and trailer to a file.
852  */
853 fps_status_t
fprint_screen(FILE * f,ptype_t ptype,unsigned opts,const char * caption,const char * printer_name,void * wait_context)854 fprint_screen(FILE *f, ptype_t ptype, unsigned opts, const char *caption,
855 	const char *printer_name, void *wait_context)
856 {
857     fps_t fps;
858     fps_status_t srv;
859     fps_status_t srv_body;
860 
861     srv = fprint_screen_start(f, ptype, opts, caption, printer_name, &fps,
862 	    wait_context);
863     if (FPS_IS_ERROR(srv) || srv == FPS_STATUS_WAIT) {
864 	return srv;
865     }
866     srv_body = fprint_screen_body(fps);
867     if (FPS_IS_ERROR(srv_body)) {
868 	fprint_screen_done(&fps);
869 	return srv_body;
870     }
871     srv = fprint_screen_done(&fps);
872     if (FPS_IS_ERROR(srv)) {
873 	return srv;
874     }
875     return srv_body;
876 }
877