/* 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, const char *s);
static void outchar_noxlat_stream(out_stream_info *stream, char c);
static char out_parse_entity(char *outbuf, size_t outbuf_size, const 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
tags. We defer these until we've
* read the full tag in order to obey an HEIGHT attribute we find. When
* we encounter a
, 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
*/
#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
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;
/*
defer mode */
unsigned int html_defer_br;
/*
* HTML "ignore" mode - we suppress all output when parsing the
* contents of a
or tag
*/
int html_in_ignore;
/*
* HTML mode - when we're in this mode, we're gathering the
* title (i.e., we're inside a 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
* 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
{
const 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, "
", 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, "
");
/* 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
* ; if we're not in HTML mode, we'll generate a hard
* tab character, which the HTML formatter will interpret as a .
*/
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 tag */
outstring_stream(stream, "");
}
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, const 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(const 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,
const 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,
const 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
, process it now */
switch(stream->html_defer_br)
{
case HTML_DEFER_BR_NONE:
/* no deferred
*/
break;
case HTML_DEFER_BR_FLUSH:
outflushn_stream(stream, 1);
break;
case HTML_DEFER_BR_BLANK:
outblank_stream(stream);
break;
}
/* no more deferred
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 ... 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, " ");
}
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, "");
}
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, "");
}
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 = 0;
/* 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, const char **sp, size_t *slenp) {
char ampbuf[10];
char *dst;
const 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 = &_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(const 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(const char *s, uint slen) {
char c;
uint orig_slen;
const 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(const 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, "
\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;
}
int tio_askfile(const char *prompt, char *reply, int replen, int prompt_type, os_filetype_t file_type) {
// let the OS layer handle it
return os_askfile(prompt, reply, replen, prompt_type, file_type);
}
int 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) {
// call the OS implementation
return os_input_dialog(icon_id, prompt, standard_button_set,
buttons, button_count,
default_index, cancel_index);
}
void outfmt(tiocxdef *ctx, uchar *txt) {
uint len;
VARUSED(ctx);
/* read the length prefix */
len = osrp2(txt) - 2;
txt += 2;
/* write out the string */
tioputslen(ctx, (char *)txt, len);
}
} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk