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, " ");
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 = &_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