1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "glk/tads/tads2/character_map.h"
24 #include "glk/tads/tads2/debug.h"
25 #include "glk/tads/tads2/error.h"
26 #include "glk/tads/tads2/memory_cache.h"
27 #include "glk/tads/tads2/os.h"
28 #include "glk/tads/tads2/run.h"
29 #include "glk/tads/tads2/text_io.h"
30 #include "glk/tads/tads2/vocabulary.h"
31 #include "glk/tads/os_glk.h"
32 
33 namespace Glk {
34 namespace TADS {
35 namespace TADS2 {
36 
37 /* Forward declarations */
38 struct out_stream_info;
39 
40 /*
41  *   use our own isxxx - anything outside the US ASCII range is not reliably
42  *   classifiable by the normal C isxxx routines
43  */
44 #define outissp(c) (((uchar)(c)) <= 127 && Common::isSpace((uchar)(c)))
45 #define outisal(c) (((uchar)(c)) <= 127 && Common::isAlpha((uchar)(c)))
46 #define outisdg(c) (((uchar)(c)) <= 127 && Common::isDigit((uchar)(c)))
47 #define outisup(c) (((uchar)(c)) <= 127 && Common::isUpper((uchar)(c)))
48 #define outislo(c) (((uchar)(c)) <= 127 && Common::isLower((uchar)(c)))
49 
50 
51 /*
52  *   Turn on formatter-level MORE mode, EXCEPT under any of the following
53  *   conditions:
54  *
55  *   - this is a MAC OS port
56  *.  - this is an HTML TADS interpreter
57  *.  - USE_OS_LINEWRAP is defined
58  *
59  *   Formatter-level MORE mode and formatter-level line wrapping go together;
60  *   you can't have one without the other.  So, if USE_OS_LINEWRAP is
61  *   defined, we must also use OS-level MORE mode, which means we don't want
62  *   formatter-level MORE mode.
63  *
64  *   For historical reasons, we check specifically for MAC_OS.  This was the
65  *   first platform for which OS-level MORE mode and OS-level line wrapping
66  *   were invented; at the time, we foolishly failed to anticipate that more
67  *   platforms might eventually come along with the same needs, so we coded a
68  *   test for MAC_OS rather than some more abstract marker.  For
69  *   compatibility, we retain this specific test.
70  *
71  *   USE_OS_LINEWRAP is intended as the more abstract marker we should
72  *   originally have used.  A port should #define USE_OS_LINEWRAP in its
73  *   system-specific os_xxx.h header to turn on OS-level line wrapping and
74  *   OS-level MORE mode.  Ports should avoid adding new #ifndef tests for
75  *   specific platforms here; we've only retained the MAC_OS test because we
76  *   don't want to break the existing MAC_OS port.
77  */
78 #ifndef MAC_OS
79 # ifndef USE_HTML
80 #  ifndef USE_OS_LINEWRAP
81 #   define USE_MORE                   /* activate formatter-level more-mode */
82 #  endif /* USE_OS_LINEWRAP */
83 # endif /* USE_HTML */
84 #endif /* MAC_OS */
85 
86 /*
87  *   In HTML mode, don't use MORE mode.  Note that we explicitly turn MORE
88  *   mode OFF, even though we won't have turned it on above, because it might
89  *   have been turned on by an os_xxx.h header.  This is here for historical
90  *   reasons; in particular, some of the HTML interpreter builds include
91  *   headers that were originally written for the normal builds for those
92  *   same platforms, and those original headers explicitly #define USE_MORE
93  *   somewhere.  So, to be absolutely sure we get it right here, we have to
94  *   explicitly turn off USE_MORE when compiling for HTML mode.
95  */
96 #ifdef USE_HTML
97 # ifdef USE_MORE
98 #  undef USE_MORE
99 # endif
100 #endif
101 
102 /*
103  *   QSPACE is the special character for a quoted space (internally, the
104  *   sequence "\ " (backslash-space) is converted to QSPACE).  It must not
105  *   be any printable character.  The value here may need to be changed in
106  *   the extremely unlikely event that TADS is ever ported to an EBCDIC
107  *   machine.
108  */
109 #define QSPACE 26
110 
111 /*
112  *   QTAB is a special hard tab character indicator.  We use this when we
113  *   need to generate a hard tab to send to the underlying output layer
114  *   (in particular, we use this to send hard tabs to the HTML formatter
115  *   when we're in HTML mode).
116  */
117 #define QTAB 25
118 
119 
120 /* maximum width of the display */
121 #define MAXWIDTH  OS_MAXWIDTH
122 
123 
124 /* ------------------------------------------------------------------------ */
125 /*
126  *   Globals and statics.  These should really be moved into a context
127  *   structure, so that the output formatter subsystem could be shared
128  *   among multiple clients.  For now, there's no practical problem using
129  *   statics, because we only need a single output subsystem at one time.
130  */
131 
132 /* current script (command input) file */
133 extern osfildef *scrfp;
134 
135 /*
136  *   This should be TRUE if the output should have two spaces after a
137  *   period (or other such punctuation. It should generally be TRUE for
138  *   fixed-width fonts, and FALSE for proportional fonts.
139  */
140 static int doublespace = 1;
141 
142 /*
143  *   Log file handle and name.  If we're copying output to a log file,
144  *   these will tell us about the file.
145  */
146 osfildef *logfp;
147 static char logfname[OSFNMAX];
148 
149 /* flag indicating whether output has occurred since last check */
150 static uchar outcnt;
151 
152 /* flag indicating whether hidden output has occurred */
153 static uchar hidout;
154 
155 /* flag indicating whether to show (TRUE) or hide (FALSE) output */
156 static uchar outflag;
157 
158 /* flag indicating whether output is hidden for debugging purposes */
159 int dbghid;
160 
161 /*
162  *   Current recursion level in formatter invocation
163  */
164 static int G_recurse = 0;
165 
166 /* active stream in current recursion level */
167 static out_stream_info *G_cur_stream;
168 
169 /* watchpoint mode flag */
170 static uchar outwxflag;
171 
172 /*
173  *   User filter function.  When this function is set, we'll invoke this
174  *   function for each string that's displayed through the output
175  *   formatter.
176  */
177 static objnum G_user_filter = MCMONINV;
178 
179 
180 /* ------------------------------------------------------------------------ */
181 /*
182  *   Hack to run with TADS 2.0 with minimal reworking.  Rather than using
183  *   an allocated output layer context, store our subsystem context
184  *   information in some statics.  This is less clean than using a real
185  *   context, but doesn't create any practical problems as we don't need
186  *   to share the output formatter subsystem among multiple simultaneous
187  *   callers.
188  */
189 static runcxdef *runctx;                               /* execution context */
190 static uchar    *fmsbase;                        /* format string area base */
191 static uchar    *fmstop;                          /* format string area top */
192 static objnum    cmdActor;                                 /* current actor */
193 
194 /* forward declarations of static functions */
195 static void outstring_stream(out_stream_info *stream, const char *s);
196 static void outchar_noxlat_stream(out_stream_info *stream, char c);
197 static char out_parse_entity(char *outbuf, size_t outbuf_size, const char **sp, size_t *slenp);
198 
199 
200 /* ------------------------------------------------------------------------ */
201 /*
202  *   HTML lexical analysis mode
203  */
204 #define HTML_MODE_NORMAL  0                    /* normal text, not in a tag */
205 #define HTML_MODE_TAG     1                         /* parsing inside a tag */
206 #define HTML_MODE_SQUOTE  2           /* in a single-quoted string in a tag */
207 #define HTML_MODE_DQUOTE  3           /* in a double-quoted string in a tag */
208 
209 /*
210  *   HTML parsing mode flag for <BR> tags.  We defer these until we've
211  *   read the full tag in order to obey an HEIGHT attribute we find.  When
212  *   we encounter a <BR>, we figure out whether we think we'll need a
213  *   flush or a blank line; if we find a HEIGHT attribute, we may change
214  *   this opinion.
215  */
216 #define HTML_DEFER_BR_NONE   0                           /* no pending <BR> */
217 #define HTML_DEFER_BR_FLUSH  1                   /* only need an outflush() */
218 #define HTML_DEFER_BR_BLANK  2                        /* need an outblank() */
219 
220 /*
221  *   If we're compiling for an HTML-enabled underlying output subsystem,
222  *   we want to call the underlying OS layer when switching in and out of
223  *   HTML mode.  If the underlying system doesn't process HTML, we don't
224  *   need to let it know anything about HTML mode.
225  */
226 #ifdef USE_HTML
227 # define out_start_html(stream) os_start_html()
228 # define out_end_html(stream)   os_end_html()
229 #else
230 # define out_start_html(stream)
231 # define out_end_html(stream)
232 #endif
233 
234 
235 /* ------------------------------------------------------------------------ */
236 /*
237  *   Output formatter stream state structure.  This structure encapsulates
238  *   the state of an individual output stream.
239  */
240 struct out_stream_info {
241 	/* low-level display routine (va_list version) */
242 	void (*do_print)(out_stream_info *stream, const char *str);
243 
244 	/* current line position and output column */
245 	uchar linepos;
246 	uchar linecol;
247 
248 	/* number of lines on the screen (since last MORE prompt) */
249 	int linecnt;
250 
251 	/* output buffer */
252 	char linebuf[MAXWIDTH];
253 
254 	/*
255 	 *   attribute buffer - we keep one attribute entry for each character in
256 	 *   the line buffer
257 	 */
258 	int attrbuf[MAXWIDTH];
259 
260 	/* current attribute for text we're buffering into linebuf */
261 	int cur_attr;
262 
263 	/* last attribute we wrote to the osifc layer */
264 	int os_attr;
265 
266 	/* CAPS mode - next character output is converted to upper-case */
267 	uchar capsflag;
268 
269 	/* NOCAPS mode - next character output is converted to lower-case */
270 	uchar nocapsflag;
271 
272 	/* ALLCAPS mode - all characters output are converted to upper-case */
273 	uchar allcapsflag;
274 
275 	/* capture information */
276 	mcmcxdef *capture_ctx;           /* memory context to use for capturing */
277 	mcmon     capture_obj;                /* object holding captured output */
278 	uint      capture_ofs;                /* write offset in capture object */
279 	int       capturing;                 /* true -> we are capturing output */
280 
281 	/* "preview" state for line flushing */
282 	int preview;
283 
284 	/* flag indicating that we just flushed a new line */
285 	int just_did_nl;
286 
287 	/* this output stream uses "MORE" mode */
288 	int use_more_mode;
289 
290 	/*
291 	 *   This output stream uses OS-level line wrapping - if this is set,
292 	 *   the output formatter will not insert a newline at the end of a
293 	 *   line that it's flushing for word wrapping, but will instead let
294 	 *   the underlying OS display layer handle the wrapping.
295 	 */
296 	int os_line_wrap;
297 
298 	/*
299 	 *   Flag indicating that the underlying output system wants to
300 	 *   receive its output as HTML.
301 	 *
302 	 *   If this is true, we'll pass through HTML to the underlying output
303 	 *   system, and in addition generate HTML sequences for certain
304 	 *   TADS-native escapes (for example, we'll convert the "\n" sequence
305 	 *   to a <BR> sequence).
306 	 *
307 	 *   If this is false, we'll do just the opposite: we'll remove HTML
308 	 *   from the output stream and convert it into normal text sequences.
309 	 */
310 	int html_target;
311 
312 	/*
313 	 *   Flag indicating that the target uses plain text.  If this flag is
314 	 *   set, we won't add the OS escape codes for highlighted characters.
315 	 */
316 	int plain_text_target;
317 
318 	/*
319 	 *   Flag indicating that the caller is displaying HTML.  We always
320 	 *   start off in text mode; the client can switch to HTML mode by
321 	 *   displaying a special escape sequence, and can switch back to text
322 	 *   mode by displaying a separate special escape sequence.
323 	 */
324 	int html_mode;
325 
326 	/* current lexical analysis mode */
327 	unsigned int html_mode_flag;
328 
329 	/* <BR> defer mode */
330 	unsigned int html_defer_br;
331 
332 	/*
333 	 *   HTML "ignore" mode - we suppress all output when parsing the
334 	 *   contents of a <TITLE> or <ABOUTBOX> tag
335 	 */
336 	int html_in_ignore;
337 
338 	/*
339 	 *   HTML <TITLE> mode - when we're in this mode, we're gathering the
340 	 *   title (i.e., we're inside a <TITLE> tag's contents).  We'll copy
341 	 *   characters to the title buffer rather than the normal output
342 	 *   buffer, and then call os_set_title() when we reach the </TITLE>
343 	 *   tag.
344 	 */
345 	int html_in_title;
346 
347 	/* buffer for the title */
348 	char html_title_buf[256];
349 
350 	/* pointer to next available character in title buffer */
351 	char *html_title_ptr;
352 
353 	/* quoting level */
354 	int html_quote_level;
355 
356 	/* PRE nesting level */
357 	int html_pre_level;
358 
359 	/*
360 	 *   Parsing mode flag for ALT attributes.  If we're parsing a tag
361 	 *   that allows ALT, such as IMG or SOUND, we'll set this flag, then
362 	 *   insert the ALT text if we encounter it during parsing.
363 	 */
364 	int html_allow_alt;
365 };
366 
367 /*
368  *   Default output converter.  This is the output converter for the
369  *   standard display.  Functions in the public interface that do not
370  *   specify an output converter will use this converter by default.
371  */
372 static out_stream_info G_std_disp;
373 
374 /*
375  *   Log file converter.  This is the output converter for a log file.
376  *   Whenever we open a log file, we'll initialize this converter; as we
377  *   display text to the main display, we'll also copy it to the log file.
378  *
379  *   We maintain an entire separate conversion context for the log file,
380  *   so that we can perform a different set of conversions on it.  We may
381  *   want, for example, to pass HTML text through to the OS display
382  *   subsystem (this is the case for the HTML-enabled interpreter), but
383  *   we'll still want to convert log file output to text.  By keeping a
384  *   separate display context for the log file, we can format output to
385  *   the log file using an entirely different style than we do for the
386  *   display.
387  */
388 static out_stream_info G_log_disp;
389 
390 
391 /* ------------------------------------------------------------------------ */
392 /*
393  *   low-level output handlers for the standard display and log file
394  */
395 
396 /* standard display printer */
do_std_print(out_stream_info * stream,const char * str)397 static void do_std_print(out_stream_info *stream, const char *str)
398 {
399 	VARUSED(stream);
400 
401 	/* display the text through the OS layer */
402 	os_printz(str);
403 }
404 
405 /* log file printer */
do_log_print(out_stream_info * stream,const char * str)406 static void do_log_print(out_stream_info *stream, const char *str)
407 {
408 	VARUSED(stream);
409 
410 	/* display to the log file */
411 	if (logfp != 0 && G_os_moremode)
412 	{
413 		os_fprintz(logfp, str);
414 		osfflush(logfp);
415 	}
416 }
417 
418 
419 /* ------------------------------------------------------------------------ */
420 /*
421  *   initialize a generic output formatter state structure
422  */
out_state_init(out_stream_info * stream)423 static void out_state_init(out_stream_info *stream)
424 {
425 	/* start out at the first column */
426 	stream->linepos = 0;
427 	stream->linecol = 0;
428 	stream->linebuf[0] = '\0';
429 
430 	/* set normal text attributes */
431 	stream->cur_attr = 0;
432 	stream->os_attr = 0;
433 
434 	/* start out at the first line */
435 	stream->linecnt = 0;
436 
437 	/* we're not in either "caps", "nocaps", or "allcaps" mode yet */
438 	stream->capsflag = stream->nocapsflag = stream->allcapsflag = FALSE;
439 
440 	/* we're not capturing yet */
441 	stream->capturing = FALSE;
442 	stream->capture_obj = MCMONINV;
443 
444 	/* we aren't previewing a line yet */
445 	stream->preview = 0;
446 
447 	/* we haven't flushed a new line yet */
448 	stream->just_did_nl = FALSE;
449 
450 	/* presume this stream does not use "MORE" mode */
451 	stream->use_more_mode = FALSE;
452 
453 	/* presume this stream uses formatter-level line wrapping */
454 	stream->os_line_wrap = FALSE;
455 
456 	/* assume that the underlying system is not HTML-enabled */
457 	stream->html_target = FALSE;
458 
459 	/* presume this target accepts OS highlighting sequences */
460 	stream->plain_text_target = FALSE;
461 
462 	/* start out in text mode */
463 	stream->html_mode = FALSE;
464 
465 	/* start out in "normal" lexical state */
466 	stream->html_mode_flag = HTML_MODE_NORMAL;
467 
468 	/* not in an ignored tag yet */
469 	stream->html_in_ignore = FALSE;
470 
471 	/* not in title mode yet */
472 	stream->html_in_title = FALSE;
473 
474 	/* not yet deferring line breaks */
475 	stream->html_defer_br = HTML_DEFER_BR_NONE;
476 
477 	/* not yet in quotes */
478 	stream->html_quote_level = 0;
479 
480 	/* not yet in a PRE block */
481 	stream->html_pre_level = 0;
482 
483 	/* not in an ALT tag yet */
484 	stream->html_allow_alt = FALSE;
485 }
486 
487 
488 /* ------------------------------------------------------------------------ */
489 /*
490  *   initialize a standard display stream
491  */
out_init_std(out_stream_info * stream)492 static void out_init_std(out_stream_info *stream)
493 {
494 	/* there's no user output filter function yet */
495 	out_set_filter(MCMONINV);
496 
497 	/* initialize the basic stream state */
498 	out_state_init(stream);
499 
500 	/* set up the low-level output routine */
501 	G_std_disp.do_print = do_std_print;
502 
503 #ifdef USE_MORE
504 	/*
505 	 *   We're compiled for MORE mode, and we're not compiling for an
506 	 *   underlying HTML formatting layer, so use MORE mode for the
507 	 *   standard display stream.
508 	 */
509 	stream->use_more_mode = TRUE;
510 #else
511 	/*
512 	 *   We're compiled for OS-layer (or HTML-layer) MORE handling.  For
513 	 *   this case, use OS-layer (or HTML-layer) line wrapping as well.
514 	 */
515 	stream->os_line_wrap = TRUE;
516 #endif
517 
518 #ifdef USE_HTML
519 	/*
520 	 *   if we're compiled for HTML mode, set the standard output stream
521 	 *   so that it knows it has an HTML target - this will ensure that
522 	 *   HTML tags are passed through to the underlying stream, and that
523 	 *   we generate HTML equivalents for our own control sequences
524 	 */
525 	stream->html_target = TRUE;
526 #endif
527 }
528 
529 /*
530  *   initialize a standard log file stream
531  */
out_init_log(out_stream_info * stream)532 static void out_init_log(out_stream_info *stream)
533 {
534 	/* initialize the basic stream state */
535 	out_state_init(stream);
536 
537 	/* set up the low-level output routine */
538 	stream->do_print = do_log_print;
539 
540 	/* use plain text in the log file stream */
541 	stream->plain_text_target = TRUE;
542 }
543 
544 
545 
546 /* ------------------------------------------------------------------------ */
547 /*
548  *   table of '&' character name sequences
549  */
550 struct amp_tbl_t {
551 	/* entity name */
552 	const char *cname;
553 
554 	/* HTML Unicode character value */
555 	uint        html_cval;
556 
557 	/* native character set expansion */
558 	char       *expan;
559 };
560 
561 /*
562  *   HTML entity mapping table.  When we're in non-HTML mode, we keep our
563  *   own expansion table so that we can map HTML entity names into the
564  *   local character set.
565  *
566  *   The entries in this table must be in sorted order (by HTML entity
567  *   name), because we use a binary search to find an entity name in the
568  *   table.
569  */
570 static struct amp_tbl_t amp_tbl[] = {
571 	{ "AElig", 198, 0 },
572 	{ "Aacute", 193, 0 },
573 	{ "Abreve", 258, 0 },
574 	{ "Acirc", 194, 0 },
575 	{ "Agrave", 192, 0 },
576 	{ "Alpha", 913, 0 },
577 	{ "Aogon", 260, 0 },
578 	{ "Aring", 197, 0 },
579 	{ "Atilde", 195, 0 },
580 	{ "Auml", 196, 0 },
581 	{ "Beta", 914, 0 },
582 	{ "Cacute", 262, 0 },
583 	{ "Ccaron", 268, 0 },
584 	{ "Ccedil", 199, 0 },
585 	{ "Chi", 935, 0 },
586 	{ "Dagger", 8225, 0 },
587 	{ "Dcaron", 270, 0 },
588 	{ "Delta", 916, 0 },
589 	{ "Dstrok", 272, 0 },
590 	{ "ETH", 208, 0 },
591 	{ "Eacute", 201, 0 },
592 	{ "Ecaron", 282, 0 },
593 	{ "Ecirc", 202, 0 },
594 	{ "Egrave", 200, 0 },
595 	{ "Eogon", 280, 0 },
596 	{ "Epsilon", 917, 0 },
597 	{ "Eta", 919, 0 },
598 	{ "Euml", 203, 0 },
599 	{ "Gamma", 915, 0 },
600 	{ "Iacute", 205, 0 },
601 	{ "Icirc", 206, 0 },
602 	{ "Igrave", 204, 0 },
603 	{ "Iota", 921, 0 },
604 	{ "Iuml", 207, 0 },
605 	{ "Kappa", 922, 0 },
606 	{ "Lacute", 313, 0 },
607 	{ "Lambda", 923, 0 },
608 	{ "Lcaron", 317, 0 },
609 	{ "Lstrok", 321, 0 },
610 	{ "Mu", 924, 0 },
611 	{ "Nacute", 323, 0 },
612 	{ "Ncaron", 327, 0 },
613 	{ "Ntilde", 209, 0 },
614 	{ "Nu", 925, 0 },
615 	{ "OElig", 338, 0 },
616 	{ "Oacute", 211, 0 },
617 	{ "Ocirc", 212, 0 },
618 	{ "Odblac", 336, 0 },
619 	{ "Ograve", 210, 0 },
620 	{ "Omega", 937, 0 },
621 	{ "Omicron", 927, 0 },
622 	{ "Oslash", 216, 0 },
623 	{ "Otilde", 213, 0 },
624 	{ "Ouml", 214, 0 },
625 	{ "Phi", 934, 0 },
626 	{ "Pi", 928, 0 },
627 	{ "Prime", 8243, 0 },
628 	{ "Psi", 936, 0 },
629 	{ "Racute", 340, 0 },
630 	{ "Rcaron", 344, 0 },
631 	{ "Rho", 929, 0 },
632 	{ "Sacute", 346, 0 },
633 	{ "Scaron", 352, 0 },
634 	{ "Scedil", 350, 0 },
635 	{ "Sigma", 931, 0 },
636 	{ "THORN", 222, 0 },
637 	{ "Tau", 932, 0 },
638 	{ "Tcaron", 356, 0 },
639 	{ "Tcedil", 354, 0 },
640 	{ "Theta", 920, 0 },
641 	{ "Uacute", 218, 0 },
642 	{ "Ucirc", 219, 0 },
643 	{ "Udblac", 368, 0 },
644 	{ "Ugrave", 217, 0 },
645 	{ "Upsilon", 933, 0 },
646 	{ "Uring", 366, 0 },
647 	{ "Uuml", 220, 0 },
648 	{ "Xi", 926, 0 },
649 	{ "Yacute", 221, 0 },
650 	{ "Yuml", 376, 0 },
651 	{ "Zacute", 377, 0 },
652 	{ "Zcaron", 381, 0 },
653 	{ "Zdot", 379, 0 },
654 	{ "Zeta", 918, 0 },
655 	{ "aacute", 225, 0 },
656 	{ "abreve", 259, 0 },
657 	{ "acirc", 226, 0 },
658 	{ "acute", 180, 0 },
659 	{ "aelig", 230, 0 },
660 	{ "agrave", 224, 0 },
661 	{ "alefsym", 8501, 0 },
662 	{ "alpha", 945, 0 },
663 	{ "amp", '&', 0 },
664 	{ "and", 8743, 0 },
665 	{ "ang", 8736, 0 },
666 	{ "aogon", 261, 0 },
667 	{ "aring", 229, 0 },
668 	{ "asymp", 8776, 0 },
669 	{ "atilde", 227, 0 },
670 	{ "auml", 228, 0 },
671 	{ "bdquo", 8222, 0 },
672 	{ "beta", 946, 0 },
673 	{ "breve", 728, 0 },
674 	{ "brvbar", 166, 0 },
675 	{ "bull", 8226, 0 },
676 	{ "cacute", 263, 0 },
677 	{ "cap", 8745, 0 },
678 	{ "caron", 711, 0 },
679 	{ "ccaron", 269, 0 },
680 	{ "ccedil", 231, 0 },
681 	{ "cedil", 184, 0 },
682 	{ "cent", 162, 0 },
683 	{ "chi", 967, 0 },
684 	{ "circ", 710, 0 },
685 	{ "clubs", 9827, 0 },
686 	{ "cong", 8773, 0 },
687 	{ "copy", 169, 0 },
688 	{ "crarr", 8629, 0 },
689 	{ "cup", 8746, 0 },
690 	{ "curren", 164, 0 },
691 	{ "dArr", 8659, 0 },
692 	{ "dagger", 8224, 0 },
693 	{ "darr", 8595, 0 },
694 	{ "dblac", 733, 0 },
695 	{ "dcaron", 271, 0 },
696 	{ "deg", 176, 0 },
697 	{ "delta", 948, 0 },
698 	{ "diams", 9830, 0 },
699 	{ "divide", 247, 0 },
700 	{ "dot", 729, 0 },
701 	{ "dstrok", 273, 0 },
702 	{ "eacute", 233, 0 },
703 	{ "ecaron", 283, 0 },
704 	{ "ecirc", 234, 0 },
705 	{ "egrave", 232, 0 },
706 	{ "emdash", 8212, 0 },
707 	{ "empty", 8709, 0 },
708 	{ "endash", 8211, 0 },
709 	{ "eogon", 281, 0 },
710 	{ "epsilon", 949, 0 },
711 	{ "equiv", 8801, 0 },
712 	{ "eta", 951, 0 },
713 	{ "eth", 240, 0 },
714 	{ "euml", 235, 0 },
715 	{ "exist", 8707, 0 },
716 	{ "fnof", 402, 0 },
717 	{ "forall", 8704, 0 },
718 	{ "frac12", 189, 0 },
719 	{ "frac14", 188, 0 },
720 	{ "frac34", 190, 0 },
721 	{ "frasl", 8260, 0 },
722 	{ "gamma", 947, 0 },
723 	{ "ge", 8805, 0 },
724 	{ "gt", '>', 0 },
725 	{ "hArr", 8660, 0 },
726 	{ "harr", 8596, 0 },
727 	{ "hearts", 9829, 0 },
728 	{ "hellip", 8230, 0 },
729 	{ "iacute", 237, 0 },
730 	{ "icirc", 238, 0 },
731 	{ "iexcl", 161, 0 },
732 	{ "igrave", 236, 0 },
733 	{ "image", 8465, 0 },
734 	{ "infin", 8734, 0 },
735 	{ "int", 8747, 0 },
736 	{ "iota", 953, 0 },
737 	{ "iquest", 191, 0 },
738 	{ "isin", 8712, 0 },
739 	{ "iuml", 239, 0 },
740 	{ "kappa", 954, 0 },
741 	{ "lArr", 8656, 0 },
742 	{ "lacute", 314, 0 },
743 	{ "lambda", 955, 0 },
744 	{ "lang", 9001, 0 },
745 	{ "laquo", 171, 0 },
746 	{ "larr", 8592, 0 },
747 	{ "lcaron", 318, 0 },
748 	{ "lceil", 8968, 0 },
749 	{ "ldq", 8220, 0 },
750 	{ "ldquo", 8220, 0 },
751 	{ "le", 8804, 0 },
752 	{ "lfloor", 8970, 0 },
753 	{ "lowast", 8727, 0 },
754 	{ "loz", 9674, 0 },
755 	{ "lsaquo", 8249, 0 },
756 	{ "lsq", 8216, 0 },
757 	{ "lsquo", 8216, 0 },
758 	{ "lstrok", 322, 0 },
759 	{ "lt", '<', 0 },
760 	{ "macr", 175, 0 },
761 	{ "mdash", 8212, 0 },
762 	{ "micro", 181, 0 },
763 	{ "middot", 183, 0 },
764 	{ "minus", 8722, 0 },
765 	{ "mu", 956, 0 },
766 	{ "nabla", 8711, 0 },
767 	{ "nacute", 324, 0 },
768 	{ "nbsp", QSPACE, 0 },
769 	{ "ncaron", 328, 0 },
770 	{ "ndash", 8211, 0 },
771 	{ "ne", 8800, 0 },
772 	{ "ni", 8715, 0 },
773 	{ "not", 172, 0 },
774 	{ "notin", 8713, 0 },
775 	{ "nsub", 8836, 0 },
776 	{ "ntilde", 241, 0 },
777 	{ "nu", 957, 0 },
778 	{ "oacute", 243, 0 },
779 	{ "ocirc", 244, 0 },
780 	{ "odblac", 337, 0 },
781 	{ "oelig", 339, 0 },
782 	{ "ogon", 731, 0 },
783 	{ "ograve", 242, 0 },
784 	{ "oline", 8254, 0 },
785 	{ "omega", 969, 0 },
786 	{ "omicron", 959, 0 },
787 	{ "oplus", 8853, 0 },
788 	{ "or", 8744, 0 },
789 	{ "ordf", 170, 0 },
790 	{ "ordm", 186, 0 },
791 	{ "oslash", 248, 0 },
792 	{ "otilde", 245, 0 },
793 	{ "otimes", 8855, 0 },
794 	{ "ouml", 246, 0 },
795 	{ "para", 182, 0 },
796 	{ "part", 8706, 0 },
797 	{ "permil", 8240, 0 },
798 	{ "perp", 8869, 0 },
799 	{ "phi", 966, 0 },
800 	{ "pi", 960, 0 },
801 	{ "piv", 982, 0 },
802 	{ "plusmn", 177, 0 },
803 	{ "pound", 163, 0 },
804 	{ "prime", 8242, 0 },
805 	{ "prod", 8719, 0 },
806 	{ "prop", 8733, 0 },
807 	{ "psi", 968, 0 },
808 	{ "quot", '"', 0 },
809 	{ "rArr", 8658, 0 },
810 	{ "racute", 341, 0 },
811 	{ "radic", 8730, 0 },
812 	{ "rang", 9002, 0 },
813 	{ "raquo", 187, 0 },
814 	{ "rarr", 8594, 0 },
815 	{ "rcaron", 345, 0 },
816 	{ "rceil", 8969, 0 },
817 	{ "rdq", 8221, 0 },
818 	{ "rdquo", 8221, 0 },
819 	{ "real", 8476, 0 },
820 	{ "reg", 174, 0 },
821 	{ "rfloor", 8971, 0 },
822 	{ "rho", 961, 0 },
823 	{ "rsaquo", 8250, 0 },
824 	{ "rsq", 8217, 0 },
825 	{ "rsquo", 8217, 0 },
826 	{ "sacute", 347, 0 },
827 	{ "sbquo", 8218, 0 },
828 	{ "scaron", 353, 0 },
829 	{ "scedil", 351, 0 },
830 	{ "sdot", 8901, 0 },
831 	{ "sect", 167, 0 },
832 	{ "shy", 173, 0 },
833 	{ "sigma", 963, 0 },
834 	{ "sigmaf", 962, 0 },
835 	{ "sim", 8764, 0 },
836 	{ "spades", 9824, 0 },
837 	{ "sub", 8834, 0 },
838 	{ "sube", 8838, 0 },
839 	{ "sum", 8721, 0 },
840 	{ "sup", 8835, 0 },
841 	{ "sup1", 185, 0 },
842 	{ "sup2", 178, 0 },
843 	{ "sup3", 179, 0 },
844 	{ "supe", 8839, 0 },
845 	{ "szlig", 223, 0 },
846 	{ "tau", 964, 0 },
847 	{ "tcaron", 357, 0 },
848 	{ "tcedil", 355, 0 },
849 	{ "there4", 8756, 0 },
850 	{ "theta", 952, 0 },
851 	{ "thetasym", 977, 0 },
852 	{ "thorn", 254, 0 },
853 	{ "thorn", 254, 0 },
854 	{ "tilde", 732, 0 },
855 	{ "times", 215, 0 },
856 	{ "trade", 8482, 0 },
857 	{ "uArr", 8657, 0 },
858 	{ "uacute", 250, 0 },
859 	{ "uarr", 8593, 0 },
860 	{ "ucirc", 251, 0 },
861 	{ "udblac", 369, 0 },
862 	{ "ugrave", 249, 0 },
863 	{ "uml", 168, 0 },
864 	{ "upsih", 978, 0 },
865 	{ "upsilon", 965, 0 },
866 	{ "uring", 367, 0 },
867 	{ "uuml", 252, 0 },
868 	{ "weierp", 8472, 0 },
869 	{ "xi", 958, 0 },
870 	{ "yacute", 253, 0 },
871 	{ "yen", 165, 0 },
872 	{ "yuml", 255, 0 },
873 	{ "zacute", 378, 0 },
874 	{ "zcaron", 382, 0 },
875 	{ "zdot", 380, 0 },
876 	{ "zeta", 950, 0 }
877 };
878 
879 
880 /* ------------------------------------------------------------------------ */
881 /*
882  *   turn on CAPS mode for a stream
883  */
outcaps_stream(out_stream_info * stream)884 static void outcaps_stream(out_stream_info *stream)
885 {
886 	/* turn on CAPS mode */
887 	stream->capsflag = TRUE;
888 
889 	/* turn off NOCAPS and ALLCAPS mode */
890 	stream->nocapsflag = FALSE;
891 	stream->allcapsflag = FALSE;
892 }
893 
894 /*
895  *   turn on NOCAPS mode for a stream
896  */
outnocaps_stream(out_stream_info * stream)897 static void outnocaps_stream(out_stream_info *stream)
898 {
899 	/* turn on NOCAPS mode */
900 	stream->nocapsflag = TRUE;
901 
902 	/* turn off CAPS and ALLCAPS mode */
903 	stream->capsflag = FALSE;
904 	stream->allcapsflag = FALSE;
905 }
906 
907 /*
908  *   turn on or off ALLCAPS mode for a stream
909  */
outallcaps_stream(out_stream_info * stream,int all_caps)910 static void outallcaps_stream(out_stream_info *stream, int all_caps)
911 {
912 	/* set the ALLCAPS flag */
913 	stream->allcapsflag = all_caps;
914 
915 	/* clear the CAPS and NOCAPS flags */
916 	stream->capsflag = FALSE;
917 	stream->nocapsflag = FALSE;
918 }
919 
920 /* ------------------------------------------------------------------------ */
921 /*
922  *   write a string to a stream
923  */
stream_print(out_stream_info * stream,char * str)924 static void stream_print(out_stream_info *stream, char *str)
925 {
926 	/* call the stream's do_print method */
927 	(*stream->do_print)(stream, str);
928 }
929 
930 /*
931  *   Write out a line
932  */
t_outline(out_stream_info * stream,int nl,const char * txt,const int * attr)933 static void t_outline(out_stream_info *stream, int nl,
934 					  const char *txt, const int *attr)
935 {
936 	extern int scrquiet;
937 
938 	/*
939 	 *   Check the "script quiet" mode - this indicates that we're reading
940 	 *   a script and not echoing output to the display.  If this mode is
941 	 *   on, and we're writing to the display, suppress this write.  If
942 	 *   the mode is off, or we're writing to another stream (such as the
943 	 *   log file), show the output as normal.
944 	 */
945 	if (!scrquiet || stream != &G_std_disp)
946 	{
947 		size_t i;
948 		char buf[MAXWIDTH];
949 		char *dst;
950 
951 		/*
952 		 *   Check to see if we've reached the end of the screen, and if
953 		 *   so run the MORE prompt.  Note that we don't make this check
954 		 *   at all if USE_MORE is undefined, since this means that the OS
955 		 *   layer code is taking responsibility for pagination issues.
956 		 *   We also don't display a MORE prompt when reading from a
957 		 *   script file.
958 		 *
959 		 *   Note that we suppress the MORE prompt if nl == 0, since this
960 		 *   is used to flush a partial line of text without starting a
961 		 *   new line (for example, when displaying a prompt where the
962 		 *   input will appear on the same line following the prompt).
963 		 *
964 		 *   Skip the MORE prompt if this stream doesn't use it.
965 		 */
966 		if (stream->use_more_mode
967 			&& scrfp == 0
968 			&& G_os_moremode
969 			&& nl != 0 && nl != 4
970 			&& stream->linecnt++ >= G_os_pagelength)
971 		{
972 			/* display the MORE prompt */
973 			out_more_prompt();
974 		}
975 
976 		/*
977 		 *   Display the text.  Run through the text in pieces; each time the
978 		 *   attributes change, set attributes at the osifc level.
979 		 */
980 		for (i = 0, dst = buf ; txt[i] != '\0' ; ++i)
981 		{
982 			/* if the attribute is changing, notify osifc */
983 			if (attr != 0 && attr[i] != stream->os_attr)
984 			{
985 				/* flush the preceding text */
986 				if (dst != buf)
987 				{
988 					*dst = '\0';
989 					stream_print(stream, buf);
990 				}
991 
992 				/* set the new attribute */
993 				os_set_text_attr(attr[i]);
994 
995 				/* remember this as the last OS attribute */
996 				stream->os_attr = attr[i];
997 
998 				/* start with a fresh buffer */
999 				dst = buf;
1000 			}
1001 
1002 			/* buffer this character */
1003 			*dst++ = txt[i];
1004 		}
1005 
1006 		/* flush the last chunk of text */
1007 		if (dst != buf)
1008 		{
1009 			*dst = '\0';
1010 			stream_print(stream, buf);
1011 		}
1012 	}
1013 }
1014 
1015 /* ------------------------------------------------------------------------ */
1016 /*
1017  *   Flush the current line to the display.  The 'nl' argument specifies
1018  *   what kind of flushing to do:
1019  *
1020  *   0: flush the current line but do not start a new line; more text will
1021  *   follow on the current line.  This is used, for example, to flush text
1022  *   after displaying a prompt and before waiting for user input.
1023  *
1024  *   1: flush the line and start a new line.
1025  *
1026  *   2: flush the line as though starting a new line, but don't add an
1027  *   actual newline character to the output, since the underlying OS
1028  *   display code will handle this.  Instead, add a space after the line.
1029  *   (This differs from mode 0 in that mode 0 shouldn't add anything at
1030  *   all after the line.)
1031  *
1032  *   3: "preview" mode.  Flush the line, but do not start a new line, and
1033  *   retain the current text in the buffer.  This is used for systems that
1034  *   handle the line wrapping in the underlying system code to flush a
1035  *   partially filled line that will need to be flushed again later.
1036  *
1037  *   4: same as mode 0, but used for internal buffer flushes only.  Do not
1038  *   involve the underlying OS layer in this type of flush - simply flush
1039  *   our buffers with no separation.
1040  */
1041 
1042 /* flush a given output stream */
outflushn_stream(out_stream_info * stream,int nl)1043 static void outflushn_stream(out_stream_info *stream, int nl)
1044 {
1045 	int i;
1046 
1047 	/* null-terminate the current output line buffer */
1048 	stream->linebuf[stream->linepos] = '\0';
1049 
1050 	/* note the position of the last character to display */
1051 	i = stream->linepos - 1;
1052 
1053 	/* if we're adding anything, remove trailing spaces */
1054 	if (nl != 0 && nl != 4)
1055 	{
1056 		/* look for last non-space character */
1057 		for ( ; i >= 0 && outissp(stream->linebuf[i]) ; --i) ;
1058 	}
1059 
1060 	/* check the output mode */
1061 	if (nl == 3)
1062 	{
1063 		/*
1064 		 *   this is the special "preview" mode -- only display the part
1065 		 *   that we haven't already previewed for this same line
1066 		 */
1067 		if (i + 1 > stream->preview)
1068 		{
1069 			/* write out the line */
1070 			t_outline(stream, 0, &stream->linebuf[stream->preview],
1071 					  &stream->attrbuf[stream->preview]);
1072 
1073 			/* skip past the part we wrote */
1074 			stream->preview += strlen(&stream->linebuf[stream->preview]);
1075 		}
1076 	}
1077 	else
1078 	{
1079 		const char *suffix = nullptr; /* extra text to add after the flushed text */
1080 		int   countnl = 0;               /* true if line counts for [more] paging */
1081 
1082 		/* null-terminate the buffer at the current position */
1083 		stream->linebuf[++i] = '\0';
1084 
1085 		/* check the mode */
1086 		switch(nl)
1087 		{
1088 		case 0:
1089 		case 3:
1090 		case 4:
1091 			/* no newline - just flush out what we have with no suffix */
1092 			suffix = 0;
1093 			break;
1094 
1095 		case 1:
1096 			/*
1097 			 *   Add a newline.  If there's nothing in the current line,
1098 			 *   or we just wrote out a newline, do not add an extra
1099 			 *   newline.  Keep all newlines in PRE mode.
1100 			 */
1101 			if (stream->linecol != 0 || !stream->just_did_nl
1102 				|| stream->html_pre_level != 0)
1103 			{
1104 				/* add a newline after the text */
1105 				suffix = "\n";
1106 
1107 				/* count the line in the page size */
1108 				countnl = 1;
1109 			}
1110 			else
1111 			{
1112 				/* don't add a newline */
1113 				suffix = 0;
1114 			}
1115 			break;
1116 
1117 		case 2:
1118 			/*
1119 			 *   we're going to depend on the underlying OS output layer
1120 			 *   to do line breaking, so don't add a newline, but do add a
1121 			 *   space, so that the underlying OS layer knows we have a
1122 			 *   word break here
1123 			 */
1124 			suffix = " ";
1125 			break;
1126 		}
1127 
1128 		/*
1129 		 *   display the line, as long as we have something buffered to
1130 		 *   display; even if we don't, display it if our column is
1131 		 *   non-zero and we didn't just do a newline, since this must
1132 		 *   mean that we've flushed a partial line and are just now doing
1133 		 *   the newline
1134 		 */
1135 		if (stream->linebuf[stream->preview] != '\0'
1136 			|| (stream->linecol != 0 && !stream->just_did_nl)
1137 			|| stream->html_pre_level > 0)
1138 		{
1139 			/* write it out */
1140 			t_outline(stream, countnl, &stream->linebuf[stream->preview],
1141 					  &stream->attrbuf[stream->preview]);
1142 
1143 			/* write the suffix, if any */
1144 			if (suffix != 0)
1145 				t_outline(stream, 0, suffix, 0);
1146 		}
1147 
1148 		/* generate an HTML line break if necessary */
1149 		if (nl == 1 && stream->html_mode && stream->html_target)
1150 			t_outline(stream, 0, "<BR HEIGHT=0>", 0);
1151 
1152 		if (nl == 0)
1153 		{
1154 			/* we're not displaying a newline, so flush what we have */
1155 			os_flush();
1156 		}
1157 		else
1158 		{
1159 			/* we displayed a newline, so reset the column position */
1160 			stream->linecol = 0;
1161 		}
1162 
1163 		/* reset the line output buffer position */
1164 		stream->linepos = stream->preview = 0;
1165 
1166 		/*
1167 		 *   If we just output a newline, note it.  If we didn't just
1168 		 *   output a newline, but we did write out anything else, note
1169 		 *   that we're no longer at the start of a line on the underlying
1170 		 *   output device.
1171 		 */
1172 		if (nl == 1)
1173 			stream->just_did_nl = TRUE;
1174 		else if (stream->linebuf[stream->preview] != '\0')
1175 			stream->just_did_nl = FALSE;
1176 	}
1177 
1178 	/*
1179 	 *   If the osifc-level attributes don't match the current attributes,
1180 	 *   bring the osifc layer up to date.  This is necessary in cases where
1181 	 *   we set attributes immediately before asking for input - we
1182 	 *   essentially need to flush the attributes without flushing any text.
1183 	 */
1184 	if (stream->cur_attr != stream->os_attr)
1185 	{
1186 		/* set the osifc attributes */
1187 		os_set_text_attr(stream->cur_attr);
1188 
1189 		/* remember the new attributes as the current osifc attributes */
1190 		stream->os_attr = stream->cur_attr;
1191 	}
1192 }
1193 
1194 /* ------------------------------------------------------------------------ */
1195 /*
1196  *   Determine if we're showing output.  Returns true if output should be
1197  *   displayed, false if it should be suppressed.  We'll note the output
1198  *   for hidden display accounting as needed.
1199  */
out_is_hidden()1200 static int out_is_hidden()
1201 {
1202 	/* check the output flag */
1203 	if (!outflag)
1204 	{
1205 		/* trace the hidden output if desired */
1206 		if (dbghid && !hidout)
1207 			trchid();
1208 
1209 		/* note the hidden output */
1210 		hidout = 1;
1211 
1212 		/*
1213 		 *   unless we're showing hidden text in the debugger, we're
1214 		 *   suppressing output, so return true
1215 		 */
1216 		if (!dbghid)
1217 			return TRUE;
1218 	}
1219 
1220 	/* we're not suppressing output */
1221 	return FALSE;
1222 }
1223 
1224 /* ------------------------------------------------------------------------ */
1225 /*
1226  *   Display a blank line to the given stream
1227  */
outblank_stream(out_stream_info * stream)1228 static void outblank_stream(out_stream_info *stream)
1229 {
1230 	/* flush the stream */
1231 	outflushn_stream(stream, 1);
1232 
1233 	/* if generating for an HTML display target, add an HTML line break */
1234 	if (stream->html_mode && stream->html_target)
1235 		outstring_stream(stream, "<BR>");
1236 
1237 	/* write out the newline */
1238 	t_outline(stream, 1, "\n", 0);
1239 }
1240 
1241 /* ------------------------------------------------------------------------ */
1242 /*
1243  *   Generate a tab for a "\t" sequence in the game text.
1244  *
1245  *   Standard (non-HTML) version: we'll generate enough spaces to take us
1246  *   to the next tab stop.
1247  *
1248  *   HTML version: if we're in native HTML mode, we'll just generate a
1249  *   <TAB MULTIPLE=4>; if we're not in HTML mode, we'll generate a hard
1250  *   tab character, which the HTML formatter will interpret as a <TAB
1251  *   MULTIPLE=4>.
1252  */
outtab_stream(out_stream_info * stream)1253 static void outtab_stream(out_stream_info *stream)
1254 {
1255 	/* check to see what the underlying system is expecting */
1256 	if (stream->html_target)
1257 	{
1258 		/* the underlying system is HTML - check for HTML mode */
1259 		if (stream->html_mode)
1260 		{
1261 			/* we're in HTML mode, so use the HTML <TAB> tag */
1262 			outstring_stream(stream, "<TAB MULTIPLE=4>");
1263 		}
1264 		else
1265 		{
1266 			/* we're not in HTML mode, so generate a hard tab character */
1267 			outchar_noxlat_stream(stream, QTAB);
1268 		}
1269 	}
1270 	else
1271 	{
1272 		int maxcol;
1273 
1274 		/*
1275 		 *   We're not in HTML mode - expand the tab with spaces.  Figure
1276 		 *   the maximum column: if we're doing our own line wrapping, never
1277 		 *   go beyond the actual display width.
1278 		 */
1279 		maxcol = (stream->os_line_wrap ? OS_MAXWIDTH : G_os_linewidth);
1280 
1281 		/* add the spaces */
1282 		do
1283 		{
1284 			stream->attrbuf[stream->linepos] = stream->cur_attr;
1285 			stream->linebuf[stream->linepos++] = ' ';
1286 			++(stream->linecol);
1287 		} while (((stream->linecol + 1) & 3) != 0
1288 				 && stream->linecol < maxcol);
1289 	}
1290 }
1291 
1292 
1293 /* ------------------------------------------------------------------------ */
1294 /*
1295  *   Flush a line
1296  */
out_flushline(out_stream_info * stream,int padding)1297 static void out_flushline(out_stream_info *stream, int padding)
1298 {
1299 	/*
1300 	 *   check to see if we're using the underlying display layer's line
1301 	 *   wrapping
1302 	 */
1303 	if (stream->os_line_wrap)
1304 	{
1305 		/*
1306 		 *   In the HTML version, we don't need the normal *MORE*
1307 		 *   processing, since the HTML layer will handle that.
1308 		 *   Furthermore, we don't need to provide actual newline breaks
1309 		 *   -- that happens after the HTML is parsed, so we don't have
1310 		 *   enough information here to figure out actual line breaks.
1311 		 *   So, we'll just flush out our buffer whenever it fills up, and
1312 		 *   suppress newlines.
1313 		 *
1314 		 *   Similarly, if we have OS-level MORE processing, don't try to
1315 		 *   figure out where the line breaks go -- just flush our buffer
1316 		 *   without a trailing newline whenever the buffer is full, and
1317 		 *   let the OS layer worry about formatting lines and paragraphs.
1318 		 *
1319 		 *   If we're using padding, use mode 2.  If we don't want padding
1320 		 *   (which is the case if we completely fill up the buffer
1321 		 *   without finding any word breaks), write out in mode 0, which
1322 		 *   just flushes the buffer exactly like it is.
1323 		 */
1324 		outflushn_stream(stream, padding ? 2 : 4);
1325 	}
1326 	else
1327 	{
1328 		/*
1329 		 *   Normal mode - we process the *MORE* prompt ourselves, and we
1330 		 *   are responsible for figuring out where the actual line breaks
1331 		 *   go.  Use outflush() to generate an actual newline whenever we
1332 		 *   flush out our buffer.
1333 		 */
1334 		outflushn_stream(stream, 1);
1335 	}
1336 }
1337 
1338 
1339 /* ------------------------------------------------------------------------ */
1340 /*
1341  *   Write a character to an output stream without translation
1342  */
outchar_noxlat_stream(out_stream_info * stream,char c)1343 static void outchar_noxlat_stream(out_stream_info *stream, char c)
1344 {
1345 	int  i;
1346 	int  qspace;
1347 
1348 	/* check for the special quoted space character */
1349 	if (c == QSPACE)
1350 	{
1351 		/* it's a quoted space - note it and convert it to a regular space */
1352 		qspace = 1;
1353 		c = ' ';
1354 	}
1355 	else if (c == QTAB)
1356 	{
1357 		/* it's a hard tab - convert it to an ordinary tab */
1358 		c = '\t';
1359 		qspace = 0;
1360 	}
1361 	else
1362 	{
1363 		/* translate any whitespace character to a regular space character */
1364 		if (outissp(c))
1365 			c = ' ';
1366 
1367 		/* it's not a quoted space */
1368 		qspace = 0;
1369 	}
1370 
1371 	/* check for the caps/nocaps flags */
1372 	if ((stream->capsflag || stream->allcapsflag) && outisal(c))
1373 	{
1374 		/* capsflag is set, so capitalize this character */
1375 		if (outislo(c))
1376 			c = toupper(c);
1377 
1378 		/* okay, we've capitalized something; clear flag */
1379 		stream->capsflag = 0;
1380 	}
1381 	else if (stream->nocapsflag && outisal(c))
1382 	{
1383 		/* nocapsflag is set, so minisculize this character */
1384 		if (outisup(c))
1385 			c = tolower(c);
1386 
1387 		/* clear the flag now that we've done the job */
1388 		stream->nocapsflag = 0;
1389 	}
1390 
1391 	/* if in capture mode, simply capture the character */
1392 	if (stream->capturing)
1393 	{
1394 		uchar *p;
1395 
1396 		/* if we have a valid capture object, copy to it */
1397 		if (stream->capture_obj != MCMONINV)
1398 		{
1399 			/* lock the object holding the captured text */
1400 			p = mcmlck(stream->capture_ctx, stream->capture_obj);
1401 
1402 			/* make sure the capture object is big enough */
1403 			if (mcmobjsiz(stream->capture_ctx, stream->capture_obj)
1404 				<= stream->capture_ofs)
1405 			{
1406 				/* expand the object by another 256 bytes */
1407 				p = mcmrealo(stream->capture_ctx, stream->capture_obj,
1408 							 (ushort)(stream->capture_ofs + 256));
1409 			}
1410 
1411 			/* add this character */
1412 			*(p + stream->capture_ofs++) = c;
1413 
1414 			/* unlock the capture object */
1415 			mcmtch(stream->capture_ctx, stream->capture_obj);
1416 			mcmunlck(stream->capture_ctx, stream->capture_obj);
1417 		}
1418 
1419 		/*
1420 		 *   we're done - we don't want to actually display the character
1421 		 *   while capturing
1422 		 */
1423 		return;
1424 	}
1425 
1426 	/* add the character to out output buffer, flushing as needed */
1427 	if (stream->linecol + 1 < G_os_linewidth)
1428 	{
1429 		/*
1430 		 *   there's room for this character, so add it to the buffer
1431 		 */
1432 
1433 		/* ignore non-quoted space at start of line outside of PRE */
1434 		if (outissp(c) && c != '\t' && stream->linecol == 0 && !qspace
1435 			&& stream->html_pre_level == 0)
1436 			return;
1437 
1438 		/* is this a non-quoted space not at the start of the line? */
1439 		if (outissp(c) && c != '\t' && stream->linecol != 0 && !qspace
1440 			&& stream->html_pre_level == 0)
1441 		{
1442 			int  pos1 = stream->linepos - 1;
1443 			char p = stream->linebuf[pos1];     /* check previous character */
1444 
1445 			/* ignore repeated spaces - collapse into a single space */
1446 			if (outissp(p))
1447 				return;
1448 
1449 			/*
1450 			 *   Certain punctuation requires a double space: a period, a
1451 			 *   question mark, an exclamation mark, or a colon; or any of
1452 			 *   these characters followed by any number of single and/or
1453 			 *   double quotes.  First, scan back to before any quotes, if
1454 			 *   are on one now, then check the preceding character; if
1455 			 *   it's one of the punctuation marks requiring a double
1456 			 *   space, add this space a second time.  (In addition to
1457 			 *   scanning back past quotes, scan past parentheses,
1458 			 *   brackets, and braces.)  Don't double the spacing if we're
1459 			 *   not in the normal doublespace mode; some people may
1460 			 *   prefer single spacing after punctuation, so we make this
1461 			 *   a run-time option.
1462 			 */
1463 			if (doublespace)
1464 			{
1465 				/* find the previous relevant punctuation character */
1466 				while (pos1 &&
1467 					   (p == '"' || p == '\'' || p == ')' || p == ']'
1468 						|| p == '}'))
1469 				{
1470 					p = stream->linebuf[--pos1];
1471 				}
1472 				if ( p == '.' || p == '?' || p == '!' || p == ':' )
1473 				{
1474 					/* a double-space is required after this character */
1475 					stream->attrbuf[stream->linepos] = stream->cur_attr;
1476 					stream->linebuf[stream->linepos++] = c;
1477 					++(stream->linecol);
1478 				}
1479 			}
1480 		}
1481 
1482 		/* add this character to the buffer */
1483 		stream->attrbuf[stream->linepos] = stream->cur_attr;
1484 		stream->linebuf[stream->linepos++] = c;
1485 
1486 		/* advance the output column position */
1487 		++(stream->linecol);
1488 		return;
1489 	}
1490 
1491 	/*
1492 	 *   The line would overflow if this character were added.  Find the
1493 	 *   most recent word break, and output the line up to the previous
1494 	 *   word.  Note that if we're trying to output a space, we'll just
1495 	 *   add it to the line buffer.  If the last character of the line
1496 	 *   buffer is already a space, we won't do anything right now.
1497 	 */
1498 	if (outissp(c) && c != '\t' && !qspace)
1499 	{
1500 		/* this is a space, so we're at a word break */
1501 		if (stream->linebuf[stream->linepos - 1] != ' ')
1502 		{
1503 			stream->attrbuf[stream->linepos] = stream->cur_attr;
1504 			stream->linebuf[stream->linepos++] = ' ';
1505 		}
1506 		return;
1507 	}
1508 
1509 	/*
1510 	 *   Find the most recent word break: look for a space or dash, starting
1511 	 *   at the end of the line.
1512 	 *
1513 	 *   If we're about to write a hyphen, we want to skip all contiguous
1514 	 *   hyphens, because we want to keep them together as a single
1515 	 *   punctuation mark; then keep going in the normal manner, which will
1516 	 *   keep the hyphens plus the word they're attached to together as a
1517 	 *   single unit.  If spaces precede the sequence of hyphens, include
1518 	 *   the prior word as well.
1519 	 */
1520 	i = stream->linepos - 1;
1521 	if (c == '-')
1522 	{
1523 		/* skip any contiguous hyphens at the end of the line */
1524 		for ( ; i >= 0 && stream->linebuf[i] == '-' ; --i) ;
1525 
1526 		/* skip any spaces preceding the sequence of hyphens */
1527 		for ( ; i >= 0 && outissp(stream->linebuf[i]) ; --i) ;
1528 	}
1529 
1530 	/*
1531 	 *   Now find the preceding space.  If we're doing our own wrapping
1532 	 *   (i.e., we're not using OS line wrapping), then look for the
1533 	 *   nearest hyphen as well.
1534 	 */
1535 	for ( ; i >= 0 && !outissp(stream->linebuf[i])
1536 		  && !(!stream->os_line_wrap && stream->linebuf[i] == '-') ; --i) ;
1537 
1538 	/* check to see if we found a good place to break */
1539 	if (i < 0)
1540 	{
1541 		/*
1542 		 *   we didn't find any good place to break - flush the entire
1543 		 *   line as-is, breaking arbitrarily in the middle of a word
1544 		 */
1545 		out_flushline(stream, FALSE);
1546 
1547 		/*
1548 		 *   we've completely cleared out the line buffer, so reset all of
1549 		 *   the line buffer counters
1550 		 */
1551 		stream->linepos = 0;
1552 		stream->linecol = 0;
1553 		stream->linebuf[0] = '\0';
1554 	}
1555 	else
1556 	{
1557 		char brkchar;
1558 		char tmpbuf[MAXWIDTH];
1559 		int tmpattr[MAXWIDTH];
1560 		size_t tmpcnt;
1561 
1562 		/* remember the word-break character */
1563 		brkchar = stream->linebuf[i];
1564 
1565 		/* null-terminate the line buffer */
1566 		stream->linebuf[stream->linepos] = '\0';
1567 
1568 		/* the next line starts after the break - save a copy */
1569 		tmpcnt = strlen(&stream->linebuf[i+1]);
1570 		memcpy(tmpbuf, &stream->linebuf[i+1], tmpcnt + 1);
1571 		memcpy(tmpattr, &stream->attrbuf[i+1], tmpcnt * sizeof(tmpattr[0]));
1572 
1573 		/*
1574 		 *   terminate the buffer at the space or after the hyphen,
1575 		 *   depending on where we broke
1576 		 */
1577 		if (outissp(brkchar))
1578 			stream->linebuf[i] = '\0';
1579 		else
1580 			stream->linebuf[i+1] = '\0';
1581 
1582 		/* write out everything up to the word break */
1583 		out_flushline(stream, TRUE);
1584 
1585 		/* copy the next line into line buffer */
1586 		memcpy(stream->linebuf, tmpbuf, tmpcnt + 1);
1587 		memcpy(stream->attrbuf, tmpattr, tmpcnt * sizeof(tmpattr[0]));
1588 		stream->linepos = tmpcnt;
1589 
1590 		/*
1591 		 *   figure what column we're now in - count all of the printable
1592 		 *   characters in the new line
1593 		 */
1594 		for (stream->linecol = 0, i = 0 ; i < stream->linepos ; ++i)
1595 		{
1596 			/* if it's printable, count it */
1597 			if (((unsigned char)stream->linebuf[i]) >= 26)
1598 				++(stream->linecol);
1599 		}
1600 	}
1601 
1602 	/* add the new character to buffer */
1603 	stream->attrbuf[stream->linepos] = stream->cur_attr;
1604 	stream->linebuf[stream->linepos++] = c;
1605 
1606 	/* advance the column counter */
1607 	++(stream->linecol);
1608 }
1609 
1610 /* ------------------------------------------------------------------------ */
1611 /*
1612  *   Write out a character, translating to the local system character set
1613  *   from the game's internal character set.
1614  */
outchar_stream(out_stream_info * stream,char c)1615 static void outchar_stream(out_stream_info *stream, char c)
1616 {
1617 	outchar_noxlat_stream(stream, cmap_i2n(c));
1618 }
1619 
1620 /*
1621  *   write out a string, translating to the local system character set
1622  */
outstring_stream(out_stream_info * stream,const char * s)1623 static void outstring_stream(out_stream_info *stream, const char *s)
1624 {
1625 	/* write out each character in the string */
1626 	for ( ; *s ; ++s)
1627 		outchar_stream(stream, *s);
1628 }
1629 
1630 /*
1631  *   write out a string without translation
1632  */
outstring_noxlat_stream(out_stream_info * stream,char * s)1633 static void outstring_noxlat_stream(out_stream_info *stream, char *s)
1634 {
1635 	for ( ; *s ; ++s)
1636 		outchar_noxlat_stream(stream, *s);
1637 }
1638 
1639 
1640 /* ------------------------------------------------------------------------ */
1641 /*
1642  *   Write out an HTML character value, translating to the local character
1643  *   set.
1644  */
outchar_html_stream(out_stream_info * stream,unsigned int htmlchar)1645 static void outchar_html_stream(out_stream_info *stream,
1646 								unsigned int htmlchar)
1647 {
1648 	amp_tbl_t *ampptr;
1649 
1650 	/*
1651 	 *   search for a mapping entry for this entity, in case it's defined
1652 	 *   in an external mapping file
1653 	 */
1654 	for (ampptr = amp_tbl ;
1655 		 ampptr < amp_tbl + sizeof(amp_tbl)/sizeof(amp_tbl[0]) ; ++ampptr)
1656 	{
1657 		/* if this is the one, stop looking */
1658 		if (ampptr->html_cval == htmlchar)
1659 			break;
1660 	}
1661 
1662 	/*
1663 	 *   If we found a mapping table entry, and the entry has an expansion
1664 	 *   from the external character mapping table file, use the external
1665 	 *   expansion; otherwise, use the default expansion.
1666 	 */
1667 	if (ampptr >= amp_tbl + sizeof(amp_tbl)/sizeof(amp_tbl[0])
1668 		|| ampptr->expan == 0)
1669 	{
1670 		char xlat_buf[50];
1671 
1672 		/*
1673 		 *   there's no external mapping table file expansion -- use the
1674 		 *   default OS mapping routine
1675 		 */
1676 		os_xlat_html4(htmlchar, xlat_buf, sizeof(xlat_buf));
1677 		outstring_noxlat_stream(stream, xlat_buf);
1678 	}
1679 	else
1680 	{
1681 		/*
1682 		 *   use the explicit mapping from the mapping table file
1683 		 */
1684 		outstring_noxlat_stream(stream, ampptr->expan);
1685 	}
1686 }
1687 
1688 
1689 /* ------------------------------------------------------------------------ */
1690 /*
1691  *   Enter a recursion level.  Returns TRUE if the caller should proceed
1692  *   with the operation, FALSE if not.
1693  *
1694  *   If we're making a recursive call, thereby re-entering the formatter,
1695  *   and this stream is not the same as the enclosing stream, we want to
1696  *   ignore this call and suppress any output to this stream, so we'll
1697  *   return FALSE.
1698  */
out_push_stream(out_stream_info * stream)1699 static int out_push_stream(out_stream_info *stream)
1700 {
1701 	/*
1702 	 *   if we're already in the formatter, and the new stream doesn't
1703 	 *   match the enclosing recursion level's stream, tell the caller to
1704 	 *   abort the operation
1705 	 */
1706 	if (G_recurse != 0 && G_cur_stream != stream)
1707 		return FALSE;
1708 
1709 	/* note the active stream */
1710 	G_cur_stream = stream;
1711 
1712 	/* count the entry */
1713 	++G_recurse;
1714 
1715 	/* tell the caller to proceed */
1716 	return TRUE;
1717 }
1718 
1719 /*
1720  *   Leave a recursion level
1721  */
out_pop_stream()1722 static void out_pop_stream()
1723 {
1724 	/* count the exit */
1725 	--G_recurse;
1726 }
1727 
1728 /* ------------------------------------------------------------------------ */
1729 /*
1730  *   nextout() returns the next character in a string, and updates the
1731  *   string pointer and remaining length.  Returns zero if no more
1732  *   characters are available in the string.
1733  */
1734 /* static char nextout(char **s, uint *len); */
1735 #define nextout(s, len) ((char)(*(len) == 0 ? 0 : (--(*(len)), *((*(s))++))))
1736 
1737 
1738 /* ------------------------------------------------------------------------ */
1739 /*
1740  *   Get the next character, writing the previous character to the given
1741  *   output stream if it's not null.
1742  */
nextout_copy(const char ** s,size_t * slen,char prv,out_stream_info * stream)1743 static char nextout_copy(const char **s, size_t *slen,
1744 						 char prv, out_stream_info *stream)
1745 {
1746 	/* if there's a stream, write the previous character to the stream */
1747 	if (stream != 0)
1748 		outchar_stream(stream, prv);
1749 
1750 	/* return the next character */
1751 	return nextout(s, slen);
1752 }
1753 
1754 /* ------------------------------------------------------------------------ */
1755 /*
1756  *   Read an HTML tag, for our primitive mini-parser.  If 'stream' is not
1757  *   null, we'll copy each character we read to the output stream.  Returns
1758  *   the next character after the tag name.
1759  */
read_tag(char * dst,size_t dstlen,int * is_end_tag,const char ** s,size_t * slen,out_stream_info * stream)1760 static char read_tag(char *dst, size_t dstlen, int *is_end_tag,
1761 					 const char **s, size_t *slen, out_stream_info *stream)
1762 {
1763 	char c;
1764 
1765 	/* skip the opening '<' */
1766 	c = nextout_copy(s, slen, '<', stream);
1767 
1768 	/* skip spaces */
1769 	while (outissp(c))
1770 		c = nextout_copy(s, slen, c, stream);
1771 
1772 	/* note if this is a closing tag */
1773 	if (c == '/' || c == '\\')
1774 	{
1775 		/* it's an end tag - note it and skip the slash */
1776 		*is_end_tag = TRUE;
1777 		c = nextout_copy(s, slen, c, stream);
1778 
1779 		/* skip yet more spaces */
1780 		while (outissp(c))
1781 			c = nextout_copy(s, slen, c, stream);
1782 	}
1783 	else
1784 		*is_end_tag = FALSE;
1785 
1786 	/*
1787 	 *   find the end of the tag name - the tag continues to the next space,
1788 	 *   '>', or end of line
1789 	 */
1790 	for ( ; c != '\0' && !outissp(c) && c != '>' ;
1791 		 c = nextout_copy(s, slen, c, stream))
1792 	{
1793 		/* add this to the tag buffer if it fits */
1794 		if (dstlen > 1)
1795 		{
1796 			*dst++ = c;
1797 			--dstlen;
1798 		}
1799 	}
1800 
1801 	/* null-terminate the tag name */
1802 	if (dstlen > 0)
1803 		*dst = '\0';
1804 
1805 	/* return the next character */
1806 	return c;
1807 }
1808 
1809 
1810 /* ------------------------------------------------------------------------ */
1811 /*
1812  *   display a string of a given length to a given stream
1813  */
outformatlen_stream(out_stream_info * stream,const char * s,size_t slen)1814 static int outformatlen_stream(out_stream_info *stream,
1815 							   const char *s, size_t slen)
1816 {
1817 	char     c;
1818 	int      done = 0;
1819 	char     fmsbuf[40];       /* space for constructing translation string */
1820 	uint     fmslen;
1821 	char    *f = 0;
1822 	char    *f1;
1823 	int      infmt = 0;
1824 
1825 	/*
1826 	 *   This routine can recurse because of format strings ("%xxx%"
1827 	 *   sequences).  When we recurse, we want to ensure that the
1828 	 *   recursion is directed to the original stream only.  So, note the
1829 	 *   current stream statically in case we re-enter the formatter.
1830 	 */
1831 	if (!out_push_stream(stream))
1832 		return 0;
1833 
1834 	/* get the first character */
1835 	c = nextout(&s, &slen);
1836 
1837 	/* if we have anything to show, show it */
1838 	while (c != '\0')
1839 	{
1840 		/* check if we're collecting translation string */
1841 		if (infmt)
1842 		{
1843 			/*
1844 			 *   if the string is too long for our buffer, or we've come
1845 			 *   across a backslash (illegal in a format string), or we've
1846 			 *   come across an HTML-significant character ('&' or '<') in
1847 			 *   HTML mode, we must have a stray percent sign; dump the
1848 			 *   whole string so far and act as though we have no format
1849 			 *   string
1850 			 */
1851 			if (c == '\\'
1852 				|| f == &fmsbuf[sizeof(fmsbuf)]
1853 				|| (stream->html_mode && (c == '<' || c == '&')))
1854 			{
1855 				outchar_stream(stream, '%');
1856 				for (f1 = fmsbuf ; f1 < f ; ++f1)
1857 					outchar_stream(stream, *f1);
1858 				infmt = 0;
1859 
1860 				/* process this character again */
1861 				continue;
1862 			}
1863 			else if (c == '%' && f == fmsbuf)       /* double percent sign? */
1864 			{
1865 				outchar_stream(stream, '%');       /* send out a single '%' */
1866 				infmt = 0;       /* no longer processing translation string */
1867 			}
1868 			else if (c == '%')   /* found end of string? translate it if so */
1869 			{
1870 				uchar *fms;
1871 				int    initcap = FALSE;
1872 				int    allcaps = FALSE;
1873 				char   fmsbuf_srch[sizeof(fmsbuf)];
1874 
1875 				/* null-terminate the string */
1876 				*f = '\0';
1877 
1878 				/* check for an init cap */
1879 				if (outisup(fmsbuf[0]))
1880 				{
1881 					/*
1882 					 *   note the initial capital, so that we follow the
1883 					 *   original capitalization in the substituted string
1884 					 */
1885 					initcap = TRUE;
1886 
1887 					/*
1888 					 *   if the second letter is capitalized as well,
1889 					 *   capitalize the entire substituted string
1890 					 */
1891 					if (fmsbuf[1] != '\0' && outisup(fmsbuf[1]))
1892 					{
1893 						/* use all caps */
1894 						allcaps = TRUE;
1895 					}
1896 				}
1897 
1898 				/* convert the entire string to lower case for searching */
1899 				strcpy(fmsbuf_srch, fmsbuf);
1900 				os_strlwr(fmsbuf_srch);
1901 
1902 				/* find the string in the format string table */
1903 				fmslen = strlen(fmsbuf_srch);
1904 				for (fms = fmsbase ; fms < fmstop ; )
1905 				{
1906 					uint propnum;
1907 					uint len;
1908 
1909 					/* get the information on this entry */
1910 					propnum = osrp2(fms);
1911 					len = osrp2(fms + 2) - 2;
1912 
1913 					/* check for a match */
1914 					if (len == fmslen &&
1915 						!memcmp(fms + 4, fmsbuf_srch, (size_t)len))
1916 					{
1917 						int old_all_caps;
1918 
1919 						/* note the current ALLCAPS mode */
1920 						old_all_caps = stream->allcapsflag;
1921 
1922 						/*
1923 						 *   we have a match - set the appropriate
1924 						 *   capitalization mode
1925 						 */
1926 						if (allcaps)
1927 							outallcaps_stream(stream, TRUE);
1928 						else if (initcap)
1929 							outcaps_stream(stream);
1930 
1931 						/*
1932 						 *   evaluate the associated property to generate
1933 						 *   the substitution text
1934 						 */
1935 						runppr(runctx, cmdActor, (prpnum)propnum, 0);
1936 
1937 						/* turn off ALLCAPS mode */
1938 						outallcaps_stream(stream, old_all_caps);
1939 
1940 						/* no need to look any further */
1941 						break;
1942 					}
1943 
1944 					/* move on to next formatstring if not yet found */
1945 					fms += len + 4;
1946 				}
1947 
1948 				/* if we can't find it, dump the format string as-is */
1949 				if (fms == fmstop)
1950 				{
1951 					outchar_stream(stream, '%');
1952 					for (f1 = fmsbuf ; f1 < f ; ++f1)
1953 						outchar_stream(stream, *f1);
1954 					outchar_stream(stream, '%');
1955 				}
1956 
1957 				/* no longer reading format string */
1958 				infmt = 0;
1959 			}
1960 			else
1961 			{
1962 				/* copy this character of the format string */
1963 				*f++ = c;
1964 			}
1965 
1966 			/* move on to the next character and continue scanning */
1967 			c = nextout(&s, &slen);
1968 			continue;
1969 		}
1970 
1971 		/*
1972 		 *   If we're parsing HTML here, and we're inside a tag, skip
1973 		 *   characters until we reach the end of the tag.
1974 		 */
1975 		if (stream->html_mode_flag != HTML_MODE_NORMAL)
1976 		{
1977 			switch(stream->html_mode_flag)
1978 			{
1979 			case HTML_MODE_TAG:
1980 				/*
1981 				 *   keep skipping up to the closing '>', but note when we
1982 				 *   enter any quoted section
1983 				 */
1984 				switch(c)
1985 				{
1986 				case '>':
1987 					/* we've reached the end of the tag */
1988 					stream->html_mode_flag = HTML_MODE_NORMAL;
1989 
1990 					/* if we have a deferred <BR>, process it now */
1991 					switch(stream->html_defer_br)
1992 					{
1993 					case HTML_DEFER_BR_NONE:
1994 						/* no deferred <BR> */
1995 						break;
1996 
1997 					case HTML_DEFER_BR_FLUSH:
1998 						outflushn_stream(stream, 1);
1999 						break;
2000 
2001 					case HTML_DEFER_BR_BLANK:
2002 						outblank_stream(stream);
2003 						break;
2004 					}
2005 
2006 					/* no more deferred <BR> pending */
2007 					stream->html_defer_br = HTML_DEFER_BR_NONE;
2008 
2009 					/* no more ALT attribute allowed */
2010 					stream->html_allow_alt = FALSE;
2011 					break;
2012 
2013 				case '"':
2014 					/* enter a double-quoted string */
2015 					stream->html_mode_flag = HTML_MODE_DQUOTE;
2016 					break;
2017 
2018 				case '\'':
2019 					/* enter a single-quoted string */
2020 					stream->html_mode_flag = HTML_MODE_SQUOTE;
2021 					break;
2022 
2023 				default:
2024 					/* if it's alphabetic, note the attribute name */
2025 					if (outisal(c))
2026 					{
2027 						char attrname[128];
2028 						char attrval[256];
2029 						char *dst;
2030 
2031 						/* gather up the attribute name */
2032 						for (dst = attrname ;
2033 							 dst + 1 < attrname + sizeof(attrname) ; )
2034 						{
2035 							/* store this character */
2036 							*dst++ = c;
2037 
2038 							/* get the next character */
2039 							c = nextout(&s, &slen);
2040 
2041 							/* if it's not alphanumeric, stop scanning */
2042 							if (!outisal(c) && !outisdg(c))
2043 								break;
2044 						}
2045 
2046 						/* null-terminate the result */
2047 						*dst++ = '\0';
2048 
2049 						/* gather the value if present */
2050 						if (c == '=')
2051 						{
2052 							char qu;
2053 
2054 							/* skip the '=' */
2055 							c = nextout(&s, &slen);
2056 
2057 							/* if we have a quote, so note */
2058 							if (c == '"' || c == '\'')
2059 							{
2060 								/* remember the quote */
2061 								qu = c;
2062 
2063 								/* skip it */
2064 								c = nextout(&s, &slen);
2065 							}
2066 							else
2067 							{
2068 								/* no quote */
2069 								qu = 0;
2070 							}
2071 
2072 							/* read the value */
2073 							for (dst = attrval ;
2074 								 dst + 1 < attrval + sizeof(attrval) ; )
2075 							{
2076 								/* store this character */
2077 								*dst++ = c;
2078 
2079 								/* read the next one */
2080 								c = nextout(&s, &slen);
2081 								if (c == '\0')
2082 								{
2083 									/*
2084 									 *   we've reached the end of the
2085 									 *   string, and we're still inside
2086 									 *   this attribute - abandon the
2087 									 *   attribute but note that we're
2088 									 *   inside a quoted string if
2089 									 *   necessary
2090 									 */
2091 									if (qu == '"')
2092 										stream->html_mode_flag =
2093 											HTML_MODE_DQUOTE;
2094 									else if (qu == '\'')
2095 										stream->html_mode_flag =
2096 											HTML_MODE_SQUOTE;
2097 									else
2098 										stream->html_mode_flag
2099 											= HTML_MODE_TAG;
2100 
2101 									/* stop scanning the string */
2102 									break;
2103 								}
2104 
2105 								/*
2106 								 *   if we're looking for a quote, check
2107 								 *   for the closing quote; otherwise,
2108 								 *   check for alphanumerics
2109 								 */
2110 								if (qu != 0)
2111 								{
2112 									/* if this is our quote, stop scanning */
2113 									if (c == qu)
2114 										break;
2115 								}
2116 								else
2117 								{
2118 									/* if it's non-alphanumeric, we're done */
2119 									if (!outisal(c) && !outisdg(c))
2120 										break;
2121 								}
2122 							}
2123 
2124 							/* skip the closing quote, if necessary */
2125 							if (qu != 0 && c == qu)
2126 								c = nextout(&s, &slen);
2127 
2128 							/* null-terminate the value string */
2129 							*dst = '\0';
2130 						}
2131 						else
2132 						{
2133 							/* no value */
2134 							attrval[0] = '\0';
2135 						}
2136 
2137 						/*
2138 						 *   see if we recognize it, and it's meaningful
2139 						 *   in the context of the current tag
2140 						 */
2141 						if (!scumm_stricmp(attrname, "height")
2142 							&& stream->html_defer_br != HTML_DEFER_BR_NONE)
2143 						{
2144 							int ht;
2145 
2146 							/*
2147 							 *   If the height is zero, always treat this
2148 							 *   as a non-blanking flush.  If it's one,
2149 							 *   treat it as we originally planned to.  If
2150 							 *   it's greater than one, add n blank lines.
2151 							 */
2152 							ht = atoi(attrval);
2153 							if (ht == 0)
2154 							{
2155 								/* always use non-blanking flush */
2156 								stream->html_defer_br = HTML_DEFER_BR_FLUSH;
2157 							}
2158 							else if (ht == 1)
2159 							{
2160 								/* keep original setting */
2161 							}
2162 							else
2163 							{
2164 								for ( ; ht > 0 ; --ht)
2165 									outblank_stream(stream);
2166 							}
2167 						}
2168 						else if (!scumm_stricmp(attrname, "alt")
2169 								 && !stream->html_in_ignore
2170 								 && stream->html_allow_alt)
2171 						{
2172 							/* write out the ALT string */
2173 							outstring_stream(stream, attrval);
2174 						}
2175 
2176 						/*
2177 						 *   since we already read the next character,
2178 						 *   simply loop back immediately
2179 						 */
2180 						continue;
2181 					}
2182 					break;
2183 				}
2184 				break;
2185 
2186 			case HTML_MODE_DQUOTE:
2187 				/* if we've reached the closing quote, return to tag state */
2188 				if (c == '"')
2189 					stream->html_mode_flag = HTML_MODE_TAG;
2190 				break;
2191 
2192 			case HTML_MODE_SQUOTE:
2193 				/* if we've reached the closing quote, return to tag state */
2194 				if (c == '\'')
2195 					stream->html_mode_flag = HTML_MODE_TAG;
2196 				break;
2197 			}
2198 
2199 			/*
2200 			 *   move on to the next character, and start over with the
2201 			 *   new character
2202 			 */
2203 			c = nextout(&s, &slen);
2204 			continue;
2205 		}
2206 
2207 		/*
2208 		 *   If we're in a title, and this isn't the start of a new tag,
2209 		 *   skip the character - we suppress all regular text output
2210 		 *   inside a <TITLE> ... </TITLE> sequence.
2211 		 */
2212 		if (stream->html_in_ignore && c != '<')
2213 		{
2214 			/* check for entities */
2215 			char cbuf[50];
2216 			if (c == '&')
2217 			{
2218 				/* translate the entity */
2219 				c = out_parse_entity(cbuf, sizeof(cbuf), &s, &slen);
2220 			}
2221 			else
2222 			{
2223 				/* it's an ordinary character - copy it out literally */
2224 				cbuf[0] = c;
2225 				cbuf[1] = '\0';
2226 
2227 				/* get the next character */
2228 				c = nextout(&s, &slen);
2229 			}
2230 
2231 			/*
2232 			 *   if we're gathering a title, and there's room in the title
2233 			 *   buffer for more (always leaving room for a null
2234 			 *   terminator), add this to the title buffer
2235 			 */
2236 			if (stream->html_in_title)
2237 			{
2238 				char *cbp;
2239 				for (cbp = cbuf ; *cbp != '\0' ; ++cbp)
2240 				{
2241 					/* if there's room, add it */
2242 					if (stream->html_title_ptr + 1 <
2243 						stream->html_title_buf
2244 						+ sizeof(stream->html_title_buf))
2245 						*stream->html_title_ptr++ = *cbp;
2246 				}
2247 			}
2248 
2249 			/* don't display anything in an ignore section */
2250 			continue;
2251 		}
2252 
2253 		if ( c == '%' )                              /* translation string? */
2254 		{
2255 			infmt = 1;
2256 			f = fmsbuf;
2257 		}
2258 		else if ( c == '\\' )                       /* special escape code? */
2259 		{
2260 			c = nextout(&s, &slen);
2261 
2262 			if (stream->capturing && c != '^' && c != 'v' && c != '\0')
2263 			{
2264 				outchar_stream(stream, '\\');
2265 				outchar_stream(stream, c);
2266 
2267 				/* keep the \- and also put out the next two chars */
2268 				if (c == '-')
2269 				{
2270 					outchar_stream(stream, nextout(&s, &slen));
2271 					outchar_stream(stream, nextout(&s, &slen));
2272 				}
2273 			}
2274 			else
2275 			{
2276 				switch(c)
2277 				{
2278 				case 'H':                                /* HTML mode entry */
2279 					/* turn on HTML mode in the renderer */
2280 					switch(c = nextout(&s, &slen))
2281 					{
2282 					case '-':
2283 						/* if we have an HTML target, notify it */
2284 						if (stream->html_target)
2285 						{
2286 							/* flush its stream */
2287 							outflushn_stream(stream, 0);
2288 
2289 							/* tell the OS layer to switch to normal mode */
2290 							out_end_html(stream);
2291 						}
2292 
2293 						/* switch to normal mode */
2294 						stream->html_mode = FALSE;
2295 						break;
2296 
2297 					case '+':
2298 					default:
2299 						/* if we have an HTML target, notify it */
2300 						if (stream->html_target)
2301 						{
2302 							/* flush the underlying stream */
2303 							outflushn_stream(stream, 0);
2304 
2305 							/* tell the OS layer to switch to HTML mode */
2306 							out_start_html(stream);
2307 						}
2308 
2309 						/* switch to HTML mode */
2310 						stream->html_mode = TRUE;
2311 
2312 						/*
2313 						 *   if the character wasn't a "+", it's not part
2314 						 *   of the "\H" sequence, so display it normally
2315 						 */
2316 						if (c != '+' && c != 0)
2317 							outchar_stream(stream, c);
2318 						break;
2319 					}
2320 
2321 					/* this sequence doesn't result in any actual output */
2322 					break;
2323 
2324 				case 'n':                                       /* newline? */
2325 					outflushn_stream(stream, 1);        /* yes, output line */
2326 					break;
2327 
2328 				case 't':                                           /* tab? */
2329 					outtab_stream(stream);
2330 					break;
2331 
2332 				case 'b':                                    /* blank line? */
2333 					outblank_stream(stream);
2334 					break;
2335 
2336 				case '\0':                               /* line ends here? */
2337 					done = 1;
2338 					break;
2339 
2340 				case ' ':                                   /* quoted space */
2341 					if (stream->html_target && stream->html_mode)
2342 					{
2343 						/*
2344 						 *   we're generating for an HTML target and we're
2345 						 *   in HTML mode - generate the HTML non-breaking
2346 						 *   space
2347 						 */
2348 						outstring_stream(stream, "&nbsp;");
2349 					}
2350 					else
2351 					{
2352 						/*
2353 						 *   we're not in HTML mode - generate our
2354 						 *   internal quoted space character
2355 						 */
2356 						outchar_stream(stream, QSPACE);
2357 					}
2358 					break;
2359 
2360 				case '^':                      /* capitalize next character */
2361 					stream->capsflag = 1;
2362 					stream->nocapsflag = 0;
2363 					break;
2364 
2365 				case 'v':
2366 					stream->nocapsflag = 1;
2367 					stream->capsflag = 0;
2368 					break;
2369 
2370 				case '(':
2371 					/* generate HTML if in the appropriate mode */
2372 					if (stream->html_mode && stream->html_target)
2373 					{
2374 						/* send HTML to the renderer */
2375 						outstring_stream(stream, "<B>");
2376 					}
2377 					else
2378 					{
2379 						/* turn on the 'hilite' attribute */
2380 						stream->cur_attr |= OS_ATTR_HILITE;
2381 					}
2382 					break;
2383 
2384 				case ')':
2385 					/* generate HTML if in the appropriate mode */
2386 					if (stream->html_mode && stream->html_target)
2387 					{
2388 						/* send HTML to the renderer */
2389 						outstring_stream(stream, "</B>");
2390 					}
2391 					else
2392 					{
2393 						/* turn off the 'hilite' attribute */
2394 						stream->cur_attr &= ~OS_ATTR_HILITE;
2395 					}
2396 					break;
2397 
2398 				case '-':
2399 					outchar_stream(stream, nextout(&s, &slen));
2400 					outchar_stream(stream, nextout(&s, &slen));
2401 					break;
2402 
2403 				default:                 /* just pass invalid escapes as-is */
2404 					outchar_stream(stream, c);
2405 					break;
2406 				}
2407 			}
2408 		}
2409 		else if (!stream->html_target
2410 				 && stream->html_mode
2411 				 && (c == '<' || c == '&'))
2412 		{
2413 			/*
2414 			 *   We're in HTML mode, but the underlying target does not
2415 			 *   accept HTML sequences.  It appears we're at the start of
2416 			 *   an "&" entity or a tag sequence, so parse it, remove it,
2417 			 *   and replace it (if possible) with a text-only equivalent.
2418 			 */
2419 			if (c == '<')
2420 			{
2421 				/* read the tag */
2422 				char tagbuf[50];
2423 				int is_end_tag;
2424 				c = read_tag(tagbuf, sizeof(tagbuf), &is_end_tag,
2425 							 &s, &slen, 0);
2426 
2427 				/*
2428 				 *   Check to see if we recognize the tag.  We only
2429 				 *   recognize a few simple tags that map easily to
2430 				 *   character mode.
2431 				 */
2432 				if (!scumm_stricmp(tagbuf, "br"))
2433 				{
2434 					/*
2435 					 *   line break - if there's anything buffered up,
2436 					 *   just flush the current line, otherwise write out
2437 					 *   a blank line
2438 					 */
2439 					if (stream->html_in_ignore)
2440 						/* suppress breaks in ignore mode */;
2441 					else if (stream->linepos != 0)
2442 						stream->html_defer_br = HTML_DEFER_BR_FLUSH;
2443 					else
2444 						stream->html_defer_br = HTML_DEFER_BR_BLANK;
2445 				}
2446 				else if (!scumm_stricmp(tagbuf, "b")
2447 						 || !scumm_stricmp(tagbuf, "i")
2448 						 || !scumm_stricmp(tagbuf, "em")
2449 						 || !scumm_stricmp(tagbuf, "strong"))
2450 				{
2451 					int attr = 0;
2452 
2453 					/* choose the attribute flag */
2454 					switch (tagbuf[0])
2455 					{
2456 					case 'b':
2457 					case 'B':
2458 						attr = OS_ATTR_BOLD;
2459 						break;
2460 
2461 					case 'i':
2462 					case 'I':
2463 						attr = OS_ATTR_ITALIC;
2464 						break;
2465 
2466 					case 'e':
2467 					case 'E':
2468 						attr = OS_ATTR_EM;
2469 						break;
2470 
2471 					case 's':
2472 					case 'S':
2473 						attr = OS_ATTR_STRONG;
2474 						break;
2475 					}
2476 
2477 					/* bold on/off - send out appropriate os-layer code */
2478 					if (stream->html_in_ignore)
2479 					{
2480 						/* suppress any change in 'ignore' mode */
2481 					}
2482 					else if (!is_end_tag)
2483 					{
2484 						/* turn on the selected attribute */
2485 						stream->cur_attr |= attr;
2486 					}
2487 					else
2488 					{
2489 						/* turn off the selected attribute */
2490 						stream->cur_attr &= ~attr;
2491 					}
2492 				}
2493 				else if (!scumm_stricmp(tagbuf, "p"))
2494 				{
2495 					/* paragraph - send out a blank line */
2496 					if (!stream->html_in_ignore)
2497 						outblank_stream(stream);
2498 				}
2499 				else if (!scumm_stricmp(tagbuf, "tab"))
2500 				{
2501 					/* tab - send out a \t */
2502 					if (!stream->html_in_ignore)
2503 						outtab_stream(stream);
2504 				}
2505 				else if (!scumm_stricmp(tagbuf, "img") || !scumm_stricmp(tagbuf, "sound"))
2506 				{
2507 					/* IMG and SOUND - allow ALT attributes */
2508 					stream->html_allow_alt = TRUE;
2509 				}
2510 				else if (!scumm_stricmp(tagbuf, "hr"))
2511 				{
2512 					int rem;
2513 
2514 					if (!stream->html_in_ignore)
2515 					{
2516 						/* start a new line */
2517 						outflushn_stream(stream, 1);
2518 
2519 						/* write out underscores to the display width */
2520 						for (rem = G_os_linewidth - 1 ; rem > 0 ; )
2521 						{
2522 							char dashbuf[100];
2523 							int cur;
2524 
2525 							/* do as much as we can on this pass */
2526 							cur = rem;
2527 							if ((size_t)cur > sizeof(dashbuf) - 1)
2528 								cur = sizeof(dashbuf) - 1;
2529 
2530 							/* do a buffer-full of dashes */
2531 							memset(dashbuf, '_', cur);
2532 							dashbuf[cur] = '\0';
2533 							outstring_stream(stream, dashbuf);
2534 
2535 							/* deduct this from the total */
2536 							rem -= cur;
2537 						}
2538 
2539 						/* put a blank line after the underscores */
2540 						outblank_stream(stream);
2541 					}
2542 				}
2543 				else if (!scumm_stricmp(tagbuf, "q"))
2544 				{
2545 					unsigned int htmlchar;
2546 
2547 					if (!stream->html_in_ignore)
2548 					{
2549 						/* if it's an open quote, increment the level */
2550 						if (!is_end_tag)
2551 							++(stream->html_quote_level);
2552 
2553 						/* add the open quote */
2554 						htmlchar =
2555 							(!is_end_tag
2556 							 ? ((stream->html_quote_level & 1) == 1
2557 								? 8220 : 8216)
2558 							 : ((stream->html_quote_level & 1) == 1
2559 								? 8221 : 8217));
2560 
2561 						/*
2562 						 *   write out the HTML character, translated to
2563 						 *   the local character set
2564 						 */
2565 						outchar_html_stream(stream, htmlchar);
2566 
2567 						/* if it's a close quote, decrement the level */
2568 						if (is_end_tag)
2569 							--(stream->html_quote_level);
2570 					}
2571 				}
2572 				else if (!scumm_stricmp(tagbuf, "title"))
2573 				{
2574 					/*
2575 					 *   Turn ignore mode on or off as appropriate, and
2576 					 *   turn on or off title mode as well.
2577 					 */
2578 					if (is_end_tag)
2579 					{
2580 						/*
2581 						 *   note that we're leaving an ignore section and
2582 						 *   a title section
2583 						 */
2584 						--(stream->html_in_ignore);
2585 						--(stream->html_in_title);
2586 
2587 						/*
2588 						 *   if we're no longer in a title, call the OS
2589 						 *   layer to tell it the title string, in case it
2590 						 *   wants to change the window title or otherwise
2591 						 *   make use of the title
2592 						 */
2593 						if (stream->html_in_title == 0)
2594 						{
2595 							/* null-terminate the title string */
2596 							*stream->html_title_ptr = '\0';
2597 
2598 							/* tell the OS about the title */
2599 							os_set_title(stream->html_title_buf);
2600 						}
2601 					}
2602 					else
2603 					{
2604 						/*
2605 						 *   if we aren't already in a title, set up to
2606 						 *   capture the title into the title buffer
2607 						 */
2608 						if (!stream->html_in_title)
2609 							stream->html_title_ptr = stream->html_title_buf;
2610 
2611 						/*
2612 						 *   note that we're in a title and in an ignore
2613 						 *   section, since nothing within gets displayed
2614 						 */
2615 						++(stream->html_in_ignore);
2616 						++(stream->html_in_title);
2617 					}
2618 				}
2619 				else if (!scumm_stricmp(tagbuf, "aboutbox"))
2620 				{
2621 					/* turn ignore mode on or off as appropriate */
2622 					if (is_end_tag)
2623 						--(stream->html_in_ignore);
2624 					else
2625 						++(stream->html_in_ignore);
2626 				}
2627 				else if (!scumm_stricmp(tagbuf, "pre"))
2628 				{
2629 					/* count the nesting level if starting PRE mode */
2630 					if (!is_end_tag)
2631 						stream->html_pre_level += 1;
2632 
2633 					/* surround the PRE block with line breaks */
2634 					outblank_stream(stream);
2635 
2636 					/* count the nesting level if ending PRE mode */
2637 					if (is_end_tag && stream->html_pre_level != 0)
2638 						stream->html_pre_level -= 1;
2639 				}
2640 
2641 				/* suppress everything up to the next '>' */
2642 				stream->html_mode_flag = HTML_MODE_TAG;
2643 
2644 				/*
2645 				 *   continue with the current character; since we're in
2646 				 *   html tag mode, we'll skip everything until we get to
2647 				 *   the closing '>'
2648 				 */
2649 				continue;
2650 			}
2651 			else if (c == '&')
2652 			{
2653 				/* parse it */
2654 				char  xlat_buf[50];
2655 				c = out_parse_entity(xlat_buf, sizeof(xlat_buf), &s, &slen);
2656 
2657 				/* write it out (we've already translated it) */
2658 				outstring_noxlat_stream(stream, xlat_buf);
2659 
2660 				/* proceed with the next character */
2661 				continue;
2662 			}
2663 		}
2664 		else if (stream->html_target && stream->html_mode && c == '<')
2665 		{
2666 			/*
2667 			 *   We're in HTML mode, and we have an underlying HTML target.
2668 			 *   We don't need to do much HTML interpretation at this level.
2669 			 *   However, we do need to keep track of when we're in a PRE
2670 			 *   block, so that we can pass whitespaces and newlines through
2671 			 *   to the underlying HTML engine without filtering when we're
2672 			 *   in preformatted text.
2673 			 */
2674 			char tagbuf[50];
2675 			int is_end_tag;
2676 			c = read_tag(tagbuf, sizeof(tagbuf), &is_end_tag,
2677 						 &s, &slen, stream);
2678 
2679 			/* check for special tags */
2680 			if (!scumm_stricmp(tagbuf, "pre"))
2681 			{
2682 				/* count the nesting level */
2683 				if (!is_end_tag)
2684 					stream->html_pre_level += 1;
2685 				else if (is_end_tag && stream->html_pre_level != 0)
2686 					stream->html_pre_level -= 1;
2687 			}
2688 
2689 			/* copy the last character after the tag to the stream */
2690 			outchar_stream(stream, c);
2691 		}
2692 		else
2693 		{
2694 			/* normal character */
2695 			outchar_stream(stream, c);
2696 		}
2697 
2698 		/* move on to the next character, unless we're finished */
2699 		if (done)
2700 			c = '\0';
2701 		else
2702 			c = nextout(&s, &slen);
2703 	}
2704 
2705 	/* if we ended up inside what looked like a format string, dump string */
2706 	if (infmt)
2707 	{
2708 		outchar_stream(stream, '%');
2709 		for (f1 = fmsbuf ; f1 < f ; ++f1)
2710 			outchar_stream(stream, *f1);
2711 	}
2712 
2713 	/* exit a recursion level */
2714 	out_pop_stream();
2715 
2716 	/* success */
2717 	return 0;
2718 }
2719 
2720 /* ------------------------------------------------------------------------ */
2721 /*
2722  *   Parse an HTML entity markup
2723  */
out_parse_entity(char * outbuf,size_t outbuf_size,const char ** sp,size_t * slenp)2724 static char out_parse_entity(char *outbuf, size_t outbuf_size, const char **sp, size_t *slenp) {
2725 	char  ampbuf[10];
2726 	char *dst;
2727 	const char *orig_s;
2728 	size_t orig_slen;
2729 	const amp_tbl_t *ampptr;
2730 	size_t lo, hi, cur;
2731 	char  c;
2732 
2733 	/*
2734 	 *   remember where the part after the '&' begins, so we can come back
2735 	 *   here later if necessary
2736 	 */
2737 	orig_s = *sp;
2738 	orig_slen = *slenp;
2739 
2740 	/* get the character after the ampersand */
2741 	c = nextout(sp, slenp);
2742 
2743 	/* if it's numeric, parse the number */
2744 	if (c == '#')
2745 	{
2746 		uint val;
2747 
2748 		/* skip the '#' */
2749 		c = nextout(sp, slenp);
2750 
2751 		/* check for hex */
2752 		if (c == 'x' || c == 'X')
2753 		{
2754 			/* skip the 'x' */
2755 			c = nextout(sp, slenp);
2756 
2757 			/* read the hex number */
2758 			for (val = 0 ; Common::isXDigit((uchar)c) ; c = nextout(sp, slenp))
2759 			{
2760 				/* accumulate the current digit into the value */
2761 				val *= 16;
2762 				if (outisdg(c))
2763 					val += c - '0';
2764 				else if (c >= 'a' && c <= 'f')
2765 					val += c - 'a' + 10;
2766 				else
2767 					val += c - 'A' + 10;
2768 			}
2769 		}
2770 		else
2771 		{
2772 			/* read the number */
2773 			for (val = 0 ; outisdg(c) ; c = nextout(sp, slenp))
2774 			{
2775 				/* accumulate the current digit into the value */
2776 				val *= 10;
2777 				val += c - '0';
2778 			}
2779 		}
2780 
2781 		/* if we found a ';' at the end, skip it */
2782 		if (c == ';')
2783 			c = nextout(sp, slenp);
2784 
2785 		/* translate the character into the output buffer */
2786 		os_xlat_html4(val, outbuf, outbuf_size);
2787 
2788 		/* we're done with this character */
2789 		return c;
2790 	}
2791 
2792 	/*
2793 	 *   Parse the sequence after the '&'.  Parse up to the closing
2794 	 *   semicolon, or any non-alphanumeric, or until we fill up the buffer.
2795 	 */
2796 	for (dst = ampbuf ;
2797 		 c != '\0' && (outisdg(c) || outisal(c))
2798 			 && dst < ampbuf + sizeof(ampbuf) - 1 ;
2799 		 *dst++ = c, c = nextout(sp, slenp)) ;
2800 
2801 	/* null-terminate the name */
2802 	*dst = '\0';
2803 
2804 	/* do a binary search for the name */
2805 	lo = 0;
2806 	hi = sizeof(amp_tbl)/sizeof(amp_tbl[0]) - 1;
2807 	for (;;)
2808 	{
2809 		int diff;
2810 
2811 		/* if we've converged, look no further */
2812 		if (lo > hi || lo >= sizeof(amp_tbl)/sizeof(amp_tbl[0]))
2813 		{
2814 			ampptr = 0;
2815 			break;
2816 		}
2817 
2818 		/* split the difference */
2819 		cur = lo + (hi - lo)/2;
2820 		ampptr = &amp_tbl[cur];
2821 
2822 		/* see where we are relative to the target item */
2823 		diff = strcmp(ampptr->cname, ampbuf);
2824 		if (diff == 0)
2825 		{
2826 			/* this is it */
2827 			break;
2828 		}
2829 		else if (diff > 0)
2830 		{
2831 			/* make sure we don't go off the end */
2832 			if (cur == hi && cur == 0)
2833 			{
2834 				/* we've failed to find it */
2835 				ampptr = 0;
2836 				break;
2837 			}
2838 
2839 			/* this one is too high - check the lower half */
2840 			hi = (cur == hi ? hi - 1 : cur);
2841 		}
2842 		else
2843 		{
2844 			/* this one is too low - check the upper half */
2845 			lo = (cur == lo ? lo + 1 : cur);
2846 		}
2847 	}
2848 
2849 	/* skip to the appropriate next character */
2850 	if (c == ';')
2851 	{
2852 		/* name ended with semicolon - skip the semicolon */
2853 		c = nextout(sp, slenp);
2854 	}
2855 	else if (ampptr != 0)
2856 	{
2857 		int skipcnt;
2858 
2859 		/* found the name - skip its exact length */
2860 		skipcnt = strlen(ampptr->cname);
2861 		for (*sp = orig_s, *slenp = orig_slen ; skipcnt != 0 ;
2862 			 c = nextout(sp, slenp), --skipcnt) ;
2863 	}
2864 
2865 	/* if we found the entry, write out the character */
2866 	if (ampptr != 0)
2867 	{
2868 		/*
2869 		 *   if this one has an external mapping table entry, use the mapping
2870 		 *   table entry; otherwise, use the default OS routine mapping
2871 		 */
2872 		if (ampptr->expan != 0)
2873 		{
2874 			/*
2875 			 *   we have an explicit expansion from the mapping table file -
2876 			 *   use it
2877 			 */
2878 			size_t copylen = strlen(ampptr->expan);
2879 			if (copylen > outbuf_size - 1)
2880 				copylen = outbuf_size - 1;
2881 
2882 			memcpy(outbuf, ampptr->expan, copylen);
2883 			outbuf[copylen] = '\0';
2884 		}
2885 		else
2886 		{
2887 			/*
2888 			 *   there's no mapping table expansion - use the default OS code
2889 			 *   expansion
2890 			 */
2891 			os_xlat_html4(ampptr->html_cval, outbuf, outbuf_size);
2892 		}
2893 	}
2894 	else
2895 	{
2896 		/*
2897 		 *   didn't find it - output the '&' literally, then back up and
2898 		 *   output the entire sequence following
2899 		 */
2900 		*sp = orig_s;
2901 		*slenp = orig_slen;
2902 		c = nextout(sp, slenp);
2903 
2904 		/* fill in the '&' return value */
2905 		outbuf[0] = '&';
2906 		outbuf[1] = '\0';
2907 	}
2908 
2909 	/* return the next character */
2910 	return c;
2911 }
2912 
2913 
2914 
2915 /* ------------------------------------------------------------------------ */
2916 /*
2917  *   Initialize the output formatter
2918  */
out_init()2919 void out_init()
2920 {
2921 	/* not yet hiding output */
2922 	outflag = 1;
2923 	outcnt = 0;
2924 	hidout = 0;
2925 
2926 	/* initialize the standard display stream */
2927 	out_init_std(&G_std_disp);
2928 
2929 	/* initialize the log file stream */
2930 	out_init_log(&G_log_disp);
2931 }
2932 
2933 
2934 /* ------------------------------------------------------------------------ */
2935 /*
2936  *   initialize the property translation table
2937  */
tiosetfmt(tiocxdef * ctx,runcxdef * rctx,uchar * fbase,uint flen)2938 void tiosetfmt(tiocxdef *ctx, runcxdef *rctx, uchar *fbase, uint flen)
2939 {
2940 	VARUSED(ctx);
2941 	fmsbase = fbase;
2942 	fmstop = fbase + flen;
2943 	runctx = rctx;
2944 }
2945 
2946 
2947 /* ------------------------------------------------------------------------ */
2948 /*
2949  *   Map an HTML entity to a local character value.  The character table
2950  *   reader will call this routine during initialization if it finds HTML
2951  *   entities in the mapping table file.  We'll remember these mappings
2952  *   for use in translating HTML entities to the local character set.
2953  *
2954  *   Note that the standard run-time can only display a single character
2955  *   set, so every HTML entity that we display must be mapped to the
2956  *   single active native character set.
2957  */
tio_set_html_expansion(unsigned int html_char_val,const char * expansion,size_t expansion_len)2958 void tio_set_html_expansion(unsigned int html_char_val,
2959 							const char *expansion, size_t expansion_len)
2960 {
2961 	amp_tbl_t *p;
2962 
2963 	/* find the character value */
2964 	for (p = amp_tbl ;
2965 		 p < amp_tbl + sizeof(amp_tbl)/sizeof(amp_tbl[0]) ; ++p)
2966 	{
2967 		/* if this is the one, store it */
2968 		if (p->html_cval == html_char_val)
2969 		{
2970 			/* allocate space for it */
2971 			p->expan = (char *)osmalloc(expansion_len + 1);
2972 
2973 			/* save it */
2974 			memcpy(p->expan, expansion, expansion_len);
2975 			p->expan[expansion_len] = '\0';
2976 
2977 			/* no need to look any further */
2978 			return;
2979 		}
2980 	}
2981 }
2982 
2983 
2984 /* ------------------------------------------------------------------------ */
2985 /*
2986  *   Write out a c-style (null-terminated) string.
2987  */
outformat(const char * s)2988 int outformat(const char *s) {
2989 	return outformatlen(s, strlen(s));
2990 }
2991 
2992 
2993 /* ------------------------------------------------------------------------ */
2994 /*
2995  *   This routine sends out a string, one character at a time (via outchar).
2996  *   Escape codes ('\n', and so forth) are handled here.
2997  */
outformatlen(const char * s,uint slen)2998 int outformatlen(const char *s, uint slen) {
2999 	char     c;
3000 	uint     orig_slen;
3001 	const char *orig_s;
3002 	int      ret;
3003 	int      called_filter;
3004 
3005 	/* presume we'll return success */
3006 	ret = 0;
3007 
3008 	/* presume we won't call the filter function */
3009 	called_filter = FALSE;
3010 
3011 	/* if there's a user filter function to invoke, call it */
3012 	if (G_user_filter != MCMONINV)
3013 	{
3014 		/* push the string */
3015 		runpstr(runctx, s, slen, 1);
3016 
3017 		/* call the filter */
3018 		runfn(runctx, G_user_filter, 1);
3019 
3020 		/*
3021 		 *   note that we called the filter, so that we'll remove the
3022 		 *   result of the filter from the stack before we return
3023 		 */
3024 		called_filter = TRUE;
3025 
3026 		/* if the result is a string, use it in place of the original text */
3027 		if (runtostyp(runctx) == DAT_SSTRING)
3028 		{
3029 			runsdef val;
3030 			uchar *p;
3031 
3032 			/* pop the value */
3033 			runpop(runctx, &val);
3034 
3035 			/*
3036 			 *   get the text from the string, and use it as a replacement
3037 			 *   for the original string
3038 			 */
3039 			p = val.runsv.runsvstr;
3040 			slen = osrp2(p) - 2;
3041 			s = (char *)(p + 2);
3042 
3043 			/*
3044 			 *   push the string back onto the stack - this will ensure
3045 			 *   that the string stays referenced while we're working, so
3046 			 *   that the garbage collector won't delete it
3047 			 */
3048 			runrepush(runctx, &val);
3049 		}
3050 	}
3051 
3052 	/* remember the original string, before we scan the first character */
3053 	orig_s = s;
3054 	orig_slen = slen;
3055 
3056 	/* get the first character to display */
3057 	c = nextout(&s, &slen);
3058 
3059 	/* if the string is non-empty, note that we've displayed something */
3060 	if (c != 0)
3061 		outcnt = 1;
3062 
3063 	/* check to see if we're hiding output */
3064 	if (out_is_hidden())
3065 		goto done;
3066 
3067 	/* if the debugger is showing watchpoints, suppress all output */
3068 	if (outwxflag)
3069 		goto done;
3070 
3071 	/* display the string */
3072 	ret = outformatlen_stream(&G_std_disp, orig_s, orig_slen);
3073 
3074 	/* if there's a log file, write to the log file as well */
3075 	if (logfp != 0)
3076 	{
3077 		outformatlen_stream(&G_log_disp, orig_s, orig_slen);
3078 		osfflush(logfp);
3079 	}
3080 
3081 done:
3082 	/* if we called the filter, remove the result from the stack */
3083 	if (called_filter)
3084 		rundisc(runctx);
3085 
3086 	/* return the result from displaying to the screen */
3087 	return ret;
3088 }
3089 
3090 /* ------------------------------------------------------------------------ */
3091 /*
3092  *   Display a blank line
3093  */
outblank()3094 void outblank()
3095 {
3096 	/* note that we've displayed something */
3097 	outcnt = 1;
3098 
3099 	/* check to see if we're hiding output */
3100 	if (out_is_hidden())
3101 		return;
3102 
3103 	/* generate the newline to the standard display */
3104 	outblank_stream(&G_std_disp);
3105 
3106 	/* if we're logging, generate the newline to the log file as well */
3107 	if (logfp != 0)
3108 	{
3109 		outblank_stream(&G_log_disp);
3110 		osfflush(logfp);
3111 	}
3112 }
3113 
3114 
3115 /* ------------------------------------------------------------------------ */
3116 /*
3117  *   outcaps() - sets an internal flag which makes the next letter output
3118  *   a capital, whether it came in that way or not.  Set the same state in
3119  *   both formatters (standard and log).
3120  */
outcaps(void)3121 void outcaps(void)
3122 {
3123 	outcaps_stream(&G_std_disp);
3124 	outcaps_stream(&G_log_disp);
3125 }
3126 
3127 /*
3128  *   outnocaps() - sets the next letter to a miniscule, whether it came in
3129  *   that way or not.
3130  */
outnocaps(void)3131 void outnocaps(void)
3132 {
3133 	outnocaps_stream(&G_std_disp);
3134 	outnocaps_stream(&G_log_disp);
3135 }
3136 
3137 /* ------------------------------------------------------------------------ */
3138 /*
3139  *   Open a log file
3140  */
tiologopn(tiocxdef * ctx,char * fn)3141 int tiologopn(tiocxdef *ctx, char *fn)
3142 {
3143 	/* if there's an old log file, close it */
3144 	if (tiologcls(ctx))
3145 		return 1;
3146 
3147 	/* save the filename for later */
3148 	strcpy(logfname, fn);
3149 
3150 	/* open the new file */
3151 	logfp = osfopwt(fn, OSFTLOG);
3152 
3153 	/*
3154 	 *   Reset the log file's output formatter state, since we're opening
3155 	 *   a new file.
3156 	 */
3157 	out_init_log(&G_log_disp);
3158 
3159 	/*
3160 	 *   Set the log file's HTML source mode flag to the same value as is
3161 	 *   currently being used in the main display stream, so that it will
3162 	 *   interpret source markups the same way that the display stream is
3163 	 *   going to.
3164 	 */
3165 	G_log_disp.html_mode = G_std_disp.html_mode;
3166 
3167 	/* return 0 on success, non-zero on failure */
3168 	return (logfp == 0);
3169 }
3170 
3171 /*
3172  *   Close the log file
3173  */
tiologcls(tiocxdef * ctx)3174 int tiologcls(tiocxdef *ctx)
3175 {
3176 	/* if we have a file, close it */
3177 	if (logfp != 0)
3178 	{
3179 		/* close the handle */
3180 		osfcls(logfp);
3181 
3182 		/* set the system file type to "log file" */
3183 		os_settype(logfname, OSFTLOG);
3184 
3185 		/* forget about our log file handle */
3186 		logfp = 0;
3187 	}
3188 
3189 	/* success */
3190 	return 0;
3191 }
3192 
3193 /* ------------------------------------------------------------------------ */
3194 /*
3195  *   Write text explicitly to the log file.  This can be used to add
3196  *   special text (such as prompt text) that would normally be suppressed
3197  *   from the log file.  When more mode is turned off, we don't
3198  *   automatically copy text to the log file; any text that the caller
3199  *   knows should be in the log file during times when more mode is turned
3200  *   off can be explicitly added with this function.
3201  *
3202  *   If nl is true, we'll add a newline at the end of this text.  The
3203  *   caller should not include any newlines in the text being displayed
3204  *   here.
3205  */
out_logfile_print(const char * txt,int nl)3206 void out_logfile_print(const char *txt, int nl)
3207 {
3208 	/* if there's no log file, there's nothing to do */
3209 	if (logfp == 0)
3210 		return;
3211 
3212 	/* add the text */
3213 	os_fprintz(logfp, txt);
3214 
3215 	/* add a newline if desired */
3216 	if (nl)
3217 	{
3218 		/* add a normal newline */
3219 		os_fprintz(logfp, "\n");
3220 
3221 		/* if the logfile is an html target, write an HTML line break */
3222 		if (G_log_disp.html_target && G_log_disp.html_mode)
3223 			os_fprintz(logfp, "<BR HEIGHT=0>\n");
3224 	}
3225 
3226 	/* flush the output */
3227 	osfflush(logfp);
3228 }
3229 
3230 /* ------------------------------------------------------------------------ */
3231 /*
3232  *   Set the current MORE mode
3233  */
setmore(int state)3234 int setmore(int state)
3235 {
3236 	int oldstate = G_os_moremode;
3237 
3238 	G_os_moremode = state;
3239 	return oldstate;
3240 }
3241 
3242 /* ------------------------------------------------------------------------ */
3243 /*
3244  *   Run the MORE prompt.  If the output layer takes responsibility for
3245  *   pagination issues (i.e., USE_MORE is defined), we'll simply display
3246  *   the prompt and wait for input.  Otherwise, the OS layer controls the
3247  *   MORE prompt, so we'll call the OS-layer function to display the
3248  *   prompt.
3249  */
out_more_prompt()3250 void out_more_prompt()
3251 {
3252 #ifdef USE_MORE
3253 	/*
3254 	 *   USE_MORE defined - we take responsibility for pagination.  Show
3255 	 *   our default MORE prompt and wait for a keystroke.
3256 	 */
3257 
3258 	int done;
3259 	int next_page = FALSE;
3260 
3261 	/* display the "MORE" prompt */
3262 	os_printz("[More]");
3263 	os_flush();
3264 
3265 	/* wait for an acceptable keystroke */
3266 	for (done = FALSE ; !done ; )
3267 	{
3268 		os_event_info_t evt;
3269 
3270 		/* get an event */
3271 		switch(os_get_event(0, FALSE, &evt))
3272 		{
3273 		case OS_EVT_KEY:
3274 			switch(evt.key[0])
3275 			{
3276 			case ' ':
3277 				/* stop waiting, show one page */
3278 				done = TRUE;
3279 				next_page = TRUE;
3280 				break;
3281 
3282 			case '\r':
3283 			case '\n':
3284 				/* stop waiting, show one line */
3285 				done = TRUE;
3286 				next_page = FALSE;
3287 				break;
3288 
3289 			default:
3290 				/* ignore any other keystrokes */
3291 				break;
3292 			}
3293 			break;
3294 
3295 		case OS_EVT_EOF:
3296 			/* end of file - there's nothing to wait for now */
3297 			done = TRUE;
3298 			next_page = TRUE;
3299 
3300 			/* don't use more prompts any more, as the user can't respond */
3301 			G_os_moremode = FALSE;
3302 			break;
3303 
3304 		default:
3305 			/* ignore other events */
3306 			break;
3307 		}
3308 	}
3309 
3310 	/*
3311 	 *   Remove the prompt from the screen by backing up and overwriting
3312 	 *   it with spaces.  (Note that this assumes that we're running in
3313 	 *   some kind of terminal or character mode with a fixed-pitch font;
3314 	 *   if that's not the case, the OS layer should be taking
3315 	 *   responsibility for pagination anyway, so this code shouldn't be
3316 	 *   in use in the first place.)
3317 	 */
3318 	os_printz("\r      \r");
3319 
3320 	/*
3321 	 *   if they pressed the space key, it means that we should show an
3322 	 *   entire new page, so reset the line count to zero; otherwise,
3323 	 *   we'll want to display another MORE prompt at the very next line,
3324 	 *   so leave the line count alone
3325 	 */
3326 	if (next_page)
3327 		G_std_disp.linecnt = 0;
3328 
3329 #else /* USE_MORE */
3330 
3331 	/*
3332 	 *   USE_MORE is undefined - this means that the OS layer is taking
3333 	 *   all responsibility for pagination.  We must ask the OS layer to
3334 	 *   display the MORE prompt, because we can't make any assumptions
3335 	 *   about what the prompt looks like.
3336 	 */
3337 
3338 	os_more_prompt();
3339 
3340 #endif /* USE_MORE */
3341 }
3342 
3343 /* ------------------------------------------------------------------------ */
3344 /*
3345  *   reset output
3346  */
outreset(void)3347 void outreset(void)
3348 {
3349 	G_std_disp.linecnt = 0;
3350 }
3351 
3352 /* ------------------------------------------------------------------------ */
3353 /*
3354  *   Determine if HTML mode is active.  Returns true if so, false if not.
3355  *   Note that this merely indicates whether an "\H+" sequence is
3356  *   currently active -- this will return true after an "\H+" sequence,
3357  *   even on text-only interpreters.
3358  */
tio_is_html_mode()3359 int tio_is_html_mode()
3360 {
3361 	/* return the current HTML mode flag for the standard display stream */
3362 	return G_std_disp.html_mode;
3363 }
3364 
3365 
3366 /* ------------------------------------------------------------------------ */
3367 /*
3368  *   Capture routines.  Capture affects only the standard display output
3369  *   stream; there's no need to capture information redundantly in the log
3370  *   file stream.
3371  */
3372 
3373 /*
3374  *   Begin/end capturing
3375  */
tiocapture(tiocxdef * tioctx,mcmcxdef * memctx,int flag)3376 void tiocapture(tiocxdef *tioctx, mcmcxdef *memctx, int flag)
3377 {
3378 	if (flag)
3379 	{
3380 		/* create a new object if necessary */
3381 		if (G_std_disp.capture_obj == MCMONINV)
3382 		{
3383 			mcmalo(memctx, 256, &G_std_disp.capture_obj);
3384 			mcmunlck(memctx, G_std_disp.capture_obj);
3385 		}
3386 
3387 		/* remember the memory context */
3388 		G_std_disp.capture_ctx = memctx;
3389 	}
3390 
3391 	/*
3392 	 *   remember capture status in the standard output stream as well as
3393 	 *   the log stream
3394 	 */
3395 	G_std_disp.capturing = flag;
3396 	G_log_disp.capturing = flag;
3397 }
3398 
3399 /* clear all captured output */
tioclrcapture(tiocxdef * tioctx)3400 void tioclrcapture(tiocxdef *tioctx)
3401 {
3402 	G_std_disp.capture_ofs = 0;
3403 }
3404 
3405 /* clear captured output back to a given size */
tiopopcapture(tiocxdef * tioctx,uint orig_size)3406 void tiopopcapture(tiocxdef *tioctx, uint orig_size)
3407 {
3408 	G_std_disp.capture_ofs = orig_size;
3409 }
3410 
3411 /* get the object handle of the captured output */
tiogetcapture(tiocxdef * ctx)3412 mcmon tiogetcapture(tiocxdef *ctx)
3413 {
3414 	return G_std_disp.capture_obj;
3415 }
3416 
3417 /* get the amount of text captured */
tiocapturesize(tiocxdef * ctx)3418 uint tiocapturesize(tiocxdef *ctx)
3419 {
3420 	return G_std_disp.capture_ofs;
3421 }
3422 
3423 /* ------------------------------------------------------------------------ */
3424 /*
3425  *   set the current actor
3426  */
tiosetactor(tiocxdef * ctx,objnum actor)3427 void tiosetactor(tiocxdef *ctx, objnum actor)
3428 {
3429 	VARUSED(ctx);
3430 	cmdActor = actor;
3431 }
3432 
3433 /*
3434  *   get the current actor
3435  */
tiogetactor(tiocxdef * ctx)3436 objnum tiogetactor(tiocxdef *ctx)
3437 {
3438 	VARUSED(ctx);
3439 	return cmdActor;
3440 }
3441 
3442 /* ------------------------------------------------------------------------ */
3443 /*
3444  *   Flush the output line.  We'll write to both the standard display and
3445  *   the log file, as needed.
3446  */
outflushn(int nl)3447 void outflushn(int nl)
3448 {
3449 	/* flush the display stream */
3450 	outflushn_stream(&G_std_disp, nl);
3451 
3452 	/* flush the log stream, if we have an open log file */
3453 	if (logfp != 0)
3454 	{
3455 		outflushn_stream(&G_log_disp, nl);
3456 		osfflush(logfp);
3457 	}
3458 }
3459 
3460 /*
3461  *   flush the current line, and start a new line
3462  */
outflush(void)3463 void outflush(void)
3464 {
3465 	/* use the common flushing routine in mode 1 (regular newline) */
3466 	outflushn(1);
3467 }
3468 
3469 /* ------------------------------------------------------------------------ */
3470 /*
3471  *   Hidden text routines
3472  */
3473 
3474 /*
3475  *   outhide - hide output in the standard display stream
3476  */
outhide(void)3477 void outhide(void)
3478 {
3479 	outflag = 0;
3480 	outcnt = 0;
3481 	hidout = 0;
3482 }
3483 
3484 /*
3485  *   Check output status.  Indicate whether output is currently hidden,
3486  *   and whether any hidden output has occurred.
3487  */
outstat(int * hidden,int * output_occurred)3488 void outstat(int *hidden, int *output_occurred)
3489 {
3490 	*hidden = !outflag;
3491 	*output_occurred = outcnt;
3492 }
3493 
3494 /* set the flag to indicate that output has occurred */
outsethidden(void)3495 void outsethidden(void)
3496 {
3497 	outcnt = 1;
3498 	hidout = 1;
3499 }
3500 
3501 /*
3502  *   outshow() - turns output back on, and returns TRUE (1) if any output
3503  *   has occurred since the last outshow(), FALSE (0) otherwise.
3504  */
outshow(void)3505 int outshow(void)
3506 {
3507 	/* turn output back on */
3508 	outflag = 1;
3509 
3510 	/* if we're debugging, note the end of hidden output */
3511 	if (dbghid && hidout)
3512 	{
3513 		hidout = 0;
3514 		trcsho();
3515 	}
3516 
3517 	/* return the flag indicating whether hidden output occurred */
3518 	return outcnt;
3519 }
3520 
3521 /* ------------------------------------------------------------------------ */
3522 /*
3523  *   start/end watchpoint evaluation - suppress all dstring output
3524  */
outwx(int flag)3525 void outwx(int flag)
3526 {
3527 	outwxflag = flag;
3528 }
3529 
3530 
3531 /* ------------------------------------------------------------------------ */
3532 /*
3533  *   Set the user filter function.  Setting this to MCMONINV clears the
3534  *   filter.
3535  */
out_set_filter(objnum filter_fn)3536 void out_set_filter(objnum filter_fn)
3537 {
3538 	/* remember the filter function */
3539 	G_user_filter = filter_fn;
3540 }
3541 
3542 /* ------------------------------------------------------------------------ */
3543 /*
3544  *   Set the double-space mode
3545  */
out_set_doublespace(int dbl)3546 void out_set_doublespace(int dbl)
3547 {
3548 	/* remember the new setting */
3549 	doublespace = dbl;
3550 }
3551 
tio_askfile(const char * prompt,char * reply,int replen,int prompt_type,os_filetype_t file_type)3552 int tio_askfile(const char *prompt, char *reply, int replen, int prompt_type, os_filetype_t file_type) {
3553 	// let the OS layer handle it
3554 	return os_askfile(prompt, reply, replen, prompt_type, file_type);
3555 }
3556 
tio_input_dialog(int icon_id,const char * prompt,int standard_button_set,const char ** buttons,int button_count,int default_index,int cancel_index)3557 int tio_input_dialog(int icon_id, const char *prompt, int standard_button_set,
3558 	const char **buttons, int button_count,
3559 	int default_index, int cancel_index) {
3560 	// call the OS implementation
3561 	return os_input_dialog(icon_id, prompt, standard_button_set,
3562 		buttons, button_count,
3563 		default_index, cancel_index);
3564 }
3565 
outfmt(tiocxdef * ctx,uchar * txt)3566 void outfmt(tiocxdef *ctx, uchar *txt) {
3567 	uint len;
3568 
3569 	VARUSED(ctx);
3570 
3571 	/* read the length prefix */
3572 	len = osrp2(txt) - 2;
3573 	txt += 2;
3574 
3575 	/* write out the string */
3576 	tioputslen(ctx, (char *)txt, len);
3577 }
3578 
3579 } // End of namespace TADS2
3580 } // End of namespace TADS
3581 } // End of namespace Glk
3582