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