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