cfad47cfa3/tads2/output.c

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
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, "&nbsp;");
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 = &amp_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