aboutsummaryrefslogtreecommitdiff
path: root/engines/glk/tads/tads2/output.cpp
diff options
context:
space:
mode:
authorPaul Gilbert2019-05-17 13:51:43 -1000
committerPaul Gilbert2019-05-24 18:21:06 -0700
commit105a1b94bd9d5a0f10752e135671f4e9a4b0d8da (patch)
tree14a535ad171084e1908882a87b29fbd085ea93af /engines/glk/tads/tads2/output.cpp
parente83972f502142b4e6191b962a7be89829bd8d708 (diff)
downloadscummvm-rg350-105a1b94bd9d5a0f10752e135671f4e9a4b0d8da.tar.gz
scummvm-rg350-105a1b94bd9d5a0f10752e135671f4e9a4b0d8da.tar.bz2
scummvm-rg350-105a1b94bd9d5a0f10752e135671f4e9a4b0d8da.zip
GLK: TADS2: Added code for output, run, various miscellaneous
Diffstat (limited to 'engines/glk/tads/tads2/output.cpp')
-rw-r--r--engines/glk/tads/tads2/output.cpp3559
1 files changed, 3559 insertions, 0 deletions
diff --git a/engines/glk/tads/tads2/output.cpp b/engines/glk/tads/tads2/output.cpp
new file mode 100644
index 0000000000..6aa39b5683
--- /dev/null
+++ b/engines/glk/tads/tads2/output.cpp
@@ -0,0 +1,3559 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/tads2/character_map.h"
+#include "glk/tads/tads2/debug.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/memory_cache.h"
+#include "glk/tads/tads2/os.h"
+#include "glk/tads/tads2/run.h"
+#include "glk/tads/tads2/text_io.h"
+#include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/os_glk.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* Forward declarations */
+struct out_stream_info;
+
+/*
+ * use our own isxxx - anything outside the US ASCII range is not reliably
+ * classifiable by the normal C isxxx routines
+ */
+#define outissp(c) (((uchar)(c)) <= 127 && Common::isSpace((uchar)(c)))
+#define outisal(c) (((uchar)(c)) <= 127 && Common::isAlpha((uchar)(c)))
+#define outisdg(c) (((uchar)(c)) <= 127 && Common::isDigit((uchar)(c)))
+#define outisup(c) (((uchar)(c)) <= 127 && Common::isUpper((uchar)(c)))
+#define outislo(c) (((uchar)(c)) <= 127 && Common::isLower((uchar)(c)))
+
+
+/*
+ * Turn on formatter-level MORE mode, EXCEPT under any of the following
+ * conditions:
+ *
+ * - this is a MAC OS port
+ *. - this is an HTML TADS interpreter
+ *. - USE_OS_LINEWRAP is defined
+ *
+ * Formatter-level MORE mode and formatter-level line wrapping go together;
+ * you can't have one without the other. So, if USE_OS_LINEWRAP is
+ * defined, we must also use OS-level MORE mode, which means we don't want
+ * formatter-level MORE mode.
+ *
+ * For historical reasons, we check specifically for MAC_OS. This was the
+ * first platform for which OS-level MORE mode and OS-level line wrapping
+ * were invented; at the time, we foolishly failed to anticipate that more
+ * platforms might eventually come along with the same needs, so we coded a
+ * test for MAC_OS rather than some more abstract marker. For
+ * compatibility, we retain this specific test.
+ *
+ * USE_OS_LINEWRAP is intended as the more abstract marker we should
+ * originally have used. A port should #define USE_OS_LINEWRAP in its
+ * system-specific os_xxx.h header to turn on OS-level line wrapping and
+ * OS-level MORE mode. Ports should avoid adding new #ifndef tests for
+ * specific platforms here; we've only retained the MAC_OS test because we
+ * don't want to break the existing MAC_OS port.
+ */
+#ifndef MAC_OS
+# ifndef USE_HTML
+# ifndef USE_OS_LINEWRAP
+# define USE_MORE /* activate formatter-level more-mode */
+# endif /* USE_OS_LINEWRAP */
+# endif /* USE_HTML */
+#endif /* MAC_OS */
+
+/*
+ * In HTML mode, don't use MORE mode. Note that we explicitly turn MORE
+ * mode OFF, even though we won't have turned it on above, because it might
+ * have been turned on by an os_xxx.h header. This is here for historical
+ * reasons; in particular, some of the HTML interpreter builds include
+ * headers that were originally written for the normal builds for those
+ * same platforms, and those original headers explicitly #define USE_MORE
+ * somewhere. So, to be absolutely sure we get it right here, we have to
+ * explicitly turn off USE_MORE when compiling for HTML mode.
+ */
+#ifdef USE_HTML
+# ifdef USE_MORE
+# undef USE_MORE
+# endif
+#endif
+
+/*
+ * QSPACE is the special character for a quoted space (internally, the
+ * sequence "\ " (backslash-space) is converted to QSPACE). It must not
+ * be any printable character. The value here may need to be changed in
+ * the extremely unlikely event that TADS is ever ported to an EBCDIC
+ * machine.
+ */
+#define QSPACE 26
+
+/*
+ * QTAB is a special hard tab character indicator. We use this when we
+ * need to generate a hard tab to send to the underlying output layer
+ * (in particular, we use this to send hard tabs to the HTML formatter
+ * when we're in HTML mode).
+ */
+#define QTAB 25
+
+
+/* maximum width of the display */
+#define MAXWIDTH OS_MAXWIDTH
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Globals and statics. These should really be moved into a context
+ * structure, so that the output formatter subsystem could be shared
+ * among multiple clients. For now, there's no practical problem using
+ * statics, because we only need a single output subsystem at one time.
+ */
+
+/* current script (command input) file */
+extern osfildef *scrfp;
+
+/*
+ * This should be TRUE if the output should have two spaces after a
+ * period (or other such punctuation. It should generally be TRUE for
+ * fixed-width fonts, and FALSE for proportional fonts.
+ */
+static int doublespace = 1;
+
+/*
+ * Log file handle and name. If we're copying output to a log file,
+ * these will tell us about the file.
+ */
+osfildef *logfp;
+static char logfname[OSFNMAX];
+
+/* flag indicating whether output has occurred since last check */
+static uchar outcnt;
+
+/* flag indicating whether hidden output has occurred */
+static uchar hidout;
+
+/* flag indicating whether to show (TRUE) or hide (FALSE) output */
+static uchar outflag;
+
+/* flag indicating whether output is hidden for debugging purposes */
+int dbghid;
+
+/*
+ * Current recursion level in formatter invocation
+ */
+static int G_recurse = 0;
+
+/* active stream in current recursion level */
+static out_stream_info *G_cur_stream;
+
+/* watchpoint mode flag */
+static uchar outwxflag;
+
+/*
+ * User filter function. When this function is set, we'll invoke this
+ * function for each string that's displayed through the output
+ * formatter.
+ */
+static objnum G_user_filter = MCMONINV;
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Hack to run with TADS 2.0 with minimal reworking. Rather than using
+ * an allocated output layer context, store our subsystem context
+ * information in some statics. This is less clean than using a real
+ * context, but doesn't create any practical problems as we don't need
+ * to share the output formatter subsystem among multiple simultaneous
+ * callers.
+ */
+static runcxdef *runctx; /* execution context */
+static uchar *fmsbase; /* format string area base */
+static uchar *fmstop; /* format string area top */
+static objnum cmdActor; /* current actor */
+
+/* forward declarations of static functions */
+static void outstring_stream(out_stream_info *stream, char *s);
+static void outchar_noxlat_stream(out_stream_info *stream, char c);
+static char out_parse_entity(char *outbuf, size_t outbuf_size,
+ char **sp, size_t *slenp);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * HTML lexical analysis mode
+ */
+#define HTML_MODE_NORMAL 0 /* normal text, not in a tag */
+#define HTML_MODE_TAG 1 /* parsing inside a tag */
+#define HTML_MODE_SQUOTE 2 /* in a single-quoted string in a tag */
+#define HTML_MODE_DQUOTE 3 /* in a double-quoted string in a tag */
+
+/*
+ * HTML parsing mode flag for <BR> tags. We defer these until we've
+ * read the full tag in order to obey an HEIGHT attribute we find. When
+ * we encounter a <BR>, we figure out whether we think we'll need a
+ * flush or a blank line; if we find a HEIGHT attribute, we may change
+ * this opinion.
+ */
+#define HTML_DEFER_BR_NONE 0 /* no pending <BR> */
+#define HTML_DEFER_BR_FLUSH 1 /* only need an outflush() */
+#define HTML_DEFER_BR_BLANK 2 /* need an outblank() */
+
+/*
+ * If we're compiling for an HTML-enabled underlying output subsystem,
+ * we want to call the underlying OS layer when switching in and out of
+ * HTML mode. If the underlying system doesn't process HTML, we don't
+ * need to let it know anything about HTML mode.
+ */
+#ifdef USE_HTML
+# define out_start_html(stream) os_start_html()
+# define out_end_html(stream) os_end_html()
+#else
+# define out_start_html(stream)
+# define out_end_html(stream)
+#endif
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Output formatter stream state structure. This structure encapsulates
+ * the state of an individual output stream.
+ */
+struct out_stream_info {
+ /* low-level display routine (va_list version) */
+ void (*do_print)(out_stream_info *stream, const char *str);
+
+ /* current line position and output column */
+ uchar linepos;
+ uchar linecol;
+
+ /* number of lines on the screen (since last MORE prompt) */
+ int linecnt;
+
+ /* output buffer */
+ char linebuf[MAXWIDTH];
+
+ /*
+ * attribute buffer - we keep one attribute entry for each character in
+ * the line buffer
+ */
+ int attrbuf[MAXWIDTH];
+
+ /* current attribute for text we're buffering into linebuf */
+ int cur_attr;
+
+ /* last attribute we wrote to the osifc layer */
+ int os_attr;
+
+ /* CAPS mode - next character output is converted to upper-case */
+ uchar capsflag;
+
+ /* NOCAPS mode - next character output is converted to lower-case */
+ uchar nocapsflag;
+
+ /* ALLCAPS mode - all characters output are converted to upper-case */
+ uchar allcapsflag;
+
+ /* capture information */
+ mcmcxdef *capture_ctx; /* memory context to use for capturing */
+ mcmon capture_obj; /* object holding captured output */
+ uint capture_ofs; /* write offset in capture object */
+ int capturing; /* true -> we are capturing output */
+
+ /* "preview" state for line flushing */
+ int preview;
+
+ /* flag indicating that we just flushed a new line */
+ int just_did_nl;
+
+ /* this output stream uses "MORE" mode */
+ int use_more_mode;
+
+ /*
+ * This output stream uses OS-level line wrapping - if this is set,
+ * the output formatter will not insert a newline at the end of a
+ * line that it's flushing for word wrapping, but will instead let
+ * the underlying OS display layer handle the wrapping.
+ */
+ int os_line_wrap;
+
+ /*
+ * Flag indicating that the underlying output system wants to
+ * receive its output as HTML.
+ *
+ * If this is true, we'll pass through HTML to the underlying output
+ * system, and in addition generate HTML sequences for certain
+ * TADS-native escapes (for example, we'll convert the "\n" sequence
+ * to a <BR> sequence).
+ *
+ * If this is false, we'll do just the opposite: we'll remove HTML
+ * from the output stream and convert it into normal text sequences.
+ */
+ int html_target;
+
+ /*
+ * Flag indicating that the target uses plain text. If this flag is
+ * set, we won't add the OS escape codes for highlighted characters.
+ */
+ int plain_text_target;
+
+ /*
+ * Flag indicating that the caller is displaying HTML. We always
+ * start off in text mode; the client can switch to HTML mode by
+ * displaying a special escape sequence, and can switch back to text
+ * mode by displaying a separate special escape sequence.
+ */
+ int html_mode;
+
+ /* current lexical analysis mode */
+ unsigned int html_mode_flag;
+
+ /* <BR> defer mode */
+ unsigned int html_defer_br;
+
+ /*
+ * HTML "ignore" mode - we suppress all output when parsing the
+ * contents of a <TITLE> or <ABOUTBOX> tag
+ */
+ int html_in_ignore;
+
+ /*
+ * HTML <TITLE> mode - when we're in this mode, we're gathering the
+ * title (i.e., we're inside a <TITLE> tag's contents). We'll copy
+ * characters to the title buffer rather than the normal output
+ * buffer, and then call os_set_title() when we reach the </TITLE>
+ * tag.
+ */
+ int html_in_title;
+
+ /* buffer for the title */
+ char html_title_buf[256];
+
+ /* pointer to next available character in title buffer */
+ char *html_title_ptr;
+
+ /* quoting level */
+ int html_quote_level;
+
+ /* PRE nesting level */
+ int html_pre_level;
+
+ /*
+ * Parsing mode flag for ALT attributes. If we're parsing a tag
+ * that allows ALT, such as IMG or SOUND, we'll set this flag, then
+ * insert the ALT text if we encounter it during parsing.
+ */
+ int html_allow_alt;
+};
+
+/*
+ * Default output converter. This is the output converter for the
+ * standard display. Functions in the public interface that do not
+ * specify an output converter will use this converter by default.
+ */
+static out_stream_info G_std_disp;
+
+/*
+ * Log file converter. This is the output converter for a log file.
+ * Whenever we open a log file, we'll initialize this converter; as we
+ * display text to the main display, we'll also copy it to the log file.
+ *
+ * We maintain an entire separate conversion context for the log file,
+ * so that we can perform a different set of conversions on it. We may
+ * want, for example, to pass HTML text through to the OS display
+ * subsystem (this is the case for the HTML-enabled interpreter), but
+ * we'll still want to convert log file output to text. By keeping a
+ * separate display context for the log file, we can format output to
+ * the log file using an entirely different style than we do for the
+ * display.
+ */
+static out_stream_info G_log_disp;
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * low-level output handlers for the standard display and log file
+ */
+
+/* standard display printer */
+static void do_std_print(out_stream_info *stream, const char *str)
+{
+ VARUSED(stream);
+
+ /* display the text through the OS layer */
+ os_printz(str);
+}
+
+/* log file printer */
+static void do_log_print(out_stream_info *stream, const char *str)
+{
+ VARUSED(stream);
+
+ /* display to the log file */
+ if (logfp != 0 && G_os_moremode)
+ {
+ os_fprintz(logfp, str);
+ osfflush(logfp);
+ }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * initialize a generic output formatter state structure
+ */
+static void out_state_init(out_stream_info *stream)
+{
+ /* start out at the first column */
+ stream->linepos = 0;
+ stream->linecol = 0;
+ stream->linebuf[0] = '\0';
+
+ /* set normal text attributes */
+ stream->cur_attr = 0;
+ stream->os_attr = 0;
+
+ /* start out at the first line */
+ stream->linecnt = 0;
+
+ /* we're not in either "caps", "nocaps", or "allcaps" mode yet */
+ stream->capsflag = stream->nocapsflag = stream->allcapsflag = FALSE;
+
+ /* we're not capturing yet */
+ stream->capturing = FALSE;
+ stream->capture_obj = MCMONINV;
+
+ /* we aren't previewing a line yet */
+ stream->preview = 0;
+
+ /* we haven't flushed a new line yet */
+ stream->just_did_nl = FALSE;
+
+ /* presume this stream does not use "MORE" mode */
+ stream->use_more_mode = FALSE;
+
+ /* presume this stream uses formatter-level line wrapping */
+ stream->os_line_wrap = FALSE;
+
+ /* assume that the underlying system is not HTML-enabled */
+ stream->html_target = FALSE;
+
+ /* presume this target accepts OS highlighting sequences */
+ stream->plain_text_target = FALSE;
+
+ /* start out in text mode */
+ stream->html_mode = FALSE;
+
+ /* start out in "normal" lexical state */
+ stream->html_mode_flag = HTML_MODE_NORMAL;
+
+ /* not in an ignored tag yet */
+ stream->html_in_ignore = FALSE;
+
+ /* not in title mode yet */
+ stream->html_in_title = FALSE;
+
+ /* not yet deferring line breaks */
+ stream->html_defer_br = HTML_DEFER_BR_NONE;
+
+ /* not yet in quotes */
+ stream->html_quote_level = 0;
+
+ /* not yet in a PRE block */
+ stream->html_pre_level = 0;
+
+ /* not in an ALT tag yet */
+ stream->html_allow_alt = FALSE;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * initialize a standard display stream
+ */
+static void out_init_std(out_stream_info *stream)
+{
+ /* there's no user output filter function yet */
+ out_set_filter(MCMONINV);
+
+ /* initialize the basic stream state */
+ out_state_init(stream);
+
+ /* set up the low-level output routine */
+ G_std_disp.do_print = do_std_print;
+
+#ifdef USE_MORE
+ /*
+ * We're compiled for MORE mode, and we're not compiling for an
+ * underlying HTML formatting layer, so use MORE mode for the
+ * standard display stream.
+ */
+ stream->use_more_mode = TRUE;
+#else
+ /*
+ * We're compiled for OS-layer (or HTML-layer) MORE handling. For
+ * this case, use OS-layer (or HTML-layer) line wrapping as well.
+ */
+ stream->os_line_wrap = TRUE;
+#endif
+
+#ifdef USE_HTML
+ /*
+ * if we're compiled for HTML mode, set the standard output stream
+ * so that it knows it has an HTML target - this will ensure that
+ * HTML tags are passed through to the underlying stream, and that
+ * we generate HTML equivalents for our own control sequences
+ */
+ stream->html_target = TRUE;
+#endif
+}
+
+/*
+ * initialize a standard log file stream
+ */
+static void out_init_log(out_stream_info *stream)
+{
+ /* initialize the basic stream state */
+ out_state_init(stream);
+
+ /* set up the low-level output routine */
+ stream->do_print = do_log_print;
+
+ /* use plain text in the log file stream */
+ stream->plain_text_target = TRUE;
+}
+
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * table of '&' character name sequences
+ */
+struct amp_tbl_t {
+ /* entity name */
+ const char *cname;
+
+ /* HTML Unicode character value */
+ uint html_cval;
+
+ /* native character set expansion */
+ char *expan;
+};
+
+/*
+ * HTML entity mapping table. When we're in non-HTML mode, we keep our
+ * own expansion table so that we can map HTML entity names into the
+ * local character set.
+ *
+ * The entries in this table must be in sorted order (by HTML entity
+ * name), because we use a binary search to find an entity name in the
+ * table.
+ */
+static struct amp_tbl_t amp_tbl[] = {
+ { "AElig", 198, 0 },
+ { "Aacute", 193, 0 },
+ { "Abreve", 258, 0 },
+ { "Acirc", 194, 0 },
+ { "Agrave", 192, 0 },
+ { "Alpha", 913, 0 },
+ { "Aogon", 260, 0 },
+ { "Aring", 197, 0 },
+ { "Atilde", 195, 0 },
+ { "Auml", 196, 0 },
+ { "Beta", 914, 0 },
+ { "Cacute", 262, 0 },
+ { "Ccaron", 268, 0 },
+ { "Ccedil", 199, 0 },
+ { "Chi", 935, 0 },
+ { "Dagger", 8225, 0 },
+ { "Dcaron", 270, 0 },
+ { "Delta", 916, 0 },
+ { "Dstrok", 272, 0 },
+ { "ETH", 208, 0 },
+ { "Eacute", 201, 0 },
+ { "Ecaron", 282, 0 },
+ { "Ecirc", 202, 0 },
+ { "Egrave", 200, 0 },
+ { "Eogon", 280, 0 },
+ { "Epsilon", 917, 0 },
+ { "Eta", 919, 0 },
+ { "Euml", 203, 0 },
+ { "Gamma", 915, 0 },
+ { "Iacute", 205, 0 },
+ { "Icirc", 206, 0 },
+ { "Igrave", 204, 0 },
+ { "Iota", 921, 0 },
+ { "Iuml", 207, 0 },
+ { "Kappa", 922, 0 },
+ { "Lacute", 313, 0 },
+ { "Lambda", 923, 0 },
+ { "Lcaron", 317, 0 },
+ { "Lstrok", 321, 0 },
+ { "Mu", 924, 0 },
+ { "Nacute", 323, 0 },
+ { "Ncaron", 327, 0 },
+ { "Ntilde", 209, 0 },
+ { "Nu", 925, 0 },
+ { "OElig", 338, 0 },
+ { "Oacute", 211, 0 },
+ { "Ocirc", 212, 0 },
+ { "Odblac", 336, 0 },
+ { "Ograve", 210, 0 },
+ { "Omega", 937, 0 },
+ { "Omicron", 927, 0 },
+ { "Oslash", 216, 0 },
+ { "Otilde", 213, 0 },
+ { "Ouml", 214, 0 },
+ { "Phi", 934, 0 },
+ { "Pi", 928, 0 },
+ { "Prime", 8243, 0 },
+ { "Psi", 936, 0 },
+ { "Racute", 340, 0 },
+ { "Rcaron", 344, 0 },
+ { "Rho", 929, 0 },
+ { "Sacute", 346, 0 },
+ { "Scaron", 352, 0 },
+ { "Scedil", 350, 0 },
+ { "Sigma", 931, 0 },
+ { "THORN", 222, 0 },
+ { "Tau", 932, 0 },
+ { "Tcaron", 356, 0 },
+ { "Tcedil", 354, 0 },
+ { "Theta", 920, 0 },
+ { "Uacute", 218, 0 },
+ { "Ucirc", 219, 0 },
+ { "Udblac", 368, 0 },
+ { "Ugrave", 217, 0 },
+ { "Upsilon", 933, 0 },
+ { "Uring", 366, 0 },
+ { "Uuml", 220, 0 },
+ { "Xi", 926, 0 },
+ { "Yacute", 221, 0 },
+ { "Yuml", 376, 0 },
+ { "Zacute", 377, 0 },
+ { "Zcaron", 381, 0 },
+ { "Zdot", 379, 0 },
+ { "Zeta", 918, 0 },
+ { "aacute", 225, 0 },
+ { "abreve", 259, 0 },
+ { "acirc", 226, 0 },
+ { "acute", 180, 0 },
+ { "aelig", 230, 0 },
+ { "agrave", 224, 0 },
+ { "alefsym", 8501, 0 },
+ { "alpha", 945, 0 },
+ { "amp", '&', 0 },
+ { "and", 8743, 0 },
+ { "ang", 8736, 0 },
+ { "aogon", 261, 0 },
+ { "aring", 229, 0 },
+ { "asymp", 8776, 0 },
+ { "atilde", 227, 0 },
+ { "auml", 228, 0 },
+ { "bdquo", 8222, 0 },
+ { "beta", 946, 0 },
+ { "breve", 728, 0 },
+ { "brvbar", 166, 0 },
+ { "bull", 8226, 0 },
+ { "cacute", 263, 0 },
+ { "cap", 8745, 0 },
+ { "caron", 711, 0 },
+ { "ccaron", 269, 0 },
+ { "ccedil", 231, 0 },
+ { "cedil", 184, 0 },
+ { "cent", 162, 0 },
+ { "chi", 967, 0 },
+ { "circ", 710, 0 },
+ { "clubs", 9827, 0 },
+ { "cong", 8773, 0 },
+ { "copy", 169, 0 },
+ { "crarr", 8629, 0 },
+ { "cup", 8746, 0 },
+ { "curren", 164, 0 },
+ { "dArr", 8659, 0 },
+ { "dagger", 8224, 0 },
+ { "darr", 8595, 0 },
+ { "dblac", 733, 0 },
+ { "dcaron", 271, 0 },
+ { "deg", 176, 0 },
+ { "delta", 948, 0 },
+ { "diams", 9830, 0 },
+ { "divide", 247, 0 },
+ { "dot", 729, 0 },
+ { "dstrok", 273, 0 },
+ { "eacute", 233, 0 },
+ { "ecaron", 283, 0 },
+ { "ecirc", 234, 0 },
+ { "egrave", 232, 0 },
+ { "emdash", 8212, 0 },
+ { "empty", 8709, 0 },
+ { "endash", 8211, 0 },
+ { "eogon", 281, 0 },
+ { "epsilon", 949, 0 },
+ { "equiv", 8801, 0 },
+ { "eta", 951, 0 },
+ { "eth", 240, 0 },
+ { "euml", 235, 0 },
+ { "exist", 8707, 0 },
+ { "fnof", 402, 0 },
+ { "forall", 8704, 0 },
+ { "frac12", 189, 0 },
+ { "frac14", 188, 0 },
+ { "frac34", 190, 0 },
+ { "frasl", 8260, 0 },
+ { "gamma", 947, 0 },
+ { "ge", 8805, 0 },
+ { "gt", '>', 0 },
+ { "hArr", 8660, 0 },
+ { "harr", 8596, 0 },
+ { "hearts", 9829, 0 },
+ { "hellip", 8230, 0 },
+ { "iacute", 237, 0 },
+ { "icirc", 238, 0 },
+ { "iexcl", 161, 0 },
+ { "igrave", 236, 0 },
+ { "image", 8465, 0 },
+ { "infin", 8734, 0 },
+ { "int", 8747, 0 },
+ { "iota", 953, 0 },
+ { "iquest", 191, 0 },
+ { "isin", 8712, 0 },
+ { "iuml", 239, 0 },
+ { "kappa", 954, 0 },
+ { "lArr", 8656, 0 },
+ { "lacute", 314, 0 },
+ { "lambda", 955, 0 },
+ { "lang", 9001, 0 },
+ { "laquo", 171, 0 },
+ { "larr", 8592, 0 },
+ { "lcaron", 318, 0 },
+ { "lceil", 8968, 0 },
+ { "ldq", 8220, 0 },
+ { "ldquo", 8220, 0 },
+ { "le", 8804, 0 },
+ { "lfloor", 8970, 0 },
+ { "lowast", 8727, 0 },
+ { "loz", 9674, 0 },
+ { "lsaquo", 8249, 0 },
+ { "lsq", 8216, 0 },
+ { "lsquo", 8216, 0 },
+ { "lstrok", 322, 0 },
+ { "lt", '<', 0 },
+ { "macr", 175, 0 },
+ { "mdash", 8212, 0 },
+ { "micro", 181, 0 },
+ { "middot", 183, 0 },
+ { "minus", 8722, 0 },
+ { "mu", 956, 0 },
+ { "nabla", 8711, 0 },
+ { "nacute", 324, 0 },
+ { "nbsp", QSPACE, 0 },
+ { "ncaron", 328, 0 },
+ { "ndash", 8211, 0 },
+ { "ne", 8800, 0 },
+ { "ni", 8715, 0 },
+ { "not", 172, 0 },
+ { "notin", 8713, 0 },
+ { "nsub", 8836, 0 },
+ { "ntilde", 241, 0 },
+ { "nu", 957, 0 },
+ { "oacute", 243, 0 },
+ { "ocirc", 244, 0 },
+ { "odblac", 337, 0 },
+ { "oelig", 339, 0 },
+ { "ogon", 731, 0 },
+ { "ograve", 242, 0 },
+ { "oline", 8254, 0 },
+ { "omega", 969, 0 },
+ { "omicron", 959, 0 },
+ { "oplus", 8853, 0 },
+ { "or", 8744, 0 },
+ { "ordf", 170, 0 },
+ { "ordm", 186, 0 },
+ { "oslash", 248, 0 },
+ { "otilde", 245, 0 },
+ { "otimes", 8855, 0 },
+ { "ouml", 246, 0 },
+ { "para", 182, 0 },
+ { "part", 8706, 0 },
+ { "permil", 8240, 0 },
+ { "perp", 8869, 0 },
+ { "phi", 966, 0 },
+ { "pi", 960, 0 },
+ { "piv", 982, 0 },
+ { "plusmn", 177, 0 },
+ { "pound", 163, 0 },
+ { "prime", 8242, 0 },
+ { "prod", 8719, 0 },
+ { "prop", 8733, 0 },
+ { "psi", 968, 0 },
+ { "quot", '"', 0 },
+ { "rArr", 8658, 0 },
+ { "racute", 341, 0 },
+ { "radic", 8730, 0 },
+ { "rang", 9002, 0 },
+ { "raquo", 187, 0 },
+ { "rarr", 8594, 0 },
+ { "rcaron", 345, 0 },
+ { "rceil", 8969, 0 },
+ { "rdq", 8221, 0 },
+ { "rdquo", 8221, 0 },
+ { "real", 8476, 0 },
+ { "reg", 174, 0 },
+ { "rfloor", 8971, 0 },
+ { "rho", 961, 0 },
+ { "rsaquo", 8250, 0 },
+ { "rsq", 8217, 0 },
+ { "rsquo", 8217, 0 },
+ { "sacute", 347, 0 },
+ { "sbquo", 8218, 0 },
+ { "scaron", 353, 0 },
+ { "scedil", 351, 0 },
+ { "sdot", 8901, 0 },
+ { "sect", 167, 0 },
+ { "shy", 173, 0 },
+ { "sigma", 963, 0 },
+ { "sigmaf", 962, 0 },
+ { "sim", 8764, 0 },
+ { "spades", 9824, 0 },
+ { "sub", 8834, 0 },
+ { "sube", 8838, 0 },
+ { "sum", 8721, 0 },
+ { "sup", 8835, 0 },
+ { "sup1", 185, 0 },
+ { "sup2", 178, 0 },
+ { "sup3", 179, 0 },
+ { "supe", 8839, 0 },
+ { "szlig", 223, 0 },
+ { "tau", 964, 0 },
+ { "tcaron", 357, 0 },
+ { "tcedil", 355, 0 },
+ { "there4", 8756, 0 },
+ { "theta", 952, 0 },
+ { "thetasym", 977, 0 },
+ { "thorn", 254, 0 },
+ { "thorn", 254, 0 },
+ { "tilde", 732, 0 },
+ { "times", 215, 0 },
+ { "trade", 8482, 0 },
+ { "uArr", 8657, 0 },
+ { "uacute", 250, 0 },
+ { "uarr", 8593, 0 },
+ { "ucirc", 251, 0 },
+ { "udblac", 369, 0 },
+ { "ugrave", 249, 0 },
+ { "uml", 168, 0 },
+ { "upsih", 978, 0 },
+ { "upsilon", 965, 0 },
+ { "uring", 367, 0 },
+ { "uuml", 252, 0 },
+ { "weierp", 8472, 0 },
+ { "xi", 958, 0 },
+ { "yacute", 253, 0 },
+ { "yen", 165, 0 },
+ { "yuml", 255, 0 },
+ { "zacute", 378, 0 },
+ { "zcaron", 382, 0 },
+ { "zdot", 380, 0 },
+ { "zeta", 950, 0 }
+};
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * turn on CAPS mode for a stream
+ */
+static void outcaps_stream(out_stream_info *stream)
+{
+ /* turn on CAPS mode */
+ stream->capsflag = TRUE;
+
+ /* turn off NOCAPS and ALLCAPS mode */
+ stream->nocapsflag = FALSE;
+ stream->allcapsflag = FALSE;
+}
+
+/*
+ * turn on NOCAPS mode for a stream
+ */
+static void outnocaps_stream(out_stream_info *stream)
+{
+ /* turn on NOCAPS mode */
+ stream->nocapsflag = TRUE;
+
+ /* turn off CAPS and ALLCAPS mode */
+ stream->capsflag = FALSE;
+ stream->allcapsflag = FALSE;
+}
+
+/*
+ * turn on or off ALLCAPS mode for a stream
+ */
+static void outallcaps_stream(out_stream_info *stream, int all_caps)
+{
+ /* set the ALLCAPS flag */
+ stream->allcapsflag = all_caps;
+
+ /* clear the CAPS and NOCAPS flags */
+ stream->capsflag = FALSE;
+ stream->nocapsflag = FALSE;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * write a string to a stream
+ */
+static void stream_print(out_stream_info *stream, char *str)
+{
+ /* call the stream's do_print method */
+ (*stream->do_print)(stream, str);
+}
+
+/*
+ * Write out a line
+ */
+static void t_outline(out_stream_info *stream, int nl,
+ const char *txt, const int *attr)
+{
+ extern int scrquiet;
+
+ /*
+ * Check the "script quiet" mode - this indicates that we're reading
+ * a script and not echoing output to the display. If this mode is
+ * on, and we're writing to the display, suppress this write. If
+ * the mode is off, or we're writing to another stream (such as the
+ * log file), show the output as normal.
+ */
+ if (!scrquiet || stream != &G_std_disp)
+ {
+ size_t i;
+ char buf[MAXWIDTH];
+ char *dst;
+
+ /*
+ * Check to see if we've reached the end of the screen, and if
+ * so run the MORE prompt. Note that we don't make this check
+ * at all if USE_MORE is undefined, since this means that the OS
+ * layer code is taking responsibility for pagination issues.
+ * We also don't display a MORE prompt when reading from a
+ * script file.
+ *
+ * Note that we suppress the MORE prompt if nl == 0, since this
+ * is used to flush a partial line of text without starting a
+ * new line (for example, when displaying a prompt where the
+ * input will appear on the same line following the prompt).
+ *
+ * Skip the MORE prompt if this stream doesn't use it.
+ */
+ if (stream->use_more_mode
+ && scrfp == 0
+ && G_os_moremode
+ && nl != 0 && nl != 4
+ && stream->linecnt++ >= G_os_pagelength)
+ {
+ /* display the MORE prompt */
+ out_more_prompt();
+ }
+
+ /*
+ * Display the text. Run through the text in pieces; each time the
+ * attributes change, set attributes at the osifc level.
+ */
+ for (i = 0, dst = buf ; txt[i] != '\0' ; ++i)
+ {
+ /* if the attribute is changing, notify osifc */
+ if (attr != 0 && attr[i] != stream->os_attr)
+ {
+ /* flush the preceding text */
+ if (dst != buf)
+ {
+ *dst = '\0';
+ stream_print(stream, buf);
+ }
+
+ /* set the new attribute */
+ os_set_text_attr(attr[i]);
+
+ /* remember this as the last OS attribute */
+ stream->os_attr = attr[i];
+
+ /* start with a fresh buffer */
+ dst = buf;
+ }
+
+ /* buffer this character */
+ *dst++ = txt[i];
+ }
+
+ /* flush the last chunk of text */
+ if (dst != buf)
+ {
+ *dst = '\0';
+ stream_print(stream, buf);
+ }
+ }
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Flush the current line to the display. The 'nl' argument specifies
+ * what kind of flushing to do:
+ *
+ * 0: flush the current line but do not start a new line; more text will
+ * follow on the current line. This is used, for example, to flush text
+ * after displaying a prompt and before waiting for user input.
+ *
+ * 1: flush the line and start a new line.
+ *
+ * 2: flush the line as though starting a new line, but don't add an
+ * actual newline character to the output, since the underlying OS
+ * display code will handle this. Instead, add a space after the line.
+ * (This differs from mode 0 in that mode 0 shouldn't add anything at
+ * all after the line.)
+ *
+ * 3: "preview" mode. Flush the line, but do not start a new line, and
+ * retain the current text in the buffer. This is used for systems that
+ * handle the line wrapping in the underlying system code to flush a
+ * partially filled line that will need to be flushed again later.
+ *
+ * 4: same as mode 0, but used for internal buffer flushes only. Do not
+ * involve the underlying OS layer in this type of flush - simply flush
+ * our buffers with no separation.
+ */
+
+/* flush a given output stream */
+static void outflushn_stream(out_stream_info *stream, int nl)
+{
+ int i;
+
+ /* null-terminate the current output line buffer */
+ stream->linebuf[stream->linepos] = '\0';
+
+ /* note the position of the last character to display */
+ i = stream->linepos - 1;
+
+ /* if we're adding anything, remove trailing spaces */
+ if (nl != 0 && nl != 4)
+ {
+ /* look for last non-space character */
+ for ( ; i >= 0 && outissp(stream->linebuf[i]) ; --i) ;
+ }
+
+ /* check the output mode */
+ if (nl == 3)
+ {
+ /*
+ * this is the special "preview" mode -- only display the part
+ * that we haven't already previewed for this same line
+ */
+ if (i + 1 > stream->preview)
+ {
+ /* write out the line */
+ t_outline(stream, 0, &stream->linebuf[stream->preview],
+ &stream->attrbuf[stream->preview]);
+
+ /* skip past the part we wrote */
+ stream->preview += strlen(&stream->linebuf[stream->preview]);
+ }
+ }
+ else
+ {
+ char *suffix = nullptr; /* extra text to add after the flushed text */
+ int countnl = 0; /* true if line counts for [more] paging */
+
+ /* null-terminate the buffer at the current position */
+ stream->linebuf[++i] = '\0';
+
+ /* check the mode */
+ switch(nl)
+ {
+ case 0:
+ case 3:
+ case 4:
+ /* no newline - just flush out what we have with no suffix */
+ suffix = 0;
+ break;
+
+ case 1:
+ /*
+ * Add a newline. If there's nothing in the current line,
+ * or we just wrote out a newline, do not add an extra
+ * newline. Keep all newlines in PRE mode.
+ */
+ if (stream->linecol != 0 || !stream->just_did_nl
+ || stream->html_pre_level != 0)
+ {
+ /* add a newline after the text */
+ suffix = "\n";
+
+ /* count the line in the page size */
+ countnl = 1;
+ }
+ else
+ {
+ /* don't add a newline */
+ suffix = 0;
+ }
+ break;
+
+ case 2:
+ /*
+ * we're going to depend on the underlying OS output layer
+ * to do line breaking, so don't add a newline, but do add a
+ * space, so that the underlying OS layer knows we have a
+ * word break here
+ */
+ suffix = " ";
+ break;
+ }
+
+ /*
+ * display the line, as long as we have something buffered to
+ * display; even if we don't, display it if our column is
+ * non-zero and we didn't just do a newline, since this must
+ * mean that we've flushed a partial line and are just now doing
+ * the newline
+ */
+ if (stream->linebuf[stream->preview] != '\0'
+ || (stream->linecol != 0 && !stream->just_did_nl)
+ || stream->html_pre_level > 0)
+ {
+ /* write it out */
+ t_outline(stream, countnl, &stream->linebuf[stream->preview],
+ &stream->attrbuf[stream->preview]);
+
+ /* write the suffix, if any */
+ if (suffix != 0)
+ t_outline(stream, 0, suffix, 0);
+ }
+
+ /* generate an HTML line break if necessary */
+ if (nl == 1 && stream->html_mode && stream->html_target)
+ t_outline(stream, 0, "<BR HEIGHT=0>", 0);
+
+ if (nl == 0)
+ {
+ /* we're not displaying a newline, so flush what we have */
+ os_flush();
+ }
+ else
+ {
+ /* we displayed a newline, so reset the column position */
+ stream->linecol = 0;
+ }
+
+ /* reset the line output buffer position */
+ stream->linepos = stream->preview = 0;
+
+ /*
+ * If we just output a newline, note it. If we didn't just
+ * output a newline, but we did write out anything else, note
+ * that we're no longer at the start of a line on the underlying
+ * output device.
+ */
+ if (nl == 1)
+ stream->just_did_nl = TRUE;
+ else if (stream->linebuf[stream->preview] != '\0')
+ stream->just_did_nl = FALSE;
+ }
+
+ /*
+ * If the osifc-level attributes don't match the current attributes,
+ * bring the osifc layer up to date. This is necessary in cases where
+ * we set attributes immediately before asking for input - we
+ * essentially need to flush the attributes without flushing any text.
+ */
+ if (stream->cur_attr != stream->os_attr)
+ {
+ /* set the osifc attributes */
+ os_set_text_attr(stream->cur_attr);
+
+ /* remember the new attributes as the current osifc attributes */
+ stream->os_attr = stream->cur_attr;
+ }
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Determine if we're showing output. Returns true if output should be
+ * displayed, false if it should be suppressed. We'll note the output
+ * for hidden display accounting as needed.
+ */
+static int out_is_hidden()
+{
+ /* check the output flag */
+ if (!outflag)
+ {
+ /* trace the hidden output if desired */
+ if (dbghid && !hidout)
+ trchid();
+
+ /* note the hidden output */
+ hidout = 1;
+
+ /*
+ * unless we're showing hidden text in the debugger, we're
+ * suppressing output, so return true
+ */
+ if (!dbghid)
+ return TRUE;
+ }
+
+ /* we're not suppressing output */
+ return FALSE;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Display a blank line to the given stream
+ */
+static void outblank_stream(out_stream_info *stream)
+{
+ /* flush the stream */
+ outflushn_stream(stream, 1);
+
+ /* if generating for an HTML display target, add an HTML line break */
+ if (stream->html_mode && stream->html_target)
+ outstring_stream(stream, "<BR>");
+
+ /* write out the newline */
+ t_outline(stream, 1, "\n", 0);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Generate a tab for a "\t" sequence in the game text.
+ *
+ * Standard (non-HTML) version: we'll generate enough spaces to take us
+ * to the next tab stop.
+ *
+ * HTML version: if we're in native HTML mode, we'll just generate a
+ * <TAB MULTIPLE=4>; if we're not in HTML mode, we'll generate a hard
+ * tab character, which the HTML formatter will interpret as a <TAB
+ * MULTIPLE=4>.
+ */
+static void outtab_stream(out_stream_info *stream)
+{
+ /* check to see what the underlying system is expecting */
+ if (stream->html_target)
+ {
+ /* the underlying system is HTML - check for HTML mode */
+ if (stream->html_mode)
+ {
+ /* we're in HTML mode, so use the HTML <TAB> tag */
+ outstring_stream(stream, "<TAB MULTIPLE=4>");
+ }
+ else
+ {
+ /* we're not in HTML mode, so generate a hard tab character */
+ outchar_noxlat_stream(stream, QTAB);
+ }
+ }
+ else
+ {
+ int maxcol;
+
+ /*
+ * We're not in HTML mode - expand the tab with spaces. Figure
+ * the maximum column: if we're doing our own line wrapping, never
+ * go beyond the actual display width.
+ */
+ maxcol = (stream->os_line_wrap ? OS_MAXWIDTH : G_os_linewidth);
+
+ /* add the spaces */
+ do
+ {
+ stream->attrbuf[stream->linepos] = stream->cur_attr;
+ stream->linebuf[stream->linepos++] = ' ';
+ ++(stream->linecol);
+ } while (((stream->linecol + 1) & 3) != 0
+ && stream->linecol < maxcol);
+ }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Flush a line
+ */
+static void out_flushline(out_stream_info *stream, int padding)
+{
+ /*
+ * check to see if we're using the underlying display layer's line
+ * wrapping
+ */
+ if (stream->os_line_wrap)
+ {
+ /*
+ * In the HTML version, we don't need the normal *MORE*
+ * processing, since the HTML layer will handle that.
+ * Furthermore, we don't need to provide actual newline breaks
+ * -- that happens after the HTML is parsed, so we don't have
+ * enough information here to figure out actual line breaks.
+ * So, we'll just flush out our buffer whenever it fills up, and
+ * suppress newlines.
+ *
+ * Similarly, if we have OS-level MORE processing, don't try to
+ * figure out where the line breaks go -- just flush our buffer
+ * without a trailing newline whenever the buffer is full, and
+ * let the OS layer worry about formatting lines and paragraphs.
+ *
+ * If we're using padding, use mode 2. If we don't want padding
+ * (which is the case if we completely fill up the buffer
+ * without finding any word breaks), write out in mode 0, which
+ * just flushes the buffer exactly like it is.
+ */
+ outflushn_stream(stream, padding ? 2 : 4);
+ }
+ else
+ {
+ /*
+ * Normal mode - we process the *MORE* prompt ourselves, and we
+ * are responsible for figuring out where the actual line breaks
+ * go. Use outflush() to generate an actual newline whenever we
+ * flush out our buffer.
+ */
+ outflushn_stream(stream, 1);
+ }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Write a character to an output stream without translation
+ */
+static void outchar_noxlat_stream(out_stream_info *stream, char c)
+{
+ int i;
+ int qspace;
+
+ /* check for the special quoted space character */
+ if (c == QSPACE)
+ {
+ /* it's a quoted space - note it and convert it to a regular space */
+ qspace = 1;
+ c = ' ';
+ }
+ else if (c == QTAB)
+ {
+ /* it's a hard tab - convert it to an ordinary tab */
+ c = '\t';
+ qspace = 0;
+ }
+ else
+ {
+ /* translate any whitespace character to a regular space character */
+ if (outissp(c))
+ c = ' ';
+
+ /* it's not a quoted space */
+ qspace = 0;
+ }
+
+ /* check for the caps/nocaps flags */
+ if ((stream->capsflag || stream->allcapsflag) && outisal(c))
+ {
+ /* capsflag is set, so capitalize this character */
+ if (outislo(c))
+ c = toupper(c);
+
+ /* okay, we've capitalized something; clear flag */
+ stream->capsflag = 0;
+ }
+ else if (stream->nocapsflag && outisal(c))
+ {
+ /* nocapsflag is set, so minisculize this character */
+ if (outisup(c))
+ c = tolower(c);
+
+ /* clear the flag now that we've done the job */
+ stream->nocapsflag = 0;
+ }
+
+ /* if in capture mode, simply capture the character */
+ if (stream->capturing)
+ {
+ uchar *p;
+
+ /* if we have a valid capture object, copy to it */
+ if (stream->capture_obj != MCMONINV)
+ {
+ /* lock the object holding the captured text */
+ p = mcmlck(stream->capture_ctx, stream->capture_obj);
+
+ /* make sure the capture object is big enough */
+ if (mcmobjsiz(stream->capture_ctx, stream->capture_obj)
+ <= stream->capture_ofs)
+ {
+ /* expand the object by another 256 bytes */
+ p = mcmrealo(stream->capture_ctx, stream->capture_obj,
+ (ushort)(stream->capture_ofs + 256));
+ }
+
+ /* add this character */
+ *(p + stream->capture_ofs++) = c;
+
+ /* unlock the capture object */
+ mcmtch(stream->capture_ctx, stream->capture_obj);
+ mcmunlck(stream->capture_ctx, stream->capture_obj);
+ }
+
+ /*
+ * we're done - we don't want to actually display the character
+ * while capturing
+ */
+ return;
+ }
+
+ /* add the character to out output buffer, flushing as needed */
+ if (stream->linecol + 1 < G_os_linewidth)
+ {
+ /*
+ * there's room for this character, so add it to the buffer
+ */
+
+ /* ignore non-quoted space at start of line outside of PRE */
+ if (outissp(c) && c != '\t' && stream->linecol == 0 && !qspace
+ && stream->html_pre_level == 0)
+ return;
+
+ /* is this a non-quoted space not at the start of the line? */
+ if (outissp(c) && c != '\t' && stream->linecol != 0 && !qspace
+ && stream->html_pre_level == 0)
+ {
+ int pos1 = stream->linepos - 1;
+ char p = stream->linebuf[pos1]; /* check previous character */
+
+ /* ignore repeated spaces - collapse into a single space */
+ if (outissp(p))
+ return;
+
+ /*
+ * Certain punctuation requires a double space: a period, a
+ * question mark, an exclamation mark, or a colon; or any of
+ * these characters followed by any number of single and/or
+ * double quotes. First, scan back to before any quotes, if
+ * are on one now, then check the preceding character; if
+ * it's one of the punctuation marks requiring a double
+ * space, add this space a second time. (In addition to
+ * scanning back past quotes, scan past parentheses,
+ * brackets, and braces.) Don't double the spacing if we're
+ * not in the normal doublespace mode; some people may
+ * prefer single spacing after punctuation, so we make this
+ * a run-time option.
+ */
+ if (doublespace)
+ {
+ /* find the previous relevant punctuation character */
+ while (pos1 &&
+ (p == '"' || p == '\'' || p == ')' || p == ']'
+ || p == '}'))
+ {
+ p = stream->linebuf[--pos1];
+ }
+ if ( p == '.' || p == '?' || p == '!' || p == ':' )
+ {
+ /* a double-space is required after this character */
+ stream->attrbuf[stream->linepos] = stream->cur_attr;
+ stream->linebuf[stream->linepos++] = c;
+ ++(stream->linecol);
+ }
+ }
+ }
+
+ /* add this character to the buffer */
+ stream->attrbuf[stream->linepos] = stream->cur_attr;
+ stream->linebuf[stream->linepos++] = c;
+
+ /* advance the output column position */
+ ++(stream->linecol);
+ return;
+ }
+
+ /*
+ * The line would overflow if this character were added. Find the
+ * most recent word break, and output the line up to the previous
+ * word. Note that if we're trying to output a space, we'll just
+ * add it to the line buffer. If the last character of the line
+ * buffer is already a space, we won't do anything right now.
+ */
+ if (outissp(c) && c != '\t' && !qspace)
+ {
+ /* this is a space, so we're at a word break */
+ if (stream->linebuf[stream->linepos - 1] != ' ')
+ {
+ stream->attrbuf[stream->linepos] = stream->cur_attr;
+ stream->linebuf[stream->linepos++] = ' ';
+ }
+ return;
+ }
+
+ /*
+ * Find the most recent word break: look for a space or dash, starting
+ * at the end of the line.
+ *
+ * If we're about to write a hyphen, we want to skip all contiguous
+ * hyphens, because we want to keep them together as a single
+ * punctuation mark; then keep going in the normal manner, which will
+ * keep the hyphens plus the word they're attached to together as a
+ * single unit. If spaces precede the sequence of hyphens, include
+ * the prior word as well.
+ */
+ i = stream->linepos - 1;
+ if (c == '-')
+ {
+ /* skip any contiguous hyphens at the end of the line */
+ for ( ; i >= 0 && stream->linebuf[i] == '-' ; --i) ;
+
+ /* skip any spaces preceding the sequence of hyphens */
+ for ( ; i >= 0 && outissp(stream->linebuf[i]) ; --i) ;
+ }
+
+ /*
+ * Now find the preceding space. If we're doing our own wrapping
+ * (i.e., we're not using OS line wrapping), then look for the
+ * nearest hyphen as well.
+ */
+ for ( ; i >= 0 && !outissp(stream->linebuf[i])
+ && !(!stream->os_line_wrap && stream->linebuf[i] == '-') ; --i) ;
+
+ /* check to see if we found a good place to break */
+ if (i < 0)
+ {
+ /*
+ * we didn't find any good place to break - flush the entire
+ * line as-is, breaking arbitrarily in the middle of a word
+ */
+ out_flushline(stream, FALSE);
+
+ /*
+ * we've completely cleared out the line buffer, so reset all of
+ * the line buffer counters
+ */
+ stream->linepos = 0;
+ stream->linecol = 0;
+ stream->linebuf[0] = '\0';
+ }
+ else
+ {
+ char brkchar;
+ char tmpbuf[MAXWIDTH];
+ int tmpattr[MAXWIDTH];
+ size_t tmpcnt;
+
+ /* remember the word-break character */
+ brkchar = stream->linebuf[i];
+
+ /* null-terminate the line buffer */
+ stream->linebuf[stream->linepos] = '\0';
+
+ /* the next line starts after the break - save a copy */
+ tmpcnt = strlen(&stream->linebuf[i+1]);
+ memcpy(tmpbuf, &stream->linebuf[i+1], tmpcnt + 1);
+ memcpy(tmpattr, &stream->attrbuf[i+1], tmpcnt * sizeof(tmpattr[0]));
+
+ /*
+ * terminate the buffer at the space or after the hyphen,
+ * depending on where we broke
+ */
+ if (outissp(brkchar))
+ stream->linebuf[i] = '\0';
+ else
+ stream->linebuf[i+1] = '\0';
+
+ /* write out everything up to the word break */
+ out_flushline(stream, TRUE);
+
+ /* copy the next line into line buffer */
+ memcpy(stream->linebuf, tmpbuf, tmpcnt + 1);
+ memcpy(stream->attrbuf, tmpattr, tmpcnt * sizeof(tmpattr[0]));
+ stream->linepos = tmpcnt;
+
+ /*
+ * figure what column we're now in - count all of the printable
+ * characters in the new line
+ */
+ for (stream->linecol = 0, i = 0 ; i < stream->linepos ; ++i)
+ {
+ /* if it's printable, count it */
+ if (((unsigned char)stream->linebuf[i]) >= 26)
+ ++(stream->linecol);
+ }
+ }
+
+ /* add the new character to buffer */
+ stream->attrbuf[stream->linepos] = stream->cur_attr;
+ stream->linebuf[stream->linepos++] = c;
+
+ /* advance the column counter */
+ ++(stream->linecol);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Write out a character, translating to the local system character set
+ * from the game's internal character set.
+ */
+static void outchar_stream(out_stream_info *stream, char c)
+{
+ outchar_noxlat_stream(stream, cmap_i2n(c));
+}
+
+/*
+ * write out a string, translating to the local system character set
+ */
+static void outstring_stream(out_stream_info *stream, char *s)
+{
+ /* write out each character in the string */
+ for ( ; *s ; ++s)
+ outchar_stream(stream, *s);
+}
+
+/*
+ * write out a string without translation
+ */
+static void outstring_noxlat_stream(out_stream_info *stream, char *s)
+{
+ for ( ; *s ; ++s)
+ outchar_noxlat_stream(stream, *s);
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Write out an HTML character value, translating to the local character
+ * set.
+ */
+static void outchar_html_stream(out_stream_info *stream,
+ unsigned int htmlchar)
+{
+ amp_tbl_t *ampptr;
+
+ /*
+ * search for a mapping entry for this entity, in case it's defined
+ * in an external mapping file
+ */
+ for (ampptr = amp_tbl ;
+ ampptr < amp_tbl + sizeof(amp_tbl)/sizeof(amp_tbl[0]) ; ++ampptr)
+ {
+ /* if this is the one, stop looking */
+ if (ampptr->html_cval == htmlchar)
+ break;
+ }
+
+ /*
+ * If we found a mapping table entry, and the entry has an expansion
+ * from the external character mapping table file, use the external
+ * expansion; otherwise, use the default expansion.
+ */
+ if (ampptr >= amp_tbl + sizeof(amp_tbl)/sizeof(amp_tbl[0])
+ || ampptr->expan == 0)
+ {
+ char xlat_buf[50];
+
+ /*
+ * there's no external mapping table file expansion -- use the
+ * default OS mapping routine
+ */
+ os_xlat_html4(htmlchar, xlat_buf, sizeof(xlat_buf));
+ outstring_noxlat_stream(stream, xlat_buf);
+ }
+ else
+ {
+ /*
+ * use the explicit mapping from the mapping table file
+ */
+ outstring_noxlat_stream(stream, ampptr->expan);
+ }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Enter a recursion level. Returns TRUE if the caller should proceed
+ * with the operation, FALSE if not.
+ *
+ * If we're making a recursive call, thereby re-entering the formatter,
+ * and this stream is not the same as the enclosing stream, we want to
+ * ignore this call and suppress any output to this stream, so we'll
+ * return FALSE.
+ */
+static int out_push_stream(out_stream_info *stream)
+{
+ /*
+ * if we're already in the formatter, and the new stream doesn't
+ * match the enclosing recursion level's stream, tell the caller to
+ * abort the operation
+ */
+ if (G_recurse != 0 && G_cur_stream != stream)
+ return FALSE;
+
+ /* note the active stream */
+ G_cur_stream = stream;
+
+ /* count the entry */
+ ++G_recurse;
+
+ /* tell the caller to proceed */
+ return TRUE;
+}
+
+/*
+ * Leave a recursion level
+ */
+static void out_pop_stream()
+{
+ /* count the exit */
+ --G_recurse;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * nextout() returns the next character in a string, and updates the
+ * string pointer and remaining length. Returns zero if no more
+ * characters are available in the string.
+ */
+/* static char nextout(char **s, uint *len); */
+#define nextout(s, len) ((char)(*(len) == 0 ? 0 : (--(*(len)), *((*(s))++))))
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Get the next character, writing the previous character to the given
+ * output stream if it's not null.
+ */
+static char nextout_copy(char **s, size_t *slen,
+ char prv, out_stream_info *stream)
+{
+ /* if there's a stream, write the previous character to the stream */
+ if (stream != 0)
+ outchar_stream(stream, prv);
+
+ /* return the next character */
+ return nextout(s, slen);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Read an HTML tag, for our primitive mini-parser. If 'stream' is not
+ * null, we'll copy each character we read to the output stream. Returns
+ * the next character after the tag name.
+ */
+static char read_tag(char *dst, size_t dstlen, int *is_end_tag,
+ char **s, size_t *slen, out_stream_info *stream)
+{
+ char c;
+
+ /* skip the opening '<' */
+ c = nextout_copy(s, slen, '<', stream);
+
+ /* skip spaces */
+ while (outissp(c))
+ c = nextout_copy(s, slen, c, stream);
+
+ /* note if this is a closing tag */
+ if (c == '/' || c == '\\')
+ {
+ /* it's an end tag - note it and skip the slash */
+ *is_end_tag = TRUE;
+ c = nextout_copy(s, slen, c, stream);
+
+ /* skip yet more spaces */
+ while (outissp(c))
+ c = nextout_copy(s, slen, c, stream);
+ }
+ else
+ *is_end_tag = FALSE;
+
+ /*
+ * find the end of the tag name - the tag continues to the next space,
+ * '>', or end of line
+ */
+ for ( ; c != '\0' && !outissp(c) && c != '>' ;
+ c = nextout_copy(s, slen, c, stream))
+ {
+ /* add this to the tag buffer if it fits */
+ if (dstlen > 1)
+ {
+ *dst++ = c;
+ --dstlen;
+ }
+ }
+
+ /* null-terminate the tag name */
+ if (dstlen > 0)
+ *dst = '\0';
+
+ /* return the next character */
+ return c;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * display a string of a given length to a given stream
+ */
+static int outformatlen_stream(out_stream_info *stream,
+ char *s, size_t slen)
+{
+ char c;
+ int done = 0;
+ char fmsbuf[40]; /* space for constructing translation string */
+ uint fmslen;
+ char *f = 0;
+ char *f1;
+ int infmt = 0;
+
+ /*
+ * This routine can recurse because of format strings ("%xxx%"
+ * sequences). When we recurse, we want to ensure that the
+ * recursion is directed to the original stream only. So, note the
+ * current stream statically in case we re-enter the formatter.
+ */
+ if (!out_push_stream(stream))
+ return 0;
+
+ /* get the first character */
+ c = nextout(&s, &slen);
+
+ /* if we have anything to show, show it */
+ while (c != '\0')
+ {
+ /* check if we're collecting translation string */
+ if (infmt)
+ {
+ /*
+ * if the string is too long for our buffer, or we've come
+ * across a backslash (illegal in a format string), or we've
+ * come across an HTML-significant character ('&' or '<') in
+ * HTML mode, we must have a stray percent sign; dump the
+ * whole string so far and act as though we have no format
+ * string
+ */
+ if (c == '\\'
+ || f == &fmsbuf[sizeof(fmsbuf)]
+ || (stream->html_mode && (c == '<' || c == '&')))
+ {
+ outchar_stream(stream, '%');
+ for (f1 = fmsbuf ; f1 < f ; ++f1)
+ outchar_stream(stream, *f1);
+ infmt = 0;
+
+ /* process this character again */
+ continue;
+ }
+ else if (c == '%' && f == fmsbuf) /* double percent sign? */
+ {
+ outchar_stream(stream, '%'); /* send out a single '%' */
+ infmt = 0; /* no longer processing translation string */
+ }
+ else if (c == '%') /* found end of string? translate it if so */
+ {
+ uchar *fms;
+ int initcap = FALSE;
+ int allcaps = FALSE;
+ char fmsbuf_srch[sizeof(fmsbuf)];
+
+ /* null-terminate the string */
+ *f = '\0';
+
+ /* check for an init cap */
+ if (outisup(fmsbuf[0]))
+ {
+ /*
+ * note the initial capital, so that we follow the
+ * original capitalization in the substituted string
+ */
+ initcap = TRUE;
+
+ /*
+ * if the second letter is capitalized as well,
+ * capitalize the entire substituted string
+ */
+ if (fmsbuf[1] != '\0' && outisup(fmsbuf[1]))
+ {
+ /* use all caps */
+ allcaps = TRUE;
+ }
+ }
+
+ /* convert the entire string to lower case for searching */
+ strcpy(fmsbuf_srch, fmsbuf);
+ os_strlwr(fmsbuf_srch);
+
+ /* find the string in the format string table */
+ fmslen = strlen(fmsbuf_srch);
+ for (fms = fmsbase ; fms < fmstop ; )
+ {
+ uint propnum;
+ uint len;
+
+ /* get the information on this entry */
+ propnum = osrp2(fms);
+ len = osrp2(fms + 2) - 2;
+
+ /* check for a match */
+ if (len == fmslen &&
+ !memcmp(fms + 4, fmsbuf_srch, (size_t)len))
+ {
+ int old_all_caps;
+
+ /* note the current ALLCAPS mode */
+ old_all_caps = stream->allcapsflag;
+
+ /*
+ * we have a match - set the appropriate
+ * capitalization mode
+ */
+ if (allcaps)
+ outallcaps_stream(stream, TRUE);
+ else if (initcap)
+ outcaps_stream(stream);
+
+ /*
+ * evaluate the associated property to generate
+ * the substitution text
+ */
+ runppr(runctx, cmdActor, (prpnum)propnum, 0);
+
+ /* turn off ALLCAPS mode */
+ outallcaps_stream(stream, old_all_caps);
+
+ /* no need to look any further */
+ break;
+ }
+
+ /* move on to next formatstring if not yet found */
+ fms += len + 4;
+ }
+
+ /* if we can't find it, dump the format string as-is */
+ if (fms == fmstop)
+ {
+ outchar_stream(stream, '%');
+ for (f1 = fmsbuf ; f1 < f ; ++f1)
+ outchar_stream(stream, *f1);
+ outchar_stream(stream, '%');
+ }
+
+ /* no longer reading format string */
+ infmt = 0;
+ }
+ else
+ {
+ /* copy this character of the format string */
+ *f++ = c;
+ }
+
+ /* move on to the next character and continue scanning */
+ c = nextout(&s, &slen);
+ continue;
+ }
+
+ /*
+ * If we're parsing HTML here, and we're inside a tag, skip
+ * characters until we reach the end of the tag.
+ */
+ if (stream->html_mode_flag != HTML_MODE_NORMAL)
+ {
+ switch(stream->html_mode_flag)
+ {
+ case HTML_MODE_TAG:
+ /*
+ * keep skipping up to the closing '>', but note when we
+ * enter any quoted section
+ */
+ switch(c)
+ {
+ case '>':
+ /* we've reached the end of the tag */
+ stream->html_mode_flag = HTML_MODE_NORMAL;
+
+ /* if we have a deferred <BR>, process it now */
+ switch(stream->html_defer_br)
+ {
+ case HTML_DEFER_BR_NONE:
+ /* no deferred <BR> */
+ break;
+
+ case HTML_DEFER_BR_FLUSH:
+ outflushn_stream(stream, 1);
+ break;
+
+ case HTML_DEFER_BR_BLANK:
+ outblank_stream(stream);
+ break;
+ }
+
+ /* no more deferred <BR> pending */
+ stream->html_defer_br = HTML_DEFER_BR_NONE;
+
+ /* no more ALT attribute allowed */
+ stream->html_allow_alt = FALSE;
+ break;
+
+ case '"':
+ /* enter a double-quoted string */
+ stream->html_mode_flag = HTML_MODE_DQUOTE;
+ break;
+
+ case '\'':
+ /* enter a single-quoted string */
+ stream->html_mode_flag = HTML_MODE_SQUOTE;
+ break;
+
+ default:
+ /* if it's alphabetic, note the attribute name */
+ if (outisal(c))
+ {
+ char attrname[128];
+ char attrval[256];
+ char *dst;
+
+ /* gather up the attribute name */
+ for (dst = attrname ;
+ dst + 1 < attrname + sizeof(attrname) ; )
+ {
+ /* store this character */
+ *dst++ = c;
+
+ /* get the next character */
+ c = nextout(&s, &slen);
+
+ /* if it's not alphanumeric, stop scanning */
+ if (!outisal(c) && !outisdg(c))
+ break;
+ }
+
+ /* null-terminate the result */
+ *dst++ = '\0';
+
+ /* gather the value if present */
+ if (c == '=')
+ {
+ char qu;
+
+ /* skip the '=' */
+ c = nextout(&s, &slen);
+
+ /* if we have a quote, so note */
+ if (c == '"' || c == '\'')
+ {
+ /* remember the quote */
+ qu = c;
+
+ /* skip it */
+ c = nextout(&s, &slen);
+ }
+ else
+ {
+ /* no quote */
+ qu = 0;
+ }
+
+ /* read the value */
+ for (dst = attrval ;
+ dst + 1 < attrval + sizeof(attrval) ; )
+ {
+ /* store this character */
+ *dst++ = c;
+
+ /* read the next one */
+ c = nextout(&s, &slen);
+ if (c == '\0')
+ {
+ /*
+ * we've reached the end of the
+ * string, and we're still inside
+ * this attribute - abandon the
+ * attribute but note that we're
+ * inside a quoted string if
+ * necessary
+ */
+ if (qu == '"')
+ stream->html_mode_flag =
+ HTML_MODE_DQUOTE;
+ else if (qu == '\'')
+ stream->html_mode_flag =
+ HTML_MODE_SQUOTE;
+ else
+ stream->html_mode_flag
+ = HTML_MODE_TAG;
+
+ /* stop scanning the string */
+ break;
+ }
+
+ /*
+ * if we're looking for a quote, check
+ * for the closing quote; otherwise,
+ * check for alphanumerics
+ */
+ if (qu != 0)
+ {
+ /* if this is our quote, stop scanning */
+ if (c == qu)
+ break;
+ }
+ else
+ {
+ /* if it's non-alphanumeric, we're done */
+ if (!outisal(c) && !outisdg(c))
+ break;
+ }
+ }
+
+ /* skip the closing quote, if necessary */
+ if (qu != 0 && c == qu)
+ c = nextout(&s, &slen);
+
+ /* null-terminate the value string */
+ *dst = '\0';
+ }
+ else
+ {
+ /* no value */
+ attrval[0] = '\0';
+ }
+
+ /*
+ * see if we recognize it, and it's meaningful
+ * in the context of the current tag
+ */
+ if (!scumm_stricmp(attrname, "height")
+ && stream->html_defer_br != HTML_DEFER_BR_NONE)
+ {
+ int ht;
+
+ /*
+ * If the height is zero, always treat this
+ * as a non-blanking flush. If it's one,
+ * treat it as we originally planned to. If
+ * it's greater than one, add n blank lines.
+ */
+ ht = atoi(attrval);
+ if (ht == 0)
+ {
+ /* always use non-blanking flush */
+ stream->html_defer_br = HTML_DEFER_BR_FLUSH;
+ }
+ else if (ht == 1)
+ {
+ /* keep original setting */
+ }
+ else
+ {
+ for ( ; ht > 0 ; --ht)
+ outblank_stream(stream);
+ }
+ }
+ else if (!scumm_stricmp(attrname, "alt")
+ && !stream->html_in_ignore
+ && stream->html_allow_alt)
+ {
+ /* write out the ALT string */
+ outstring_stream(stream, attrval);
+ }
+
+ /*
+ * since we already read the next character,
+ * simply loop back immediately
+ */
+ continue;
+ }
+ break;
+ }
+ break;
+
+ case HTML_MODE_DQUOTE:
+ /* if we've reached the closing quote, return to tag state */
+ if (c == '"')
+ stream->html_mode_flag = HTML_MODE_TAG;
+ break;
+
+ case HTML_MODE_SQUOTE:
+ /* if we've reached the closing quote, return to tag state */
+ if (c == '\'')
+ stream->html_mode_flag = HTML_MODE_TAG;
+ break;
+ }
+
+ /*
+ * move on to the next character, and start over with the
+ * new character
+ */
+ c = nextout(&s, &slen);
+ continue;
+ }
+
+ /*
+ * If we're in a title, and this isn't the start of a new tag,
+ * skip the character - we suppress all regular text output
+ * inside a <TITLE> ... </TITLE> sequence.
+ */
+ if (stream->html_in_ignore && c != '<')
+ {
+ /* check for entities */
+ char cbuf[50];
+ if (c == '&')
+ {
+ /* translate the entity */
+ c = out_parse_entity(cbuf, sizeof(cbuf), &s, &slen);
+ }
+ else
+ {
+ /* it's an ordinary character - copy it out literally */
+ cbuf[0] = c;
+ cbuf[1] = '\0';
+
+ /* get the next character */
+ c = nextout(&s, &slen);
+ }
+
+ /*
+ * if we're gathering a title, and there's room in the title
+ * buffer for more (always leaving room for a null
+ * terminator), add this to the title buffer
+ */
+ if (stream->html_in_title)
+ {
+ char *cbp;
+ for (cbp = cbuf ; *cbp != '\0' ; ++cbp)
+ {
+ /* if there's room, add it */
+ if (stream->html_title_ptr + 1 <
+ stream->html_title_buf
+ + sizeof(stream->html_title_buf))
+ *stream->html_title_ptr++ = *cbp;
+ }
+ }
+
+ /* don't display anything in an ignore section */
+ continue;
+ }
+
+ if ( c == '%' ) /* translation string? */
+ {
+ infmt = 1;
+ f = fmsbuf;
+ }
+ else if ( c == '\\' ) /* special escape code? */
+ {
+ c = nextout(&s, &slen);
+
+ if (stream->capturing && c != '^' && c != 'v' && c != '\0')
+ {
+ outchar_stream(stream, '\\');
+ outchar_stream(stream, c);
+
+ /* keep the \- and also put out the next two chars */
+ if (c == '-')
+ {
+ outchar_stream(stream, nextout(&s, &slen));
+ outchar_stream(stream, nextout(&s, &slen));
+ }
+ }
+ else
+ {
+ switch(c)
+ {
+ case 'H': /* HTML mode entry */
+ /* turn on HTML mode in the renderer */
+ switch(c = nextout(&s, &slen))
+ {
+ case '-':
+ /* if we have an HTML target, notify it */
+ if (stream->html_target)
+ {
+ /* flush its stream */
+ outflushn_stream(stream, 0);
+
+ /* tell the OS layer to switch to normal mode */
+ out_end_html(stream);
+ }
+
+ /* switch to normal mode */
+ stream->html_mode = FALSE;
+ break;
+
+ case '+':
+ default:
+ /* if we have an HTML target, notify it */
+ if (stream->html_target)
+ {
+ /* flush the underlying stream */
+ outflushn_stream(stream, 0);
+
+ /* tell the OS layer to switch to HTML mode */
+ out_start_html(stream);
+ }
+
+ /* switch to HTML mode */
+ stream->html_mode = TRUE;
+
+ /*
+ * if the character wasn't a "+", it's not part
+ * of the "\H" sequence, so display it normally
+ */
+ if (c != '+' && c != 0)
+ outchar_stream(stream, c);
+ break;
+ }
+
+ /* this sequence doesn't result in any actual output */
+ break;
+
+ case 'n': /* newline? */
+ outflushn_stream(stream, 1); /* yes, output line */
+ break;
+
+ case 't': /* tab? */
+ outtab_stream(stream);
+ break;
+
+ case 'b': /* blank line? */
+ outblank_stream(stream);
+ break;
+
+ case '\0': /* line ends here? */
+ done = 1;
+ break;
+
+ case ' ': /* quoted space */
+ if (stream->html_target && stream->html_mode)
+ {
+ /*
+ * we're generating for an HTML target and we're
+ * in HTML mode - generate the HTML non-breaking
+ * space
+ */
+ outstring_stream(stream, "&nbsp;");
+ }
+ else
+ {
+ /*
+ * we're not in HTML mode - generate our
+ * internal quoted space character
+ */
+ outchar_stream(stream, QSPACE);
+ }
+ break;
+
+ case '^': /* capitalize next character */
+ stream->capsflag = 1;
+ stream->nocapsflag = 0;
+ break;
+
+ case 'v':
+ stream->nocapsflag = 1;
+ stream->capsflag = 0;
+ break;
+
+ case '(':
+ /* generate HTML if in the appropriate mode */
+ if (stream->html_mode && stream->html_target)
+ {
+ /* send HTML to the renderer */
+ outstring_stream(stream, "<B>");
+ }
+ else
+ {
+ /* turn on the 'hilite' attribute */
+ stream->cur_attr |= OS_ATTR_HILITE;
+ }
+ break;
+
+ case ')':
+ /* generate HTML if in the appropriate mode */
+ if (stream->html_mode && stream->html_target)
+ {
+ /* send HTML to the renderer */
+ outstring_stream(stream, "</B>");
+ }
+ else
+ {
+ /* turn off the 'hilite' attribute */
+ stream->cur_attr &= ~OS_ATTR_HILITE;
+ }
+ break;
+
+ case '-':
+ outchar_stream(stream, nextout(&s, &slen));
+ outchar_stream(stream, nextout(&s, &slen));
+ break;
+
+ default: /* just pass invalid escapes as-is */
+ outchar_stream(stream, c);
+ break;
+ }
+ }
+ }
+ else if (!stream->html_target
+ && stream->html_mode
+ && (c == '<' || c == '&'))
+ {
+ /*
+ * We're in HTML mode, but the underlying target does not
+ * accept HTML sequences. It appears we're at the start of
+ * an "&" entity or a tag sequence, so parse it, remove it,
+ * and replace it (if possible) with a text-only equivalent.
+ */
+ if (c == '<')
+ {
+ /* read the tag */
+ char tagbuf[50];
+ int is_end_tag;
+ c = read_tag(tagbuf, sizeof(tagbuf), &is_end_tag,
+ &s, &slen, 0);
+
+ /*
+ * Check to see if we recognize the tag. We only
+ * recognize a few simple tags that map easily to
+ * character mode.
+ */
+ if (!scumm_stricmp(tagbuf, "br"))
+ {
+ /*
+ * line break - if there's anything buffered up,
+ * just flush the current line, otherwise write out
+ * a blank line
+ */
+ if (stream->html_in_ignore)
+ /* suppress breaks in ignore mode */;
+ else if (stream->linepos != 0)
+ stream->html_defer_br = HTML_DEFER_BR_FLUSH;
+ else
+ stream->html_defer_br = HTML_DEFER_BR_BLANK;
+ }
+ else if (!scumm_stricmp(tagbuf, "b")
+ || !scumm_stricmp(tagbuf, "i")
+ || !scumm_stricmp(tagbuf, "em")
+ || !scumm_stricmp(tagbuf, "strong"))
+ {
+ int attr;
+
+ /* choose the attribute flag */
+ switch (tagbuf[0])
+ {
+ case 'b':
+ case 'B':
+ attr = OS_ATTR_BOLD;
+ break;
+
+ case 'i':
+ case 'I':
+ attr = OS_ATTR_ITALIC;
+ break;
+
+ case 'e':
+ case 'E':
+ attr = OS_ATTR_EM;
+ break;
+
+ case 's':
+ case 'S':
+ attr = OS_ATTR_STRONG;
+ break;
+ }
+
+ /* bold on/off - send out appropriate os-layer code */
+ if (stream->html_in_ignore)
+ {
+ /* suppress any change in 'ignore' mode */
+ }
+ else if (!is_end_tag)
+ {
+ /* turn on the selected attribute */
+ stream->cur_attr |= attr;
+ }
+ else
+ {
+ /* turn off the selected attribute */
+ stream->cur_attr &= ~attr;
+ }
+ }
+ else if (!scumm_stricmp(tagbuf, "p"))
+ {
+ /* paragraph - send out a blank line */
+ if (!stream->html_in_ignore)
+ outblank_stream(stream);
+ }
+ else if (!scumm_stricmp(tagbuf, "tab"))
+ {
+ /* tab - send out a \t */
+ if (!stream->html_in_ignore)
+ outtab_stream(stream);
+ }
+ else if (!scumm_stricmp(tagbuf, "img") || !scumm_stricmp(tagbuf, "sound"))
+ {
+ /* IMG and SOUND - allow ALT attributes */
+ stream->html_allow_alt = TRUE;
+ }
+ else if (!scumm_stricmp(tagbuf, "hr"))
+ {
+ int rem;
+
+ if (!stream->html_in_ignore)
+ {
+ /* start a new line */
+ outflushn_stream(stream, 1);
+
+ /* write out underscores to the display width */
+ for (rem = G_os_linewidth - 1 ; rem > 0 ; )
+ {
+ char dashbuf[100];
+ int cur;
+
+ /* do as much as we can on this pass */
+ cur = rem;
+ if ((size_t)cur > sizeof(dashbuf) - 1)
+ cur = sizeof(dashbuf) - 1;
+
+ /* do a buffer-full of dashes */
+ memset(dashbuf, '_', cur);
+ dashbuf[cur] = '\0';
+ outstring_stream(stream, dashbuf);
+
+ /* deduct this from the total */
+ rem -= cur;
+ }
+
+ /* put a blank line after the underscores */
+ outblank_stream(stream);
+ }
+ }
+ else if (!scumm_stricmp(tagbuf, "q"))
+ {
+ unsigned int htmlchar;
+
+ if (!stream->html_in_ignore)
+ {
+ /* if it's an open quote, increment the level */
+ if (!is_end_tag)
+ ++(stream->html_quote_level);
+
+ /* add the open quote */
+ htmlchar =
+ (!is_end_tag
+ ? ((stream->html_quote_level & 1) == 1
+ ? 8220 : 8216)
+ : ((stream->html_quote_level & 1) == 1
+ ? 8221 : 8217));
+
+ /*
+ * write out the HTML character, translated to
+ * the local character set
+ */
+ outchar_html_stream(stream, htmlchar);
+
+ /* if it's a close quote, decrement the level */
+ if (is_end_tag)
+ --(stream->html_quote_level);
+ }
+ }
+ else if (!scumm_stricmp(tagbuf, "title"))
+ {
+ /*
+ * Turn ignore mode on or off as appropriate, and
+ * turn on or off title mode as well.
+ */
+ if (is_end_tag)
+ {
+ /*
+ * note that we're leaving an ignore section and
+ * a title section
+ */
+ --(stream->html_in_ignore);
+ --(stream->html_in_title);
+
+ /*
+ * if we're no longer in a title, call the OS
+ * layer to tell it the title string, in case it
+ * wants to change the window title or otherwise
+ * make use of the title
+ */
+ if (stream->html_in_title == 0)
+ {
+ /* null-terminate the title string */
+ *stream->html_title_ptr = '\0';
+
+ /* tell the OS about the title */
+ os_set_title(stream->html_title_buf);
+ }
+ }
+ else
+ {
+ /*
+ * if we aren't already in a title, set up to
+ * capture the title into the title buffer
+ */
+ if (!stream->html_in_title)
+ stream->html_title_ptr = stream->html_title_buf;
+
+ /*
+ * note that we're in a title and in an ignore
+ * section, since nothing within gets displayed
+ */
+ ++(stream->html_in_ignore);
+ ++(stream->html_in_title);
+ }
+ }
+ else if (!scumm_stricmp(tagbuf, "aboutbox"))
+ {
+ /* turn ignore mode on or off as appropriate */
+ if (is_end_tag)
+ --(stream->html_in_ignore);
+ else
+ ++(stream->html_in_ignore);
+ }
+ else if (!scumm_stricmp(tagbuf, "pre"))
+ {
+ /* count the nesting level if starting PRE mode */
+ if (!is_end_tag)
+ stream->html_pre_level += 1;
+
+ /* surround the PRE block with line breaks */
+ outblank_stream(stream);
+
+ /* count the nesting level if ending PRE mode */
+ if (is_end_tag && stream->html_pre_level != 0)
+ stream->html_pre_level -= 1;
+ }
+
+ /* suppress everything up to the next '>' */
+ stream->html_mode_flag = HTML_MODE_TAG;
+
+ /*
+ * continue with the current character; since we're in
+ * html tag mode, we'll skip everything until we get to
+ * the closing '>'
+ */
+ continue;
+ }
+ else if (c == '&')
+ {
+ /* parse it */
+ char xlat_buf[50];
+ c = out_parse_entity(xlat_buf, sizeof(xlat_buf), &s, &slen);
+
+ /* write it out (we've already translated it) */
+ outstring_noxlat_stream(stream, xlat_buf);
+
+ /* proceed with the next character */
+ continue;
+ }
+ }
+ else if (stream->html_target && stream->html_mode && c == '<')
+ {
+ /*
+ * We're in HTML mode, and we have an underlying HTML target.
+ * We don't need to do much HTML interpretation at this level.
+ * However, we do need to keep track of when we're in a PRE
+ * block, so that we can pass whitespaces and newlines through
+ * to the underlying HTML engine without filtering when we're
+ * in preformatted text.
+ */
+ char tagbuf[50];
+ int is_end_tag;
+ c = read_tag(tagbuf, sizeof(tagbuf), &is_end_tag,
+ &s, &slen, stream);
+
+ /* check for special tags */
+ if (!scumm_stricmp(tagbuf, "pre"))
+ {
+ /* count the nesting level */
+ if (!is_end_tag)
+ stream->html_pre_level += 1;
+ else if (is_end_tag && stream->html_pre_level != 0)
+ stream->html_pre_level -= 1;
+ }
+
+ /* copy the last character after the tag to the stream */
+ outchar_stream(stream, c);
+ }
+ else
+ {
+ /* normal character */
+ outchar_stream(stream, c);
+ }
+
+ /* move on to the next character, unless we're finished */
+ if (done)
+ c = '\0';
+ else
+ c = nextout(&s, &slen);
+ }
+
+ /* if we ended up inside what looked like a format string, dump string */
+ if (infmt)
+ {
+ outchar_stream(stream, '%');
+ for (f1 = fmsbuf ; f1 < f ; ++f1)
+ outchar_stream(stream, *f1);
+ }
+
+ /* exit a recursion level */
+ out_pop_stream();
+
+ /* success */
+ return 0;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Parse an HTML entity markup
+ */
+static char out_parse_entity(char *outbuf, size_t outbuf_size,
+ char **sp, size_t *slenp)
+{
+ char ampbuf[10];
+ char *dst;
+ char *orig_s;
+ size_t orig_slen;
+ const amp_tbl_t *ampptr;
+ size_t lo, hi, cur;
+ char c;
+
+ /*
+ * remember where the part after the '&' begins, so we can come back
+ * here later if necessary
+ */
+ orig_s = *sp;
+ orig_slen = *slenp;
+
+ /* get the character after the ampersand */
+ c = nextout(sp, slenp);
+
+ /* if it's numeric, parse the number */
+ if (c == '#')
+ {
+ uint val;
+
+ /* skip the '#' */
+ c = nextout(sp, slenp);
+
+ /* check for hex */
+ if (c == 'x' || c == 'X')
+ {
+ /* skip the 'x' */
+ c = nextout(sp, slenp);
+
+ /* read the hex number */
+ for (val = 0 ; Common::isXDigit((uchar)c) ; c = nextout(sp, slenp))
+ {
+ /* accumulate the current digit into the value */
+ val *= 16;
+ if (outisdg(c))
+ val += c - '0';
+ else if (c >= 'a' && c <= 'f')
+ val += c - 'a' + 10;
+ else
+ val += c - 'A' + 10;
+ }
+ }
+ else
+ {
+ /* read the number */
+ for (val = 0 ; outisdg(c) ; c = nextout(sp, slenp))
+ {
+ /* accumulate the current digit into the value */
+ val *= 10;
+ val += c - '0';
+ }
+ }
+
+ /* if we found a ';' at the end, skip it */
+ if (c == ';')
+ c = nextout(sp, slenp);
+
+ /* translate the character into the output buffer */
+ os_xlat_html4(val, outbuf, outbuf_size);
+
+ /* we're done with this character */
+ return c;
+ }
+
+ /*
+ * Parse the sequence after the '&'. Parse up to the closing
+ * semicolon, or any non-alphanumeric, or until we fill up the buffer.
+ */
+ for (dst = ampbuf ;
+ c != '\0' && (outisdg(c) || outisal(c))
+ && dst < ampbuf + sizeof(ampbuf) - 1 ;
+ *dst++ = c, c = nextout(sp, slenp)) ;
+
+ /* null-terminate the name */
+ *dst = '\0';
+
+ /* do a binary search for the name */
+ lo = 0;
+ hi = sizeof(amp_tbl)/sizeof(amp_tbl[0]) - 1;
+ for (;;)
+ {
+ int diff;
+
+ /* if we've converged, look no further */
+ if (lo > hi || lo >= sizeof(amp_tbl)/sizeof(amp_tbl[0]))
+ {
+ ampptr = 0;
+ break;
+ }
+
+ /* split the difference */
+ cur = lo + (hi - lo)/2;
+ ampptr = &amp_tbl[cur];
+
+ /* see where we are relative to the target item */
+ diff = strcmp(ampptr->cname, ampbuf);
+ if (diff == 0)
+ {
+ /* this is it */
+ break;
+ }
+ else if (diff > 0)
+ {
+ /* make sure we don't go off the end */
+ if (cur == hi && cur == 0)
+ {
+ /* we've failed to find it */
+ ampptr = 0;
+ break;
+ }
+
+ /* this one is too high - check the lower half */
+ hi = (cur == hi ? hi - 1 : cur);
+ }
+ else
+ {
+ /* this one is too low - check the upper half */
+ lo = (cur == lo ? lo + 1 : cur);
+ }
+ }
+
+ /* skip to the appropriate next character */
+ if (c == ';')
+ {
+ /* name ended with semicolon - skip the semicolon */
+ c = nextout(sp, slenp);
+ }
+ else if (ampptr != 0)
+ {
+ int skipcnt;
+
+ /* found the name - skip its exact length */
+ skipcnt = strlen(ampptr->cname);
+ for (*sp = orig_s, *slenp = orig_slen ; skipcnt != 0 ;
+ c = nextout(sp, slenp), --skipcnt) ;
+ }
+
+ /* if we found the entry, write out the character */
+ if (ampptr != 0)
+ {
+ /*
+ * if this one has an external mapping table entry, use the mapping
+ * table entry; otherwise, use the default OS routine mapping
+ */
+ if (ampptr->expan != 0)
+ {
+ /*
+ * we have an explicit expansion from the mapping table file -
+ * use it
+ */
+ size_t copylen = strlen(ampptr->expan);
+ if (copylen > outbuf_size - 1)
+ copylen = outbuf_size - 1;
+
+ memcpy(outbuf, ampptr->expan, copylen);
+ outbuf[copylen] = '\0';
+ }
+ else
+ {
+ /*
+ * there's no mapping table expansion - use the default OS code
+ * expansion
+ */
+ os_xlat_html4(ampptr->html_cval, outbuf, outbuf_size);
+ }
+ }
+ else
+ {
+ /*
+ * didn't find it - output the '&' literally, then back up and
+ * output the entire sequence following
+ */
+ *sp = orig_s;
+ *slenp = orig_slen;
+ c = nextout(sp, slenp);
+
+ /* fill in the '&' return value */
+ outbuf[0] = '&';
+ outbuf[1] = '\0';
+ }
+
+ /* return the next character */
+ return c;
+}
+
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Initialize the output formatter
+ */
+void out_init()
+{
+ /* not yet hiding output */
+ outflag = 1;
+ outcnt = 0;
+ hidout = 0;
+
+ /* initialize the standard display stream */
+ out_init_std(&G_std_disp);
+
+ /* initialize the log file stream */
+ out_init_log(&G_log_disp);
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * initialize the property translation table
+ */
+void tiosetfmt(tiocxdef *ctx, runcxdef *rctx, uchar *fbase, uint flen)
+{
+ VARUSED(ctx);
+ fmsbase = fbase;
+ fmstop = fbase + flen;
+ runctx = rctx;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Map an HTML entity to a local character value. The character table
+ * reader will call this routine during initialization if it finds HTML
+ * entities in the mapping table file. We'll remember these mappings
+ * for use in translating HTML entities to the local character set.
+ *
+ * Note that the standard run-time can only display a single character
+ * set, so every HTML entity that we display must be mapped to the
+ * single active native character set.
+ */
+void tio_set_html_expansion(unsigned int html_char_val,
+ const char *expansion, size_t expansion_len)
+{
+ amp_tbl_t *p;
+
+ /* find the character value */
+ for (p = amp_tbl ;
+ p < amp_tbl + sizeof(amp_tbl)/sizeof(amp_tbl[0]) ; ++p)
+ {
+ /* if this is the one, store it */
+ if (p->html_cval == html_char_val)
+ {
+ /* allocate space for it */
+ p->expan = (char *)osmalloc(expansion_len + 1);
+
+ /* save it */
+ memcpy(p->expan, expansion, expansion_len);
+ p->expan[expansion_len] = '\0';
+
+ /* no need to look any further */
+ return;
+ }
+ }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Write out a c-style (null-terminated) string.
+ */
+int outformat(char *s)
+{
+ return outformatlen(s, strlen(s));
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * This routine sends out a string, one character at a time (via outchar).
+ * Escape codes ('\n', and so forth) are handled here.
+ */
+int outformatlen(char *s, uint slen)
+{
+ char c;
+ uint orig_slen;
+ char *orig_s;
+ int ret;
+ int called_filter;
+
+ /* presume we'll return success */
+ ret = 0;
+
+ /* presume we won't call the filter function */
+ called_filter = FALSE;
+
+ /* if there's a user filter function to invoke, call it */
+ if (G_user_filter != MCMONINV)
+ {
+ /* push the string */
+ runpstr(runctx, s, slen, 1);
+
+ /* call the filter */
+ runfn(runctx, G_user_filter, 1);
+
+ /*
+ * note that we called the filter, so that we'll remove the
+ * result of the filter from the stack before we return
+ */
+ called_filter = TRUE;
+
+ /* if the result is a string, use it in place of the original text */
+ if (runtostyp(runctx) == DAT_SSTRING)
+ {
+ runsdef val;
+ uchar *p;
+
+ /* pop the value */
+ runpop(runctx, &val);
+
+ /*
+ * get the text from the string, and use it as a replacement
+ * for the original string
+ */
+ p = val.runsv.runsvstr;
+ slen = osrp2(p) - 2;
+ s = (char *)(p + 2);
+
+ /*
+ * push the string back onto the stack - this will ensure
+ * that the string stays referenced while we're working, so
+ * that the garbage collector won't delete it
+ */
+ runrepush(runctx, &val);
+ }
+ }
+
+ /* remember the original string, before we scan the first character */
+ orig_s = s;
+ orig_slen = slen;
+
+ /* get the first character to display */
+ c = nextout(&s, &slen);
+
+ /* if the string is non-empty, note that we've displayed something */
+ if (c != 0)
+ outcnt = 1;
+
+ /* check to see if we're hiding output */
+ if (out_is_hidden())
+ goto done;
+
+ /* if the debugger is showing watchpoints, suppress all output */
+ if (outwxflag)
+ goto done;
+
+ /* display the string */
+ ret = outformatlen_stream(&G_std_disp, orig_s, orig_slen);
+
+ /* if there's a log file, write to the log file as well */
+ if (logfp != 0)
+ {
+ outformatlen_stream(&G_log_disp, orig_s, orig_slen);
+ osfflush(logfp);
+ }
+
+done:
+ /* if we called the filter, remove the result from the stack */
+ if (called_filter)
+ rundisc(runctx);
+
+ /* return the result from displaying to the screen */
+ return ret;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Display a blank line
+ */
+void outblank()
+{
+ /* note that we've displayed something */
+ outcnt = 1;
+
+ /* check to see if we're hiding output */
+ if (out_is_hidden())
+ return;
+
+ /* generate the newline to the standard display */
+ outblank_stream(&G_std_disp);
+
+ /* if we're logging, generate the newline to the log file as well */
+ if (logfp != 0)
+ {
+ outblank_stream(&G_log_disp);
+ osfflush(logfp);
+ }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * outcaps() - sets an internal flag which makes the next letter output
+ * a capital, whether it came in that way or not. Set the same state in
+ * both formatters (standard and log).
+ */
+void outcaps(void)
+{
+ outcaps_stream(&G_std_disp);
+ outcaps_stream(&G_log_disp);
+}
+
+/*
+ * outnocaps() - sets the next letter to a miniscule, whether it came in
+ * that way or not.
+ */
+void outnocaps(void)
+{
+ outnocaps_stream(&G_std_disp);
+ outnocaps_stream(&G_log_disp);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Open a log file
+ */
+int tiologopn(tiocxdef *ctx, char *fn)
+{
+ /* if there's an old log file, close it */
+ if (tiologcls(ctx))
+ return 1;
+
+ /* save the filename for later */
+ strcpy(logfname, fn);
+
+ /* open the new file */
+ logfp = osfopwt(fn, OSFTLOG);
+
+ /*
+ * Reset the log file's output formatter state, since we're opening
+ * a new file.
+ */
+ out_init_log(&G_log_disp);
+
+ /*
+ * Set the log file's HTML source mode flag to the same value as is
+ * currently being used in the main display stream, so that it will
+ * interpret source markups the same way that the display stream is
+ * going to.
+ */
+ G_log_disp.html_mode = G_std_disp.html_mode;
+
+ /* return 0 on success, non-zero on failure */
+ return (logfp == 0);
+}
+
+/*
+ * Close the log file
+ */
+int tiologcls(tiocxdef *ctx)
+{
+ /* if we have a file, close it */
+ if (logfp != 0)
+ {
+ /* close the handle */
+ osfcls(logfp);
+
+ /* set the system file type to "log file" */
+ os_settype(logfname, OSFTLOG);
+
+ /* forget about our log file handle */
+ logfp = 0;
+ }
+
+ /* success */
+ return 0;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Write text explicitly to the log file. This can be used to add
+ * special text (such as prompt text) that would normally be suppressed
+ * from the log file. When more mode is turned off, we don't
+ * automatically copy text to the log file; any text that the caller
+ * knows should be in the log file during times when more mode is turned
+ * off can be explicitly added with this function.
+ *
+ * If nl is true, we'll add a newline at the end of this text. The
+ * caller should not include any newlines in the text being displayed
+ * here.
+ */
+void out_logfile_print(char *txt, int nl)
+{
+ /* if there's no log file, there's nothing to do */
+ if (logfp == 0)
+ return;
+
+ /* add the text */
+ os_fprintz(logfp, txt);
+
+ /* add a newline if desired */
+ if (nl)
+ {
+ /* add a normal newline */
+ os_fprintz(logfp, "\n");
+
+ /* if the logfile is an html target, write an HTML line break */
+ if (G_log_disp.html_target && G_log_disp.html_mode)
+ os_fprintz(logfp, "<BR HEIGHT=0>\n");
+ }
+
+ /* flush the output */
+ osfflush(logfp);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Set the current MORE mode
+ */
+int setmore(int state)
+{
+ int oldstate = G_os_moremode;
+
+ G_os_moremode = state;
+ return oldstate;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Run the MORE prompt. If the output layer takes responsibility for
+ * pagination issues (i.e., USE_MORE is defined), we'll simply display
+ * the prompt and wait for input. Otherwise, the OS layer controls the
+ * MORE prompt, so we'll call the OS-layer function to display the
+ * prompt.
+ */
+void out_more_prompt()
+{
+#ifdef USE_MORE
+ /*
+ * USE_MORE defined - we take responsibility for pagination. Show
+ * our default MORE prompt and wait for a keystroke.
+ */
+
+ int done;
+ int next_page = FALSE;
+
+ /* display the "MORE" prompt */
+ os_printz("[More]");
+ os_flush();
+
+ /* wait for an acceptable keystroke */
+ for (done = FALSE ; !done ; )
+ {
+ os_event_info_t evt;
+
+ /* get an event */
+ switch(os_get_event(0, FALSE, &evt))
+ {
+ case OS_EVT_KEY:
+ switch(evt.key[0])
+ {
+ case ' ':
+ /* stop waiting, show one page */
+ done = TRUE;
+ next_page = TRUE;
+ break;
+
+ case '\r':
+ case '\n':
+ /* stop waiting, show one line */
+ done = TRUE;
+ next_page = FALSE;
+ break;
+
+ default:
+ /* ignore any other keystrokes */
+ break;
+ }
+ break;
+
+ case OS_EVT_EOF:
+ /* end of file - there's nothing to wait for now */
+ done = TRUE;
+ next_page = TRUE;
+
+ /* don't use more prompts any more, as the user can't respond */
+ G_os_moremode = FALSE;
+ break;
+
+ default:
+ /* ignore other events */
+ break;
+ }
+ }
+
+ /*
+ * Remove the prompt from the screen by backing up and overwriting
+ * it with spaces. (Note that this assumes that we're running in
+ * some kind of terminal or character mode with a fixed-pitch font;
+ * if that's not the case, the OS layer should be taking
+ * responsibility for pagination anyway, so this code shouldn't be
+ * in use in the first place.)
+ */
+ os_printz("\r \r");
+
+ /*
+ * if they pressed the space key, it means that we should show an
+ * entire new page, so reset the line count to zero; otherwise,
+ * we'll want to display another MORE prompt at the very next line,
+ * so leave the line count alone
+ */
+ if (next_page)
+ G_std_disp.linecnt = 0;
+
+#else /* USE_MORE */
+
+ /*
+ * USE_MORE is undefined - this means that the OS layer is taking
+ * all responsibility for pagination. We must ask the OS layer to
+ * display the MORE prompt, because we can't make any assumptions
+ * about what the prompt looks like.
+ */
+
+ os_more_prompt();
+
+#endif /* USE_MORE */
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * reset output
+ */
+void outreset(void)
+{
+ G_std_disp.linecnt = 0;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Determine if HTML mode is active. Returns true if so, false if not.
+ * Note that this merely indicates whether an "\H+" sequence is
+ * currently active -- this will return true after an "\H+" sequence,
+ * even on text-only interpreters.
+ */
+int tio_is_html_mode()
+{
+ /* return the current HTML mode flag for the standard display stream */
+ return G_std_disp.html_mode;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Capture routines. Capture affects only the standard display output
+ * stream; there's no need to capture information redundantly in the log
+ * file stream.
+ */
+
+/*
+ * Begin/end capturing
+ */
+void tiocapture(tiocxdef *tioctx, mcmcxdef *memctx, int flag)
+{
+ if (flag)
+ {
+ /* create a new object if necessary */
+ if (G_std_disp.capture_obj == MCMONINV)
+ {
+ mcmalo(memctx, 256, &G_std_disp.capture_obj);
+ mcmunlck(memctx, G_std_disp.capture_obj);
+ }
+
+ /* remember the memory context */
+ G_std_disp.capture_ctx = memctx;
+ }
+
+ /*
+ * remember capture status in the standard output stream as well as
+ * the log stream
+ */
+ G_std_disp.capturing = flag;
+ G_log_disp.capturing = flag;
+}
+
+/* clear all captured output */
+void tioclrcapture(tiocxdef *tioctx)
+{
+ G_std_disp.capture_ofs = 0;
+}
+
+/* clear captured output back to a given size */
+void tiopopcapture(tiocxdef *tioctx, uint orig_size)
+{
+ G_std_disp.capture_ofs = orig_size;
+}
+
+/* get the object handle of the captured output */
+mcmon tiogetcapture(tiocxdef *ctx)
+{
+ return G_std_disp.capture_obj;
+}
+
+/* get the amount of text captured */
+uint tiocapturesize(tiocxdef *ctx)
+{
+ return G_std_disp.capture_ofs;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * set the current actor
+ */
+void tiosetactor(tiocxdef *ctx, objnum actor)
+{
+ VARUSED(ctx);
+ cmdActor = actor;
+}
+
+/*
+ * get the current actor
+ */
+objnum tiogetactor(tiocxdef *ctx)
+{
+ VARUSED(ctx);
+ return cmdActor;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Flush the output line. We'll write to both the standard display and
+ * the log file, as needed.
+ */
+void outflushn(int nl)
+{
+ /* flush the display stream */
+ outflushn_stream(&G_std_disp, nl);
+
+ /* flush the log stream, if we have an open log file */
+ if (logfp != 0)
+ {
+ outflushn_stream(&G_log_disp, nl);
+ osfflush(logfp);
+ }
+}
+
+/*
+ * flush the current line, and start a new line
+ */
+void outflush(void)
+{
+ /* use the common flushing routine in mode 1 (regular newline) */
+ outflushn(1);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Hidden text routines
+ */
+
+/*
+ * outhide - hide output in the standard display stream
+ */
+void outhide(void)
+{
+ outflag = 0;
+ outcnt = 0;
+ hidout = 0;
+}
+
+/*
+ * Check output status. Indicate whether output is currently hidden,
+ * and whether any hidden output has occurred.
+ */
+void outstat(int *hidden, int *output_occurred)
+{
+ *hidden = !outflag;
+ *output_occurred = outcnt;
+}
+
+/* set the flag to indicate that output has occurred */
+void outsethidden(void)
+{
+ outcnt = 1;
+ hidout = 1;
+}
+
+/*
+ * outshow() - turns output back on, and returns TRUE (1) if any output
+ * has occurred since the last outshow(), FALSE (0) otherwise.
+ */
+int outshow(void)
+{
+ /* turn output back on */
+ outflag = 1;
+
+ /* if we're debugging, note the end of hidden output */
+ if (dbghid && hidout)
+ {
+ hidout = 0;
+ trcsho();
+ }
+
+ /* return the flag indicating whether hidden output occurred */
+ return outcnt;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * start/end watchpoint evaluation - suppress all dstring output
+ */
+void outwx(int flag)
+{
+ outwxflag = flag;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Set the user filter function. Setting this to MCMONINV clears the
+ * filter.
+ */
+void out_set_filter(objnum filter_fn)
+{
+ /* remember the filter function */
+ G_user_filter = filter_fn;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ * Set the double-space mode
+ */
+void out_set_doublespace(int dbl)
+{
+ /* remember the new setting */
+ doublespace = dbl;
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk