1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "glk/agt/agility.h"
24 #include "glk/agt/interp.h"
25 #include "glk/agt/agt.h"
26 
27 namespace Glk {
28 namespace AGT {
29 
30 /*
31  * Glk interface for AGiliTy 1.1.1.1
32  * -------------------------------
33  *
34  * This module contains the the Glk porting layer for AGiliTy.  It
35  * defines the Glk arguments list structure, the entry points for the
36  * Glk library framework to use, and all platform-abstracted I/O to
37  * link to Glk's I/O.
38  *
39  * The following items are omitted from this Glk port:
40  *
41  *  o Calls to g_vm->glk_tick().  The Glk documentation states that the
42  *    interpreter should call g_vm->glk_tick() every opcode or so.  This is
43  *    intrusive to code (it goes outside of this module), and since
44  *    most Glk libraries do precisely nothing in g_vm->glk_tick(), there is
45  *    little motivation to add it.
46  *
47  *  o Glk tries to assert control over _all_ file I/O.  It's just too
48  *    disruptive to add it to existing code, so for now, the AGiliTy
49  *    interpreter is still dependent on stdio and the like.
50  */
51 
52 /*
53  * True and false definitions -- usually defined in glkstart.h, but we need
54  * them early, so we'll define them here too.  We also need NULL, but that's
55  * normally from stdio.h or one of it's cousins.
56  */
57 #ifndef FALSE
58 # define FALSE 0
59 #endif
60 #ifndef TRUE
61 # define TRUE (!FALSE)
62 #endif
63 
64 
65 /*---------------------------------------------------------------------*/
66 /*  Module variables, miscellaneous externals not in header files      */
67 /*---------------------------------------------------------------------*/
68 
69 /* Glk AGiliTy port version number. */
70 static const glui32 GAGT_PORT_VERSION = 0x00010701;
71 
72 /* Forward declaration of event wait functions. */
73 static void gagt_event_wait(glui32 wait_type, event_t *event);
74 static void gagt_event_wait_2(glui32 wait_type_1,
75 							  glui32 wait_type_2,
76 							  event_t *event);
77 
78 /*
79  * Forward declaration of the g_vm->glk_exit() wrapper.  Normal functions in this
80  * module should not to call g_vm->glk_exit() directly; they should always call it
81  * through the wrapper instead.
82  */
83 static void gagt_exit();
84 
85 
86 /*---------------------------------------------------------------------*/
87 /*  Glk port utility functions                                         */
88 /*---------------------------------------------------------------------*/
89 
90 /*
91  * gagt_fatal()
92  *
93  * Fatal error handler.  The function returns, expecting the caller to
94  * abort() or otherwise handle the error.
95  */
gagt_fatal(const char * string)96 static void gagt_fatal(const char *string) {
97 	/*
98 	 * If the failure happens too early for us to have a window, print
99 	 * the message to stderr.
100 	 */
101 	if (!g_vm->gagt_main_window)
102 		error("INTERNAL ERROR: %s", string);
103 
104 	/* Cancel all possible pending window input events. */
105 	g_vm->glk_cancel_line_event(g_vm->gagt_main_window, NULL);
106 	g_vm->glk_cancel_char_event(g_vm->gagt_main_window);
107 
108 	/* Print a message indicating the error. */
109 	g_vm->glk_set_window(g_vm->gagt_main_window);
110 	g_vm->glk_set_style(style_Normal);
111 	g_vm->glk_put_string("\n\nINTERNAL ERROR: ");
112 	g_vm->glk_put_string(string);
113 
114 	g_vm->glk_put_string("\n\nPlease record the details of this error, try to"
115 	                     " note down everything you did to cause it, and email"
116 	                     " this information to simon_baldwin@yahoo.com.\n\n");
117 }
118 
119 
120 /*
121  * gagt_malloc()
122  * gagt_realloc()
123  *
124  * Non-failing malloc and realloc; call gagt_fatal() and exit if memory
125  * allocation fails.
126  */
gagt_malloc(size_t size)127 static void *gagt_malloc(size_t size) {
128 	void *pointer;
129 
130 	pointer = malloc(size);
131 	if (!pointer) {
132 		gagt_fatal("GLK: Out of system memory");
133 		gagt_exit();
134 	}
135 
136 	return pointer;
137 }
138 
gagt_realloc(void * ptr,size_t size)139 static void *gagt_realloc(void *ptr, size_t size) {
140 	void *pointer;
141 
142 	pointer = realloc(ptr, size);
143 	if (!pointer) {
144 		gagt_fatal("GLK: Out of system memory");
145 		gagt_exit();
146 	}
147 
148 	return pointer;
149 }
150 
151 
152 /*
153  * gagt_strncasecmp()
154  * gagt_strcasecmp()
155  *
156  * Strncasecmp and strcasecmp are not ANSI functions, so here are local
157  * definitions to do the same jobs.
158  */
gagt_strncasecmp(const char * s1,const char * s2,size_t n)159 static int gagt_strncasecmp(const char *s1, const char *s2, size_t n) {
160 	size_t index;
161 
162 	for (index = 0; index < n; index++) {
163 		int diff;
164 
165 		diff = g_vm->glk_char_to_lower(s1[index]) - g_vm->glk_char_to_lower(s2[index]);
166 		if (diff < 0 || diff > 0)
167 			return diff < 0 ? -1 : 1;
168 	}
169 
170 	return 0;
171 }
172 
gagt_strcasecmp(const char * s1,const char * s2)173 static int gagt_strcasecmp(const char *s1, const char *s2) {
174 	size_t s1len, s2len;
175 	int result;
176 
177 	s1len = strlen(s1);
178 	s2len = strlen(s2);
179 
180 	result = gagt_strncasecmp(s1, s2, s1len < s2len ? s1len : s2len);
181 	if (result < 0 || result > 0)
182 		return result;
183 	else
184 		return s1len < s2len ? -1 : s1len > s2len ? 1 : 0;
185 }
186 
187 
188 /*
189  * gagt_debug()
190  *
191  * Handler for module debug output.  If no debug, it ignores the call,
192  * otherwise it prints a debug message, prefixed by the function name.
193  */
gagt_debug(const char * function,const char * format,...)194 static void gagt_debug(const char *function, const char *format, ...) {
195 	if (DEBUG_OUT) {
196 		Common::WriteStream *ws = dynamic_cast<Common::WriteStream *>(debugfile);
197 		assert(ws);
198 
199 		ws->writeString(Common::String::format("%s (", function));
200 		if (format && strlen(format) > 0) {
201 			va_list va;
202 
203 			va_start(va, format);
204 			Common::String data = Common::String::vformat(format, va);
205 			ws->writeString(data);
206 			va_end(va);
207 		}
208 
209 		ws->writeString(")\n");
210 	}
211 }
212 
213 
214 /*---------------------------------------------------------------------*/
215 /*  Functions not ported - functionally unchanged from os_none.c       */
216 /*---------------------------------------------------------------------*/
217 
218 /*
219  * agt_tone()
220  *
221  * Produce a hz-Hertz sound for ms milliseconds.
222  */
agt_tone(int hz,int ms)223 void agt_tone(int hz, int ms) {
224 	gagt_debug("agt_tone", "hz=%d, ms=%d", hz, ms);
225 }
226 
227 
228 /*
229  * agt_rand()
230  *
231  * Return random number from a to b inclusive.  The random number generator
232  * is seeded on the first call, to a reproducible sequence if stable_random,
233  * otherwise using time().
234  */
agt_rand(int a,int b)235 int agt_rand(int a, int b) {
236 	int result;
237 
238 	result = a + g_vm->getRandomNumber(0x7fffffff) % (b - a + 1);
239 	gagt_debug("agt_rand", "a=%d, b=%d -> %d", a, b, result);
240 	return result;
241 }
242 
243 
244 /*---------------------------------------------------------------------*/
245 /*  Workrounds for bugs in core AGiliTy.                               */
246 /*---------------------------------------------------------------------*/
247 
248 /*
249  * gagt_workround_menus()
250  *
251  * Somewhere in AGiliTy's menu handling stuff is a condition that sets up
252  * an eventual NULL dereference in rstrncpy(), called from num_name_func().
253  * For some reason, perhaps memory overruns, perhaps something else, it
254  * happens after a few turns have been made through agt_menu().  Replacing
255  * agt_menu() won't avoid it.
256  *
257  * However, the menu stuff isn't too useful, or attractive, in a game, so one
258  * solution is to simply disable it.  While not possible to do this directly,
259  * there is a sneaky way, using our carnal knowledge of core AGiliTy.  In
260  * runverb.c, there is code to prevent menu mode from being turned on where
261  * verbmenu is NULL.  Verbmenu is set up in agil.c on loading the game, but,
262  * crucially, is set up before agil.c calls start_interface().  So... here
263  * we can free it, set it to NULL, set menu_mode to 0 (it probably is already)
264  * and AGiliTy behaves as if the game prevents menu mode.
265  */
gagt_workround_menus()266 static void gagt_workround_menus() {
267 	free(verbmenu);
268 	verbmenu = NULL;
269 
270 	menu_mode = 0;
271 }
272 
273 
274 /*
275  * gagt_workround_fileexist()
276  *
277  * This function verifies that the game file can be opened, in effect second-
278  * guessing run_game().
279  *
280  * AGiliTy's fileexist() has in it either a bug, or a misfeature.  It always
281  * passes a nofix value of 1 into try_open_file(), which defeats the code to
282  * retry with both upper and lower cased filenames.  So here we have to go
283  * round the houses, with readopen()/readclose().
284  */
gagt_workround_fileexist(fc_type fc,filetype ft)285 static int gagt_workround_fileexist(fc_type fc, filetype ft) {
286 	genfile file;
287 	const char *errstr;
288 
289 	errstr = NULL;
290 	file = readopen(fc, ft, &errstr);
291 
292 	if (file) {
293 		readclose(file);
294 		return TRUE;
295 	}
296 	return FALSE;
297 }
298 
299 
300 /*---------------------------------------------------------------------*/
301 /*  I/O interface start and stop functions.                            */
302 /*---------------------------------------------------------------------*/
303 
304 /* AGiliTy font_status values that indicate what font may be used. */
305 enum {
306 	GAGT_FIXED_REQUIRED = 1, GAGT_PROPORTIONAL_OKAY = 2
307 };
308 
309 
310 /*
311  * start_interface()
312  * close_interface()
313  *
314  * Startup and shutdown callout points.  The start function for Glk looks
315  * at the value of font_status that the game sets, to see if it has a strong
316  * view of the font to use.  If it does, then we'll reflect that in the
317  * module's font contol, perhaps overriding any command line options that the
318  * user has passed in.
319  */
start_interface(fc_type fc)320 void start_interface(fc_type fc) {
321 	switch (font_status) {
322 	case GAGT_FIXED_REQUIRED:
323 		g_vm->gagt_font_mode = FONT_FIXED_WIDTH;
324 		break;
325 
326 	case GAGT_PROPORTIONAL_OKAY:
327 		g_vm->gagt_font_mode = FONT_PROPORTIONAL;
328 		break;
329 
330 	default:
331 		break;
332 	}
333 
334 	gagt_workround_menus();
335 
336 	gagt_debug("start_interface", "fc=%p", fc);
337 }
338 
close_interface()339 void close_interface() {
340 	if (filevalid(scriptfile, fSCR))
341 		close_pfile(scriptfile, 0);
342 
343 	gagt_debug("close_interface", "");
344 }
345 
346 
347 /*---------------------------------------------------------------------*/
348 /*  Code page 437 to ISO 8859 Latin-1 translations                     */
349 /*---------------------------------------------------------------------*/
350 
351 /*
352  * AGiliTy uses IBM code page 437 characters, and Glk works in ISO 8859
353  * Latin-1.  There's some good news, in that a number of the characters,
354  * especially international ones, in these two sets are the same.  The bad
355  * news is that, for codes above 127 (that is, beyond 7-bit ASCII), or for
356  * codes below 32, they live in different places.  So, here is a table of
357  * conversions for codes not equivalent to 7-bit ASCII, and a pair of
358  * conversion routines.
359  *
360  * Note that some code page 437 characters don't have ISO 8859 Latin-1
361  * equivalents.  Predominantly, these are the box-drawing characters, which
362  * is a pity, because these are the ones that are used the most.  Anyway,
363  * in these cases, the table substitutes an approximated base ASCII char-
364  * acter in its place.
365  *
366  * The first entry of table comments below is the character's UNICODE value,
367  * just in case it's useful at some future date.
368  */
369 typedef const struct {
370 	const unsigned char cp437;      /* Code page 437 character. */
371 	const unsigned char iso8859_1;  /* ISO 8859 Latin-1 character. */
372 } gagt_char_t;
373 typedef gagt_char_t *gagt_charref_t;
374 
375 static gagt_char_t GAGT_CHAR_TABLE[] = {
376 	/*
377 	 * Low characters -- those below 0x20.   These are the really odd code
378 	 * page 437 characters, rarely used by AGT games.  Low characters are
379 	 * omitted from the reverse lookup, and participate only in the forwards
380 	 * lookup from code page 437 to ISO 8859 Latin-1.
381 	 */
382 	{0x01,  '@'},     /* 263a White smiling face */
383 	{0x02,  '@'},     /* 263b Black smiling face */
384 	{0x03,  '?'},     /* 2665 Black heart suit */
385 	{0x04,  '?'},     /* 2666 Black diamond suit */
386 	{0x05,  '?'},     /* 2663 Black club suit */
387 	{0x06,  '?'},     /* 2660 Black spade suit */
388 	{0x07, 0xb7},     /* 2022 Bullet */
389 	{0x08, 0xb7},     /* 25d8 Inverse bullet */
390 	{0x09, 0xb7},     /* 25e6 White bullet */
391 	{0x0a, 0xb7},     /* 25d9 Inverse white circle */
392 	{0x0b,  '?'},     /* 2642 Male sign */
393 	{0x0c,  '?'},     /* 2640 Female sign */
394 	{0x0d,  '?'},     /* 266a Eighth note */
395 	{0x0e,  '?'},     /* 266b Beamed eighth notes */
396 	{0x0f, 0xa4},     /* 263c White sun with rays */
397 	{0x10,  '>'},     /* 25b6 Black right-pointing triangle */
398 	{0x11,  '<'},     /* 25c0 Black left-pointing triangle */
399 	{0x12, 0xa6},     /* 2195 Up down arrow */
400 	{0x13,  '!'},     /* 203c Double exclamation mark */
401 	{0x14, 0xb6},     /* 00b6 Pilcrow sign */
402 	{0x15, 0xa7},     /* 00a7 Section sign */
403 	{0x16,  '#'},     /* 25ac Black rectangle */
404 	{0x17, 0xa6},     /* 21a8 Up down arrow with base */
405 	{0x18,  '^'},     /* 2191 Upwards arrow */
406 	{0x19,  'v'},     /* 2193 Downwards arrow */
407 	{0x1a,  '>'},     /* 2192 Rightwards arrow */
408 	{0x1b,  '<'},     /* 2190 Leftwards arrow */
409 	{0x1c,  '?'},     /* 2310 Reversed not sign */
410 	{0x1d,  '-'},     /* 2194 Left right arrow */
411 	{0x1e,  '^'},     /* 25b2 Black up-pointing triangle */
412 	{0x1f,  'v'},     /* 25bc Black down-pointing triangle */
413 
414 	/*
415 	 * High characters -- those above 0x7f.  These are more often used by AGT
416 	 * games, particularly for box drawing.
417 	 */
418 	{0x80, 0xc7},     /* 00c7 Latin capital letter c with cedilla */
419 	{0x81, 0xfc},     /* 00fc Latin small letter u with diaeresis */
420 	{0x82, 0xe9},     /* 00e9 Latin small letter e with acute */
421 	{0x83, 0xe2},     /* 00e2 Latin small letter a with circumflex */
422 	{0x84, 0xe4},     /* 00e4 Latin small letter a with diaeresis */
423 	{0x85, 0xe0},     /* 00e0 Latin small letter a with grave */
424 	{0x86, 0xe5},     /* 00e5 Latin small letter a with ring above */
425 	{0x87, 0xe7},     /* 00e7 Latin small letter c with cedilla */
426 	{0x88, 0xea},     /* 00ea Latin small letter e with circumflex */
427 	{0x89, 0xeb},     /* 00eb Latin small letter e with diaeresis */
428 	{0x8a, 0xe8},     /* 00e8 Latin small letter e with grave */
429 	{0x8b, 0xef},     /* 00ef Latin small letter i with diaeresis */
430 	{0x8c, 0xee},     /* 00ee Latin small letter i with circumflex */
431 	{0x8d, 0xec},     /* 00ec Latin small letter i with grave */
432 	{0x8e, 0xc4},     /* 00c4 Latin capital letter a with diaeresis */
433 	{0x8f, 0xc5},     /* 00c5 Latin capital letter a with ring above */
434 	{0x90, 0xc9},     /* 00c9 Latin capital letter e with acute */
435 	{0x91, 0xe6},     /* 00e6 Latin small ligature ae */
436 	{0x92, 0xc6},     /* 00c6 Latin capital ligature ae */
437 	{0x93, 0xf4},     /* 00f4 Latin small letter o with circumflex */
438 	{0x94, 0xf6},     /* 00f6 Latin small letter o with diaeresis */
439 	{0x95, 0xf2},     /* 00f2 Latin small letter o with grave */
440 	{0x96, 0xfb},     /* 00fb Latin small letter u with circumflex */
441 	{0x97, 0xf9},     /* 00f9 Latin small letter u with grave */
442 	{0x98, 0xff},     /* 00ff Latin small letter y with diaeresis */
443 	{0x99, 0xd6},     /* 00d6 Latin capital letter o with diaeresis */
444 	{0x9a, 0xdc},     /* 00dc Latin capital letter u with diaeresis */
445 	{0x9b, 0xa2},     /* 00a2 Cent sign */
446 	{0x9c, 0xa3},     /* 00a3 Pound sign */
447 	{0x9d, 0xa5},     /* 00a5 Yen sign */
448 	{0x9e,  'p'},     /* 20a7 Peseta sign */
449 	{0x9f,  'f'},     /* 0192 Latin small letter f with hook */
450 	{0xa0, 0xe1},     /* 00e1 Latin small letter a with acute */
451 	{0xa1, 0xed},     /* 00ed Latin small letter i with acute */
452 	{0xa2, 0xf3},     /* 00f3 Latin small letter o with acute */
453 	{0xa3, 0xfa},     /* 00fa Latin small letter u with acute */
454 	{0xa4, 0xf1},     /* 00f1 Latin small letter n with tilde */
455 	{0xa5, 0xd1},     /* 00d1 Latin capital letter n with tilde */
456 	{0xa6, 0xaa},     /* 00aa Feminine ordinal indicator */
457 	{0xa7, 0xba},     /* 00ba Masculine ordinal indicator */
458 	{0xa8, 0xbf},     /* 00bf Inverted question mark */
459 	{0xa9,  '.'},     /* 2310 Reversed not sign */
460 	{0xaa, 0xac},     /* 00ac Not sign */
461 	{0xab, 0xbd},     /* 00bd Vulgar fraction one half */
462 	{0xac, 0xbc},     /* 00bc Vulgar fraction one quarter */
463 	{0xad, 0xa1},     /* 00a1 Inverted exclamation mark */
464 	{0xae, 0xab},     /* 00ab Left-pointing double angle quotation mark */
465 	{0xaf, 0xbb},     /* 00bb Right-pointing double angle quotation mark */
466 	{0xb0,  '#'},     /* 2591 Light shade */
467 	{0xb1,  '#'},     /* 2592 Medium shade */
468 	{0xb2,  '#'},     /* 2593 Dark shade */
469 	{0xb3,  '|'},     /* 2502 Box light vertical */
470 	{0xb4,  '+'},     /* 2524 Box light vertical and left */
471 	{0xb5,  '+'},     /* 2561 Box vertical single and left double */
472 	{0xb6,  '|'},     /* 2562 Box vertical double and left single */
473 	{0xb7,  '+'},     /* 2556 Box down double and left single */
474 	{0xb8,  '+'},     /* 2555 Box down single and left double */
475 	{0xb9,  '+'},     /* 2563 Box double vertical and left */
476 	{0xba,  '|'},     /* 2551 Box double vertical */
477 	{0xbb, '\\'},     /* 2557 Box double down and left */
478 	{0xbc,  '/'},     /* 255d Box double up and left */
479 	{0xbd,  '+'},     /* 255c Box up double and left single */
480 	{0xbe,  '+'},     /* 255b Box up single and left double */
481 	{0xbf, '\\'},     /* 2510 Box light down and left */
482 	{0xc0, '\\'},     /* 2514 Box light up and right */
483 	{0xc1,  '+'},     /* 2534 Box light up and horizontal */
484 	{0xc2,  '+'},     /* 252c Box light down and horizontal */
485 	{0xc3,  '+'},     /* 251c Box light vertical and right */
486 	{0xc4,  '-'},     /* 2500 Box light horizontal */
487 	{0xc5,  '+'},     /* 253c Box light vertical and horizontal */
488 	{0xc6,  '|'},     /* 255e Box vertical single and right double */
489 	{0xc7,  '|'},     /* 255f Box vertical double and right single */
490 	{0xc8, '\\'},     /* 255a Box double up and right */
491 	{0xc9,  '/'},     /* 2554 Box double down and right */
492 	{0xca,  '+'},     /* 2569 Box double up and horizontal */
493 	{0xcb,  '+'},     /* 2566 Box double down and horizontal */
494 	{0xcc,  '+'},     /* 2560 Box double vertical and right */
495 	{0xcd,  '='},     /* 2550 Box double horizontal */
496 	{0xce,  '+'},     /* 256c Box double vertical and horizontal */
497 	{0xcf,  '='},     /* 2567 Box up single and horizontal double */
498 	{0xd0,  '+'},     /* 2568 Box up double and horizontal single */
499 	{0xd1,  '='},     /* 2564 Box down single and horizontal double */
500 	{0xd2,  '+'},     /* 2565 Box down double and horizontal single */
501 	{0xd3,  '+'},     /* 2559 Box up double and right single */
502 	{0xd4,  '+'},     /* 2558 Box up single and right double */
503 	{0xd5,  '+'},     /* 2552 Box down single and right double */
504 	{0xd6,  '+'},     /* 2553 Box down double and right single */
505 	{0xd7,  '+'},     /* 256b Box vertical double and horizontal single */
506 	{0xd8,  '+'},     /* 256a Box vertical single and horizontal double */
507 	{0xd9,  '/'},     /* 2518 Box light up and left */
508 	{0xda,  '/'},     /* 250c Box light down and right */
509 	{0xdb,  '@'},     /* 2588 Full block */
510 	{0xdc,  '@'},     /* 2584 Lower half block */
511 	{0xdd,  '@'},     /* 258c Left half block */
512 	{0xde,  '@'},     /* 2590 Right half block */
513 	{0xdf,  '@'},     /* 2580 Upper half block */
514 	{0xe0,  'a'},     /* 03b1 Greek small letter alpha */
515 	{0xe1, 0xdf},     /* 00df Latin small letter sharp s */
516 	{0xe2,  'G'},     /* 0393 Greek capital letter gamma */
517 	{0xe3,  'p'},     /* 03c0 Greek small letter pi */
518 	{0xe4,  'S'},     /* 03a3 Greek capital letter sigma */
519 	{0xe5,  's'},     /* 03c3 Greek small letter sigma */
520 	{0xe6, 0xb5},     /* 00b5 Micro sign */
521 	{0xe7,  't'},     /* 03c4 Greek small letter tau */
522 	{0xe8,  'F'},     /* 03a6 Greek capital letter phi */
523 	{0xe9,  'T'},     /* 0398 Greek capital letter theta */
524 	{0xea,  'O'},     /* 03a9 Greek capital letter omega */
525 	{0xeb,  'd'},     /* 03b4 Greek small letter delta */
526 	{0xec,  '.'},     /* 221e Infinity */
527 	{0xed,  'f'},     /* 03c6 Greek small letter phi */
528 	{0xee,  'e'},     /* 03b5 Greek small letter epsilon */
529 	{0xef,  '^'},     /* 2229 Intersection */
530 	{0xf0,  '='},     /* 2261 Identical to */
531 	{0xf1, 0xb1},     /* 00b1 Plus-minus sign */
532 	{0xf2,  '>'},     /* 2265 Greater-than or equal to */
533 	{0xf3,  '<'},     /* 2264 Less-than or equal to */
534 	{0xf4,  'f'},     /* 2320 Top half integral */
535 	{0xf5,  'j'},     /* 2321 Bottom half integral */
536 	{0xf6, 0xf7},     /* 00f7 Division sign */
537 	{0xf7,  '='},     /* 2248 Almost equal to */
538 	{0xf8, 0xb0},     /* 00b0 Degree sign */
539 	{0xf9, 0xb7},     /* 2219 Bullet operator */
540 	{0xfa, 0xb7},     /* 00b7 Middle dot */
541 	{0xfb,  '/'},     /* 221a Square root */
542 	{0xfc,  'n'},     /* 207f Superscript latin small letter n */
543 	{0xfd, 0xb2},     /* 00b2 Superscript two */
544 	{0xfe,  '#'},     /* 25a0 Black square */
545 	{0xff, 0xa0},     /* 00a0 No-break space */
546 	{0, 0}            /* 0000 [END OF TABLE] */
547 };
548 
549 
550 /*
551  * gagt_cp_to_iso()
552  *
553  * Convert a string from code page 437 into ISO 8859 Latin-1.  The input and
554  * output buffers may be one and the same.
555  */
gagt_cp_to_iso(const unsigned char * from_string,unsigned char * to_string)556 static void gagt_cp_to_iso(const unsigned char *from_string, unsigned char *to_string) {
557 	static int is_initialized = FALSE;
558 	static unsigned char table[BYTE_MAX_VAL + 1];
559 
560 	int index;
561 	unsigned char cp437, iso8859_1;
562 	assert(from_string && to_string);
563 
564 	if (!is_initialized) {
565 		gagt_charref_t entry;
566 
567 		/*
568 		 * Create a lookup entry for each code in the main table.  Fill in gaps
569 		 * for 7-bit characters with their ASCII equivalent values.  Any
570 		 * remaining codes not represented in the main table will map to zeroes
571 		 * in the lookup table, as static variables are initialized to zero.
572 		 */
573 		for (entry = GAGT_CHAR_TABLE; entry->cp437; entry++) {
574 			cp437 = entry->cp437;
575 			iso8859_1 = entry->iso8859_1;
576 
577 //			assert(cp437 < 0x20 || (cp437 > INT8_MAX_VAL && cp437 <= BYTE_MAX_VAL));
578 			table[cp437] = iso8859_1;
579 		}
580 		for (index = 0; index <= INT8_MAX_VAL; index++) {
581 			if (table[index] == 0)
582 				table[index] = index;
583 		}
584 
585 		is_initialized = TRUE;
586 	}
587 
588 	for (index = 0; from_string[index] != '\0'; index++) {
589 		cp437 = from_string[index];
590 		iso8859_1 = table[cp437];
591 
592 		to_string[index] = iso8859_1 ? iso8859_1 : cp437;
593 	}
594 
595 	to_string[index] = '\0';
596 }
597 
598 
599 /*
600  * gagt_iso_to_cp()
601  *
602  * Convert a string from ISO 8859 Latin-1 to code page 437.  The input and
603  * output buffers may be one and the same.
604  */
gagt_iso_to_cp(const unsigned char * from_string,unsigned char * to_string)605 static void gagt_iso_to_cp(const unsigned char *from_string, unsigned char *to_string) {
606 	static int is_initialized = FALSE;
607 	static unsigned char table[BYTE_MAX_VAL + 1];
608 
609 	int index;
610 	unsigned char iso8859_1, cp437;
611 	assert(from_string && to_string);
612 
613 	if (!is_initialized) {
614 		gagt_charref_t entry;
615 
616 		/*
617 		 * Create a reverse lookup entry for each code in the main table,
618 		 * overriding all of the low table entries (that is, anything under
619 		 * 128) with their ASCII no matter what the table contained.
620 		 *
621 		 * Any codes not represented in the main table will map to zeroes in
622 		 * the reverse lookup table, since static variables are initialized to
623 		 * zero.  The first 128 characters are equivalent to ASCII.  Moreover,
624 		 * some ISO 8859 Latin-1 entries are faked as base ASCII; where an
625 		 * entry is already occupied, the main table entry is skipped, so the
626 		 * match, which is n:1 in the reverse direction, works in first-found
627 		 * mode.
628 		 */
629 		for (entry = GAGT_CHAR_TABLE; entry->iso8859_1; entry++) {
630 			cp437 = entry->cp437;
631 			iso8859_1 = entry->iso8859_1;
632 
633 			if (table[iso8859_1] == 0)
634 				table[iso8859_1] = cp437;
635 		}
636 		for (index = 0; index <= INT8_MAX_VAL; index++)
637 			table[index] = index;
638 
639 		is_initialized = TRUE;
640 	}
641 
642 	for (index = 0; from_string[index] != '\0'; index++) {
643 		iso8859_1 = from_string[index];
644 		cp437 = table[iso8859_1];
645 
646 		to_string[index] = cp437 ? cp437 : iso8859_1;
647 	}
648 
649 	to_string[index] = '\0';
650 }
651 
652 
653 /*---------------------------------------------------------------------*/
654 /*  Glk port status line functions                                     */
655 /*---------------------------------------------------------------------*/
656 
657 /*
658  * Buffered copy of the latest status line passed in by the interpreter.
659  * Buffering it means it's readily available to print for Glk libraries
660  * that don't support separate windows.  We also need a copy of the last
661  * status buffer printed for non-windowing Glk libraries, for comparison.
662  */
663 static char *gagt_status_buffer = NULL,
664 			 *gagt_status_buffer_printed = NULL;
665 
666 /*
667  * Indication that we are in mid-delay.  The delay is silent, and can look
668  * kind of confusing, so to try to make it less so, we'll have the status
669  * window show something about it.
670  */
671 static int gagt_inside_delay = FALSE;
672 
673 
674 /*
675  * agt_statline()
676  *
677  * This function is called from our call to print_statline().  Here we'll
678  * convert the string and buffer in an allocated area for later use.
679  */
agt_statline(const char * cp_string)680 void agt_statline(const char *cp_string) {
681 	assert(cp_string);
682 
683 	free(gagt_status_buffer);
684 	gagt_status_buffer = (char *)gagt_malloc(strlen(cp_string) + 1);
685 	gagt_cp_to_iso((const unsigned char *)cp_string, (unsigned char *)gagt_status_buffer);
686 
687 	gagt_debug("agt_statline", "string='%s'", cp_string);
688 }
689 
690 
691 /*
692  * gagt_status_update_extended()
693  *
694  * Helper for gagt_status_update() and gagt_status_in_delay().  This function
695  * displays the second line of any extended status display, giving a list of
696  * exits from the compass rose, and if in an AGT delay, a waiting indicator.
697  */
gagt_status_update_extended()698 static void gagt_status_update_extended() {
699 	uint width, height;
700 	assert(g_vm->gagt_status_window);
701 
702 	g_vm->glk_window_get_size(g_vm->gagt_status_window, &width, &height);
703 	if (height > 1) {
704 		uint32 index;
705 		int exit;
706 
707 		/* Clear the second status line only. */
708 		g_vm->glk_window_move_cursor(g_vm->gagt_status_window, 0, 1);
709 		g_vm->glk_set_window(g_vm->gagt_status_window);
710 		g_vm->glk_set_style(style_User1);
711 		for (index = 0; index < width; index++)
712 			g_vm->glk_put_char(' ');
713 
714 		/*
715 		 * Check bits in the compass rose, and print out exit names from
716 		 * the exitname[] array.
717 		 */
718 		g_vm->glk_window_move_cursor(g_vm->gagt_status_window, 0, 1);
719 		g_vm->glk_put_string("  Exits: ");
720 		for (exit = 0; exit < (int)sizeof(exitname) / (int)sizeof(exitname[0]); exit++) {
721 			if (compass_rose & (1 << exit)) {
722 				g_vm->glk_put_string(exitname[exit]);
723 				g_vm->glk_put_char(' ');
724 			}
725 		}
726 
727 		/* If the delay flag is set, print a waiting indicator at the right. */
728 		if (gagt_inside_delay) {
729 			g_vm->glk_window_move_cursor(g_vm->gagt_status_window,
730 			                             width - strlen("Waiting... "), 1);
731 			g_vm->glk_put_string("Waiting... ");
732 		}
733 
734 		g_vm->glk_set_window(g_vm->gagt_main_window);
735 	}
736 }
737 
738 
739 /*
740  * gagt_status_update()
741  *
742  *
743  * This function calls print_statline() to prompt the interpreter into calling
744  * our agt_statline(), then if we have a status window, displays the status
745  * string, and calls gagt_status_update_extended() if necessary to handle the
746  * second status line.  If we don't see a call to our agt_statline, we output
747  * a default status string.
748  */
gagt_status_update()749 static void gagt_status_update() {
750 	uint width, height;
751 	uint32 index;
752 	assert(g_vm->gagt_status_window);
753 
754 	g_vm->glk_window_get_size(g_vm->gagt_status_window, &width, &height);
755 	if (height > 0) {
756 		g_vm->glk_window_clear(g_vm->gagt_status_window);
757 		g_vm->glk_window_move_cursor(g_vm->gagt_status_window, 0, 0);
758 		g_vm->glk_set_window(g_vm->gagt_status_window);
759 
760 		g_vm->glk_set_style(style_User1);
761 		for (index = 0; index < width; index++)
762 			g_vm->glk_put_char(' ');
763 		g_vm->glk_window_move_cursor(g_vm->gagt_status_window, 0, 0);
764 
765 		/* Call print_statline() to refresh status line buffer contents. */
766 		print_statline();
767 
768 		/* See if we have a buffered status line available. */
769 		if (gagt_status_buffer) {
770 			glui32 print_width;
771 
772 			/*
773 			 * Print the basic buffered status string, truncating to the
774 			 * current status window width if necessary, then try adding a
775 			 * second line if extended status enabled.
776 			 */
777 			print_width = width < strlen(gagt_status_buffer)
778 			              ? width : strlen(gagt_status_buffer);
779 			g_vm->glk_put_buffer(gagt_status_buffer, print_width);
780 
781 			if (g_vm->gagt_extended_status_enabled)
782 				gagt_status_update_extended();
783 		} else {
784 			/*
785 			 * We don't (yet) have a status line.  Perhaps we're at the
786 			 * very start of a game.  Print a standard message.
787 			 */
788 			g_vm->glk_put_string("Glk AGiliTy version 1.1.1.1");
789 		}
790 
791 		g_vm->glk_set_window(g_vm->gagt_main_window);
792 	}
793 }
794 
795 
796 /*
797  * gagt_status_print()
798  *
799  * Print the current contents of the completed status line buffer out in the
800  * main window, if it has changed since the last call.  This is for non-
801  * windowing Glk libraries.
802  *
803  * Like gagt_status_update(), this function calls print_statline() to prompt
804  * the interpreter into calling our agt_statline(), then if we have a new
805  * status line, it prints it.
806  */
gagt_status_print()807 static void gagt_status_print() {
808 	/* Call print_statline() to refresh status line buffer contents. */
809 	print_statline();
810 
811 	/*
812 	 * Do no more if there is no status line to print, or if the status
813 	 * line hasn't changed since last printed.
814 	 */
815 	if (!gagt_status_buffer
816 	        || (gagt_status_buffer_printed
817 	            && strcmp(gagt_status_buffer, gagt_status_buffer_printed) == 0))
818 		return;
819 
820 	/* Set fixed width font to try to preserve status line formatting. */
821 	g_vm->glk_set_style(style_Preformatted);
822 
823 	/*
824 	 * Bracket, and output the status line buffer.  We don't need to put any
825 	 * spacing after the opening bracket or before the closing one, because
826 	 * AGiliTy puts leading/trailing spaces on its status lines.
827 	 */
828 	g_vm->glk_put_string("[");
829 	g_vm->glk_put_string(gagt_status_buffer);
830 	g_vm->glk_put_string("]\n");
831 
832 	/* Save the details of the printed status buffer. */
833 	free(gagt_status_buffer_printed);
834 	gagt_status_buffer_printed = (char *)gagt_malloc(strlen(gagt_status_buffer) + 1);
835 	strcpy(gagt_status_buffer_printed, gagt_status_buffer);
836 }
837 
838 
839 /*
840  * gagt_status_notify()
841  *
842  * Front end function for updating status.  Either updates the status window
843  * or prints the status line to the main window.
844  *
845  * Functions interested in updating the status line should call either this
846  * function, or gagt_status_redraw(), and not print_statline().
847  */
gagt_status_notify()848 static void gagt_status_notify() {
849 	if (!BATCH_MODE) {
850 		if (g_vm->gagt_status_window)
851 			gagt_status_update();
852 		else
853 			gagt_status_print();
854 	}
855 }
856 
857 
858 /*
859  * gagt_status_redraw()
860  *
861  * Redraw the contents of any status window with the buffered status string.
862  * This function handles window sizing, and updates the interpreter with
863  * status_width, so may, and should, be called on resize and arrange events.
864  *
865  * Functions interested in updating the status line should call either this
866  * function, or gagt_status_notify(), and not print_statline().
867  */
gagt_status_redraw()868 static void gagt_status_redraw() {
869 	if (!BATCH_MODE) {
870 		if (g_vm->gagt_status_window) {
871 			uint width, height;
872 			winid_t parent;
873 
874 			/*
875 			 * Measure the status window, and update the interpreter's
876 			 * status_width variable.
877 			 */
878 			g_vm->glk_window_get_size(g_vm->gagt_status_window, &width, &height);
879 			status_width = width;
880 
881 			/*
882 			 * Rearrange the status window, without changing its actual
883 			 * arrangement in any way.  This is a hack to work round
884 			 * incorrect window repainting in Xglk; it forces a complete
885 			 * repaint of affected windows on Glk window resize and
886 			 * arrange events, and works in part because Xglk doesn't
887 			 * check for actual arrangement changes in any way before
888 			 * invalidating its windows.  The hack should be harmless to
889 			 * Glk libraries other than Xglk, moreover, we're careful to
890 			 * activate it only on resize and arrange events.
891 			 */
892 			parent = g_vm->glk_window_get_parent(g_vm->gagt_status_window);
893 			g_vm->glk_window_set_arrangement(parent,
894 			                                 winmethod_Above | winmethod_Fixed,
895 			                                 height, NULL);
896 
897 			gagt_status_update();
898 		}
899 	}
900 }
901 
902 
903 /*
904  * gagt_status_in_delay()
905  *
906  * Tells status line functions whether the game is delaying, or not.  This
907  * function updates the extended status line, if present, automatically.
908  */
gagt_status_in_delay(int inside_delay)909 static void gagt_status_in_delay(int inside_delay) {
910 	if (!BATCH_MODE) {
911 		/* Save the new delay status flag. */
912 		gagt_inside_delay = inside_delay;
913 
914 		/*
915 		 * Update just the second line of the status window display, if
916 		 * extended status is being displayed.
917 		 */
918 		if (g_vm->gagt_status_window && g_vm->gagt_extended_status_enabled)
919 			gagt_status_update_extended();
920 	}
921 }
922 
923 
924 /*
925  * gagt_status_cleanup()
926  *
927  * Free memory resources allocated by status line functions.  Called on game
928  * end.
929  */
gagt_status_cleanup()930 static void gagt_status_cleanup() {
931 	free(gagt_status_buffer);
932 	gagt_status_buffer = NULL;
933 
934 	free(gagt_status_buffer_printed);
935 	gagt_status_buffer_printed = NULL;
936 }
937 
938 
939 /*---------------------------------------------------------------------*/
940 /*  Glk port color and text attribute handling                         */
941 /*---------------------------------------------------------------------*/
942 
943 /*
944  * AGT color and character attribute definitions.  This is the range of
945  * values passed in to agt_textcolor().
946  */
947 enum {
948 	AGT_BLACK = 0,
949 	AGT_BLUE = 1,
950 	AGT_GREEN = 2,
951 	AGT_CYAN = 3,
952 	AGT_RED = 4,
953 	AGT_MAGENTA = 5,
954 	AGT_BROWN = 6,
955 	AGT_NORMAL = 7,
956 	AGT_BLINKING = 8,
957 	AGT_WHITE = 9,
958 	AGT_FIXED_FONT = 10,
959 	AGT_VARIABLE_FONT = 11,
960 	AGT_EMPHASIS = -1,
961 	AGT_DE_EMPHASIS = -2
962 };
963 
964 /*
965  * AGiliTy colors and text attributes seem a bit confused.  Let's see if we
966  * can sort them out.  Sadly, once we have, it's often not possible to
967  * render the full range in all Glk's anyway.  Nevertheless...
968  */
969 struct gagt_attrset_t {
970 	int color;     /* Text color. */
971 	int blink;     /* Text blinking flag. */
972 	int fixed;     /* Text fixed font flag. */
973 	int emphasis;  /* Text emphasized flag. */
974 };
975 
976 /*
977  * Attributes as currently set by AGiliTy.  The default values set up here
978  * correspond to AGT_NORMAL.
979  */
980 static gagt_attrset_t gagt_current_attribute_set = { AGT_WHITE, FALSE,
981 													 FALSE, FALSE
982 												   };
983 
984 /*
985  * An extra flag to indicate if we have coerced fixed font override.  On
986  * some occasions, we need to ensure that we get fixed font no matter what
987  * the game says.
988  */
989 static int gagt_coerced_fixed = FALSE;
990 
991 /*
992  * Bit masks for packing colors and attributes.  Normally, I don't like
993  * bit-twiddling all that much, but for packing all of the above into a
994  * single byte, that's what we need.  Stuff color into the low four bits,
995  * convenient since color is from 0 to 9, then use three bits for the other
996  * attributes.
997  */
998 static const unsigned char GAGT_COLOR_MASK = 0x0f,
999 						   GAGT_BLINK_MASK = 1 << 4,
1000 						   GAGT_FIXED_MASK = 1 << 5,
1001 						   GAGT_EMPHASIS_MASK = 1 << 6;
1002 
1003 /* Forward declaration of message function. */
1004 static void gagt_standout_string(const char *message);
1005 
1006 
1007 /*
1008  * agt_textcolor()
1009  *
1010  * The AGiliTy porting guide defines the use of this function as:
1011  *
1012  *   Set text color to color #c, where the colors are as follows:
1013  *    0=Black, 1=Blue,    2=Green, 3=Cyan,
1014  *    4=Red,   5=Magenta, 6=Brown,
1015  *    7=Normal("White"-- which may actually be some other color)
1016  *       This should turn off blinking, bold, color, etc. and restore
1017  *       the text mode to its default appearance.
1018  *    8=Turn on blinking.
1019  *    9= *Just* White (not neccessarily "normal" and no need to turn off
1020  *        blinking)
1021  *   10=Turn on fixed pitch font.
1022  *   11=Turn off fixed pitch font
1023  *   Also used to set other text attributes:
1024  *     -1=emphasized text, used (e.g.) for room titles
1025  *     -2=end emphasized text
1026  *
1027  * Here we try to make sense of all this.  Given an argument, we'll try to
1028  * update our separated color and text attributes flags to reflect the
1029  * expected text rendering.
1030  */
agt_textcolor(int color)1031 void agt_textcolor(int color) {
1032 	switch (color) {
1033 	case AGT_BLACK:
1034 	case AGT_BLUE:
1035 	case AGT_GREEN:
1036 	case AGT_CYAN:
1037 	case AGT_RED:
1038 	case AGT_MAGENTA:
1039 	case AGT_BROWN:
1040 	case AGT_WHITE:
1041 		gagt_current_attribute_set.color = color;
1042 		break;
1043 
1044 	case AGT_NORMAL:
1045 		gagt_current_attribute_set.color = AGT_WHITE;
1046 		gagt_current_attribute_set.blink = FALSE;
1047 		gagt_current_attribute_set.fixed = FALSE;
1048 		gagt_current_attribute_set.emphasis = FALSE;
1049 		break;
1050 
1051 	case AGT_BLINKING:
1052 		gagt_current_attribute_set.blink = TRUE;
1053 		break;
1054 
1055 	case AGT_FIXED_FONT:
1056 		gagt_current_attribute_set.fixed = TRUE;
1057 		break;
1058 
1059 	case AGT_VARIABLE_FONT:
1060 		gagt_current_attribute_set.fixed = FALSE;
1061 		break;
1062 
1063 	case AGT_EMPHASIS:
1064 		gagt_current_attribute_set.emphasis = TRUE;
1065 		break;
1066 
1067 	case AGT_DE_EMPHASIS:
1068 		gagt_current_attribute_set.emphasis = FALSE;
1069 		break;
1070 
1071 	default:
1072 		gagt_fatal("GLK: Unknown color encountered");
1073 		gagt_exit();
1074 	}
1075 
1076 	gagt_debug("agt_textcolor", "color=% d -> %d%s%s%s",
1077 	           color,
1078 	           gagt_current_attribute_set.color,
1079 	           gagt_current_attribute_set.blink ? " blink" : "",
1080 	           gagt_current_attribute_set.fixed ? " fixed" : "",
1081 	           gagt_current_attribute_set.emphasis ? " bold" : "");
1082 }
1083 
1084 
1085 /*
1086  * gagt_coerce_fixed_font()
1087  *
1088  * This coerces, or relaxes, a fixed font setting.  Used by box drawing, to
1089  * ensure that we get a temporary fixed font setting for known differenti-
1090  * ated parts of game output text.  Pass in TRUE to coerce fixed font, and
1091  * FALSE to relax it.
1092  */
gagt_coerce_fixed_font(int coerce)1093 static void gagt_coerce_fixed_font(int coerce) {
1094 	gagt_coerced_fixed = coerce;
1095 }
1096 
1097 
1098 /*
1099  * gagt_pack_attributes()
1100  *
1101  * Pack a set of color and text rendering attributes into a single byte,
1102  * and return it.  This function is used so that a set of text attributes
1103  * can be encoded into a byte array that parallels the output strings that
1104  * we buffer from the interpreter.
1105  */
gagt_pack_attributes(const gagt_attrset_t * attribute_set,int coerced)1106 static unsigned char gagt_pack_attributes(const gagt_attrset_t *attribute_set, int coerced) {
1107 	unsigned char packed;
1108 	assert(attribute_set);
1109 
1110 	/* Set the initial result to be color; these are the low bits. */
1111 	assert((attribute_set->color & ~GAGT_COLOR_MASK) == 0);
1112 	packed = attribute_set->color;
1113 
1114 	/*
1115 	 * Now OR in the text attributes settings, taking either the value for
1116 	 * fixed or the coerced fixed font.
1117 	 */
1118 	packed |= attribute_set->blink ? GAGT_BLINK_MASK : 0;
1119 	packed |= attribute_set->fixed || coerced ? GAGT_FIXED_MASK : 0;
1120 	packed |= attribute_set->emphasis ? GAGT_EMPHASIS_MASK : 0;
1121 
1122 	return packed;
1123 }
1124 
1125 
1126 /*
1127  * gagt_unpack_attributes()
1128  *
1129  * Unpack a set of packed current color and text rendering attributes from a
1130  * single byte, and return the result of unpacking.  This reconstitutes the
1131  * text attributes that were current at the time of packing.
1132  */
gagt_unpack_attributes(unsigned char packed,gagt_attrset_t * attribute_set)1133 static void gagt_unpack_attributes(unsigned char packed, gagt_attrset_t *attribute_set) {
1134 	assert(attribute_set);
1135 
1136 	attribute_set->color = packed & GAGT_COLOR_MASK;
1137 	attribute_set->blink = (packed & GAGT_BLINK_MASK) != 0;
1138 	attribute_set->fixed = (packed & GAGT_FIXED_MASK) != 0;
1139 	attribute_set->emphasis = (packed & GAGT_EMPHASIS_MASK) != 0;
1140 }
1141 
1142 
1143 /*
1144  * gagt_pack_current_attributes()
1145  *
1146  * Pack the current color and text rendering attributes into a single byte,
1147  * and return it.
1148  */
gagt_pack_current_attributes()1149 static unsigned char gagt_pack_current_attributes() {
1150 	return gagt_pack_attributes(&gagt_current_attribute_set, gagt_coerced_fixed);
1151 }
1152 
1153 
1154 /*
1155  * gagt_init_user_styles()
1156  *
1157  * Attempt to set up two defined styles, User1 and User2, to represent
1158  * fixed font with AGT emphasis (rendered as Glk subheader), and fixed font
1159  * with AGT blink (rendered as Glk emphasis), respectively.
1160  *
1161  * The Glk stylehints here may not actually be honored by the Glk library.
1162  * We'll try to detect this later on.
1163  */
gagt_init_user_styles()1164 static void gagt_init_user_styles() {
1165 	/*
1166 	 * Set User1 to be fixed width, bold, and not italic.  Here we're sort of
1167 	 * assuming that the style starts life equal to Normal.
1168 	 */
1169 	g_vm->glk_stylehint_set(wintype_TextBuffer, style_User1,
1170 	                        stylehint_Proportional, 0);
1171 	g_vm->glk_stylehint_set(wintype_TextBuffer, style_User1, stylehint_Weight, 1);
1172 	g_vm->glk_stylehint_set(wintype_TextBuffer, style_User1, stylehint_Oblique, 0);
1173 
1174 	/*
1175 	 * Set User2 to be fixed width, normal, and italic, with the same
1176 	 * assumptions.
1177 	 */
1178 	g_vm->glk_stylehint_set(wintype_TextBuffer, style_User2,
1179 	                        stylehint_Proportional, 0);
1180 	g_vm->glk_stylehint_set(wintype_TextBuffer, style_User2, stylehint_Weight, 0);
1181 	g_vm->glk_stylehint_set(wintype_TextBuffer, style_User2, stylehint_Oblique, 1);
1182 }
1183 
1184 
1185 /*
1186  * gagt_confirm_appearance()
1187  *
1188  * Attempt to find out if a Glk style's on screen appearance matches a given
1189  * expectation.  There's a chance (often 100% with current Xglk) that we
1190  * can't tell, in which case we'll play safe, and say that it doesn't (our
1191  * caller is hoping it does).
1192  *
1193  * That is, when we return FALSE, we mean either it's not as expected, or we
1194  * don't know.
1195  */
gagt_confirm_appearance(glui32 style,glui32 stylehint,glui32 expected)1196 static int gagt_confirm_appearance(glui32 style, glui32 stylehint, glui32 expected) {
1197 	uint result;
1198 
1199 	if (g_vm->glk_style_measure(g_vm->gagt_main_window, style, stylehint, &result)) {
1200 		/*
1201 		 * Measurement succeeded, so return TRUE if the result matches the
1202 		 * caller's expectation.
1203 		 */
1204 		if (result == expected)
1205 			return TRUE;
1206 	}
1207 
1208 	/* No straight answer, or the style's stylehint failed to match. */
1209 	return FALSE;
1210 }
1211 
1212 
1213 /*
1214  * gagt_is_style_fixed()
1215  * gagt_is_style_bold()
1216  * gagt_is_style_oblique()
1217  *
1218  * Convenience functions for gagt_select_style().  A return of TRUE indicates
1219  * that the style has this attribute; FALSE indicates either that it hasn't,
1220  * or that it's not determinable.
1221  */
gagt_is_style_fixed(glui32 style)1222 static int gagt_is_style_fixed(glui32 style) {
1223 	return gagt_confirm_appearance(style, stylehint_Proportional, 0);
1224 }
1225 
gagt_is_style_bold(glui32 style)1226 static int gagt_is_style_bold(glui32 style) {
1227 	return gagt_confirm_appearance(style, stylehint_Weight, 1);
1228 }
1229 
gagt_is_style_oblique(glui32 style)1230 static int gagt_is_style_oblique(glui32 style) {
1231 	return gagt_confirm_appearance(style, stylehint_Oblique, 1);
1232 }
1233 
1234 
1235 /*
1236  * gagt_select_style()
1237  *
1238  * Given a set of AGT text attributes, this function returns a Glk style that
1239  * is suitable (or more accurately, the best we can come up with) for render-
1240  * ing this set of attributes.
1241  *
1242  * For now, we ignore color totally, and just concentrate on the other attr-
1243  * ibutes.  This is because few, if any, games use color (no Photopia here),
1244  * few Glk libraries, at least on Linux, allow fine grained control over text
1245  * color, and even if you can get it, the scarcity of user-defined styles in
1246  * Glk makes it too painful to contemplate.
1247  */
gagt_select_style(gagt_attrset_t * attribute_set)1248 static glui32 gagt_select_style(gagt_attrset_t *attribute_set) {
1249 	glui32 style;
1250 	assert(attribute_set);
1251 
1252 	/*
1253 	 * Glk styles are mutually exclusive, so here we'll work here by making a
1254 	 * precedence selection: AGT emphasis take precedence over AGT blinking,
1255 	 * which itself takes precedence over normal text.  Fortunately, few, if
1256 	 * any, AGT games set both emphasis and blinking (not likely to be a
1257 	 * pleasant combination).
1258 	 *
1259 	 * We'll try to map AGT emphasis to Glk Subheader, AGT blink to Glk
1260 	 * Emphasized, and normal text to Glk Normal, with modifications to this
1261 	 * for fixed width requests.
1262 	 *
1263 	 * First, then, see if emphasized text is requested in the attributes.
1264 	 */
1265 	if (attribute_set->emphasis) {
1266 		/*
1267 		 * Consider whether something requested a fixed width font or
1268 		 * disallowed a proportional one.
1269 		 *
1270 		 * Glk Preformatted is boring, flat, and lifeless.  It often offers no
1271 		 * fine grained control over emphasis, and so on.  So here we try to
1272 		 * find something better.  However, not all Glk libraries implement
1273 		 * stylehints, so we need to try to be careful to ensure that we get a
1274 		 * fixed width font, no matter what else we may miss out on.
1275 		 */
1276 		if (attribute_set->fixed) {
1277 			/*
1278 			 * To start off, we'll see if User1, the font we set up for fixed
1279 			 * width bold, really is fixed width and bold.  If it is, we'll
1280 			 * use it.
1281 			 *
1282 			 * If it isn't, we'll check Subheader.  Our Glk library probably
1283 			 * isn't implementing stylehints, but if Subheader is fixed width,
1284 			 * it may provide a better look than Preformatted -- certainly
1285 			 * it's worth a go.
1286 			 *
1287 			 * If Subheader isn't fixed width, we'll take another look at User1.
1288 			 * It could be that the check for bold wasn't definitive, but it
1289 			 * is nevertheless bold.  So check for fixed width -- if set, it's
1290 			 * probably good enough to use this font, certainly no worse than
1291 			 * Preformatted.
1292 			 *
1293 			 * If Subheader isn't guaranteed fixed width, nor is User1, we're
1294 			 * cornered into Preformatted.
1295 			 */
1296 			if (gagt_is_style_fixed(style_User1)
1297 			        && gagt_is_style_bold(style_User1))
1298 				style = style_User1;
1299 
1300 			else if (gagt_is_style_fixed(style_Subheader))
1301 				style = style_Subheader;
1302 
1303 			else if (gagt_is_style_fixed(style_User1))
1304 				style = style_User1;
1305 
1306 			else
1307 				style = style_Preformatted;
1308 		} else
1309 			/* This is the easy case, use Subheader. */
1310 			style = style_Subheader;
1311 	} else if (attribute_set->blink) {
1312 		/*
1313 		 * Again, consider whether something requested a fixed width
1314 		 * font or disallowed a proportional one.
1315 		 */
1316 		if (attribute_set->fixed) {
1317 			/*
1318 			 * As above, try to find something better than Preformatted, first
1319 			 * trying User2, then Emphasized, then User2 again, and finally
1320 			 * settling for Preformatted if neither of these two looks any
1321 			 * better.
1322 			 */
1323 			if (gagt_is_style_fixed(style_User2)
1324 			        && gagt_is_style_oblique(style_User2))
1325 				style = style_User2;
1326 
1327 			else if (gagt_is_style_fixed(style_Emphasized))
1328 				style = style_Emphasized;
1329 
1330 			else if (gagt_is_style_fixed(style_User2))
1331 				style = style_User2;
1332 
1333 			else
1334 				style = style_Preformatted;
1335 		} else
1336 			/* This is the easy case, use Emphasized. */
1337 			style = style_Emphasized;
1338 	} else {
1339 		/*
1340 		 * There's no emphasis or blinking in the attributes.  In this case,
1341 		 * use Preformatted for fixed width, and Normal for text that can be
1342 		 * rendered proportionally.
1343 		 */
1344 		if (attribute_set->fixed)
1345 			style = style_Preformatted;
1346 		else
1347 			style = style_Normal;
1348 	}
1349 
1350 	return style;
1351 }
1352 
1353 
1354 /*---------------------------------------------------------------------*/
1355 /*  Glk port output buffering functions                                */
1356 /*---------------------------------------------------------------------*/
1357 
1358 /*
1359  * Buffering game output happens at two levels.  The first level is a single
1360  * line buffer, used to catch text sent to us with agt_puts().  In parallel
1361  * with the text strings, we keep and buffer the game text attributes, as
1362  * handed to agt_textcolor(), that are in effect at the time the string is
1363  * handed to us, packed for brevity.
1364  *
1365  * As each line is completed, by a call to agt_newline(), this single line
1366  * buffer is transferred to a main text page buffer.  The main page buffer
1367  * has places in it where we can assign paragraph, font hints, and perhaps
1368  * other marker information to a line.  Initially unset, they're filled in
1369  * at the point where we need to display the buffer.
1370  */
1371 
1372 /*
1373  * Definition of font hints values.  Font hints may be:
1374  *   o none, for lines not in a definite paragraph;
1375  *   o proportional, for lines that can probably be safely rendered in a
1376  *     proportional font (if the AGT game text attributes allow it) and
1377  *     where the newline may be replaced by a space;
1378  *   o proportional_newline, for lines that may be rendered using a
1379  *     proportional font, but where the newline looks like it matters;
1380  *   o proportional_newline_standout, for proportional_newline lines that
1381  *     are also standout (for spacing in display functions);
1382  *   o fixed_width, for tables and other text that looks like it is a
1383  *     candidate for fixed font output.
1384  */
1385 typedef enum {
1386 	HINT_NONE,
1387 	HINT_PROPORTIONAL,
1388 	HINT_PROPORTIONAL_NEWLINE,
1389 	HINT_PROPORTIONAL_NEWLINE_STANDOUT,
1390 	HINT_FIXED_WIDTH
1391 } gagt_font_hint_t;
1392 
1393 /* Magic number used to ensure a pointer points to a page buffer line. */
1394 static const unsigned int GAGT_LINE_MAGIC = 0x5bc14482;
1395 
1396 /*
1397  * Definition of a single line buffer.  This is a growable string and a
1398  * parallel growable attributes array.  The string is buffered without any
1399  * null terminator -- not needed since we retain length.
1400  */
1401 typedef struct {
1402 	unsigned char *data;        /* Buffered character data. */
1403 	unsigned char *attributes;  /* Parallel character attributes, packed. */
1404 	int allocation;             /* Bytes allocated to each of the above. */
1405 	int length;                 /* Amount of data actually buffered. */
1406 } gagt_string_t;
1407 typedef gagt_string_t *gagt_stringref_t;
1408 
1409 /*
1410  * Definition of a page buffer entry.  This is a structure that holds the
1411  * the result of a single line buffer above, plus additional areas that
1412  * describe line text positioning, a blank line flag, a paragraph pointer
1413  * (NULL if not in a paragraph), and a font hint.
1414  */
1415 typedef struct gagt_line_s *gagt_lineref_t;
1416 typedef struct gagt_paragraph_s *gagt_paragraphref_t;
1417 
1418 struct gagt_line_s {
1419 	unsigned int magic;             /* Assertion check dog-tag. */
1420 
1421 	gagt_string_t buffer;           /* Buffered line string data. */
1422 
1423 	int indent;                     /* Line indentation. */
1424 	int outdent;                    /* Trailing line whitespace. */
1425 	int real_length;                /* Real line length. */
1426 	int is_blank;                   /* Line blank flag. */
1427 	int is_hyphenated;              /* Line hyphenated flag. */
1428 
1429 	gagt_paragraphref_t paragraph;  /* Paragraph containing the line. */
1430 	gagt_font_hint_t font_hint;     /* Line's font hint. */
1431 
1432 	gagt_lineref_t next;            /* List next element. */
1433 	gagt_lineref_t prior;           /* List prior element. */
1434 };
1435 
1436 /*
1437  * Definition of the actual page buffer.  This is a doubly-linked list of
1438  * lines, with a tail pointer to facilitate adding entries at the end.
1439  */
1440 static gagt_lineref_t gagt_page_head = NULL,
1441 					  gagt_page_tail = NULL;
1442 
1443 /*
1444  * Definition of the current output line; this one is appended to on
1445  * agt_puts(), and transferred into the page buffer on agt_newline().
1446  */
1447 static gagt_string_t gagt_current_buffer = { NULL, NULL, 0, 0 };
1448 
1449 /*
1450  * gagt_string_append()
1451  * gagt_string_transfer()
1452  * gagt_string_free()
1453  *
1454  * String append, move, and allocation free for string_t buffers.
1455  */
gagt_string_append(gagt_stringref_t buffer,const char * string,unsigned char packed_attributes)1456 static void gagt_string_append(gagt_stringref_t buffer, const char *string,
1457 							   unsigned char packed_attributes) {
1458 	int length, bytes;
1459 
1460 	/*
1461 	 * Find the size we'll need from the line buffer to add this string,
1462 	 * and grow buffer if necessary.
1463 	 */
1464 	length = strlen(string);
1465 	for (bytes = buffer->allocation; bytes < buffer->length + length;)
1466 		bytes = bytes == 0 ? 1 : bytes << 1;
1467 
1468 	if (bytes > buffer->allocation) {
1469 		buffer->data = (uchar *)gagt_realloc(buffer->data, bytes);
1470 		buffer->attributes = (uchar *)gagt_realloc(buffer->attributes, bytes);
1471 
1472 		buffer->allocation = bytes;
1473 	}
1474 
1475 	/* Add string to the line buffer, and store packed text attributes. */
1476 	memcpy(buffer->data + buffer->length, string, length);
1477 	memset(buffer->attributes + buffer->length, packed_attributes, length);
1478 
1479 	buffer->length += length;
1480 }
1481 
gagt_string_transfer(gagt_stringref_t from,gagt_stringref_t to)1482 static void gagt_string_transfer(gagt_stringref_t from, gagt_stringref_t to) {
1483 	*to = *from;
1484 	from->data = from->attributes = NULL;
1485 	from->allocation = from->length = 0;
1486 }
1487 
gagt_string_free(gagt_stringref_t buffer)1488 static void gagt_string_free(gagt_stringref_t buffer) {
1489 	free(buffer->data);
1490 	free(buffer->attributes);
1491 	buffer->data = buffer->attributes = NULL;
1492 	buffer->allocation = buffer->length = 0;
1493 }
1494 
1495 
1496 /*
1497  * gagt_get_string_indent()
1498  * gagt_get_string_outdent()
1499  * gagt_get_string_real_length()
1500  * gagt_is_string_blank()
1501  * gagt_is_string_hyphenated()
1502  *
1503  * Metrics functions for string_t buffers.
1504  */
gagt_get_string_indent(const gagt_stringref_t buffer)1505 static int gagt_get_string_indent(const gagt_stringref_t buffer) {
1506 	int indent, index;
1507 
1508 	indent = 0;
1509 	for (index = 0;
1510 	        index < buffer->length && isspace(buffer->data[index]);
1511 	        index++)
1512 		indent++;
1513 
1514 	return indent;
1515 }
1516 
gagt_get_string_outdent(const gagt_stringref_t buffer)1517 static int gagt_get_string_outdent(const gagt_stringref_t buffer) {
1518 	int outdent, index;
1519 
1520 	outdent = 0;
1521 	for (index = buffer->length - 1;
1522 	        index >= 0 && isspace(buffer->data[index]); index--)
1523 		outdent++;
1524 
1525 	return outdent;
1526 }
1527 
1528 
gagt_get_string_real_length(const gagt_stringref_t buffer)1529 static int gagt_get_string_real_length(const gagt_stringref_t buffer) {
1530 	int indent, outdent;
1531 
1532 	indent = gagt_get_string_indent(buffer);
1533 	outdent = gagt_get_string_outdent(buffer);
1534 
1535 	return indent == buffer->length ? 0 : buffer->length - indent - outdent;
1536 }
1537 
gagt_is_string_blank(const gagt_stringref_t buffer)1538 static int gagt_is_string_blank(const gagt_stringref_t buffer) {
1539 	return gagt_get_string_indent(buffer) == buffer->length;
1540 }
1541 
gagt_is_string_hyphenated(const gagt_stringref_t buffer)1542 static int gagt_is_string_hyphenated(const gagt_stringref_t buffer) {
1543 	int is_hyphenated;
1544 
1545 	is_hyphenated = FALSE;
1546 
1547 	if (!gagt_is_string_blank(buffer)
1548 	        && gagt_get_string_real_length(buffer) > 1) {
1549 		int last;
1550 
1551 		last = buffer->length - gagt_get_string_outdent(buffer) - 1;
1552 
1553 		if (buffer->data[last] == '-') {
1554 			if (isalpha(buffer->data[last - 1]))
1555 				is_hyphenated = TRUE;
1556 		}
1557 	}
1558 
1559 	return is_hyphenated;
1560 }
1561 
1562 
1563 /*
1564  * gagt_output_delete()
1565  *
1566  * Delete all buffered page and line text.  Free all malloc'ed buffer memory,
1567  * and return the buffer variables to their initial values.
1568  */
gagt_output_delete()1569 static void gagt_output_delete() {
1570 	gagt_lineref_t line, next_line;
1571 
1572 	for (line = gagt_page_head; line; line = next_line) {
1573 		assert(line->magic == GAGT_LINE_MAGIC);
1574 		next_line = line->next;
1575 
1576 		gagt_string_free(&line->buffer);
1577 
1578 		memset(line, 0, sizeof(*line));
1579 		free(line);
1580 	}
1581 
1582 	gagt_page_head = gagt_page_tail = NULL;
1583 
1584 	gagt_string_free(&gagt_current_buffer);
1585 }
1586 
1587 
1588 /*
1589  * agt_puts()
1590  *
1591  * Buffer the string passed in into our current single line buffer.  The
1592  * function converts to ISO 8859 Latin-1 encoding before buffering.
1593  */
agt_puts(const char * cp_string)1594 void agt_puts(const char *cp_string) {
1595 	assert(cp_string);
1596 
1597 	if (!BATCH_MODE) {
1598 		char *iso_string;
1599 		unsigned char packed;
1600 		int length;
1601 
1602 		/* Update the apparent (virtual) window x position. */
1603 		length = strlen(cp_string);
1604 		curr_x += length;
1605 
1606 		/*
1607 		 * Convert the buffer from IBM cp 437 to Glk's ISO 8859 Latin-1, and
1608 		 * add string and packed text attributes to the current line buffer.
1609 		 */
1610 		iso_string = (char *)gagt_malloc(length + 1);
1611 		gagt_cp_to_iso((const uchar *)cp_string, (uchar *)iso_string);
1612 		packed = gagt_pack_current_attributes();
1613 		gagt_string_append(&gagt_current_buffer, iso_string, packed);
1614 
1615 		/* Add the string to any script file. */
1616 		if (script_on)
1617 			textputs(scriptfile, iso_string);
1618 
1619 		free(iso_string);
1620 		gagt_debug("agt_puts", "string='%s'", cp_string);
1621 	}
1622 }
1623 
1624 
1625 /*
1626  * agt_newline()
1627  *
1628  * Accept a newline to the main window.  Our job here is to append the
1629  * current line buffer to the page buffer, and clear the line buffer to
1630  * begin accepting new text.
1631  */
agt_newline()1632 void agt_newline() {
1633 	if (!BATCH_MODE) {
1634 		gagt_lineref_t line;
1635 
1636 		/* Update the apparent (virtual) window x position. */
1637 		curr_x = 0;
1638 
1639 		/* Create a new line entry for the page buffer. */
1640 		line = (gagt_lineref_t)gagt_malloc(sizeof(*line));
1641 		line->magic = GAGT_LINE_MAGIC;
1642 
1643 		/* Move the line from the line buffer into the page buffer. */
1644 		gagt_string_transfer(&gagt_current_buffer, &line->buffer);
1645 
1646 		/* Fill in the line buffer metrics. */
1647 		line->indent = gagt_get_string_indent(&line->buffer);
1648 		line->outdent = gagt_get_string_outdent(&line->buffer);
1649 		line->real_length = gagt_get_string_real_length(&line->buffer);
1650 		line->is_blank = gagt_is_string_blank(&line->buffer);
1651 		line->is_hyphenated = gagt_is_string_hyphenated(&line->buffer);
1652 
1653 		/* For now, default the remaining page buffer fields for the line. */
1654 		line->paragraph = NULL;
1655 		line->font_hint = HINT_NONE;
1656 
1657 		/* Add to the list, creating a new list if necessary. */
1658 		line->next = NULL;
1659 		line->prior = gagt_page_tail;
1660 		if (gagt_page_head)
1661 			gagt_page_tail->next = line;
1662 		else
1663 			gagt_page_head = line;
1664 		gagt_page_tail = line;
1665 
1666 		/* Add a newline to any script file. */
1667 		if (script_on)
1668 			textputs(scriptfile, "\n");
1669 
1670 		gagt_debug("agt_newline", "");
1671 	}
1672 }
1673 
1674 
1675 /*
1676  * gagt_get_first_page_line()
1677  * gagt_get_next_page_line()
1678  * gagt_get_prior_page_line()
1679  *
1680  * Iterator functions for the page buffer.  These functions return the first
1681  * line from the page buffer, the next line, or the previous line, given a
1682  * line, respectively.  They return NULL if no lines, or no more lines, are
1683  * available.
1684  */
gagt_get_first_page_line()1685 static gagt_lineref_t gagt_get_first_page_line() {
1686 	gagt_lineref_t line;
1687 
1688 	line = gagt_page_head;
1689 	assert(!line || line->magic == GAGT_LINE_MAGIC);
1690 	return line;
1691 }
1692 
gagt_get_next_page_line(const gagt_lineref_t line)1693 static gagt_lineref_t gagt_get_next_page_line(const gagt_lineref_t line) {
1694 	gagt_lineref_t next_line;
1695 	assert(line && line->magic == GAGT_LINE_MAGIC);
1696 
1697 	next_line = line->next;
1698 	assert(!next_line || next_line->magic == GAGT_LINE_MAGIC);
1699 	return next_line;
1700 }
1701 
gagt_get_prior_page_line(const gagt_lineref_t line)1702 static gagt_lineref_t gagt_get_prior_page_line(const gagt_lineref_t line) {
1703 	gagt_lineref_t prior_line;
1704 	assert(line && line->magic == GAGT_LINE_MAGIC);
1705 
1706 	prior_line = line->prior;
1707 	assert(!prior_line || prior_line->magic == GAGT_LINE_MAGIC);
1708 	return prior_line;
1709 }
1710 
1711 
1712 /*---------------------------------------------------------------------*/
1713 /*  Glk port paragraphing functions and data                           */
1714 /*---------------------------------------------------------------------*/
1715 
1716 /* Magic number used to ensure a pointer points to a paragraph. */
1717 static const unsigned int GAGT_PARAGRAPH_MAGIC = 0xb9a2297b;
1718 
1719 /* Forward definition of special paragraph reference. */
1720 typedef const struct gagt_special_s *gagt_specialref_t;
1721 
1722 /*
1723  * Definition of a paragraph entry.  This is a structure that holds a
1724  * pointer to the first line buffer in the paragraph.
1725  */
1726 struct gagt_paragraph_s {
1727 	unsigned int magic;             /* Assertion check dog-tag. */
1728 
1729 	gagt_lineref_t first_line;      /* First line in the paragraph. */
1730 	gagt_specialref_t special;      /* Special paragraph entry. */
1731 
1732 	int line_count;                 /* Number of lines in the paragraph. */
1733 	int id;                         /* Paragraph id, sequence, for debug only. */
1734 
1735 	gagt_paragraphref_t next;       /* List next element. */
1736 	gagt_paragraphref_t prior;      /* List prior element. */
1737 };
1738 
1739 /*
1740  * A doubly-linked list of paragraphs, with a tail pointer to facilitate
1741  * adding entries at the end.
1742  */
1743 static gagt_paragraphref_t gagt_paragraphs_head = NULL,
1744 						   gagt_paragraphs_tail = NULL;
1745 
1746 /*
1747  * gagt_paragraphs_delete()
1748  *
1749  * Delete paragraphs held in the list.  This function doesn't delete the
1750  * page buffer lines, just the paragraphs describing the page.
1751  */
gagt_paragraphs_delete()1752 static void gagt_paragraphs_delete() {
1753 	gagt_paragraphref_t paragraph, next_paragraph;
1754 
1755 	for (paragraph = gagt_paragraphs_head; paragraph; paragraph = next_paragraph) {
1756 		assert(paragraph->magic == GAGT_PARAGRAPH_MAGIC);
1757 		next_paragraph = paragraph->next;
1758 
1759 		memset(paragraph, 0, sizeof(*paragraph));
1760 		free(paragraph);
1761 	}
1762 
1763 	gagt_paragraphs_head = gagt_paragraphs_tail = NULL;
1764 }
1765 
1766 
1767 /*
1768  * gagt_find_paragraph_start()
1769  *
1770  * Find and return the next non-blank line in the page buffer, given a start
1771  * point.  Returns NULL if there are no more blank lines.
1772  */
gagt_find_paragraph_start(const gagt_lineref_t begin)1773 static gagt_lineref_t gagt_find_paragraph_start(const gagt_lineref_t begin) {
1774 	gagt_lineref_t line, match;
1775 
1776 	/*
1777 	 * Advance line to the beginning of the next paragraph, stopping on the
1778 	 * first non-blank line, or at the end of the page buffer.
1779 	 */
1780 	match = NULL;
1781 	for (line = begin; line; line = gagt_get_next_page_line(line)) {
1782 		if (!line->is_blank) {
1783 			match = line;
1784 			break;
1785 		}
1786 	}
1787 
1788 	return match;
1789 }
1790 
1791 
1792 /*
1793  * gagt_find_block_end()
1794  * gagt_find_blank_line_block_end()
1795  *
1796  * Find and return the apparent end of a paragraph from the page buffer,
1797  * given a start point, and an indentation reference.  The end is either
1798  * the point where indentation returns to the reference indentation, or
1799  * the next blank line.
1800  *
1801  * Indentation reference can be -1, indicating that only the next blank
1802  * line will end the paragraph.  Indentation references less than 1 are
1803  * also ignored.
1804  */
gagt_find_block_end(const gagt_lineref_t begin,int indent)1805 static gagt_lineref_t gagt_find_block_end(const gagt_lineref_t begin, int indent) {
1806 	gagt_lineref_t line, match;
1807 
1808 	/*
1809 	 * Initialize the match to be the start of the block, then advance line
1810 	 * until we hit a blank line or the end of the page buffer.  At this point,
1811 	 * match contains the last line checked.
1812 	 */
1813 	match = begin;
1814 	for (line = begin; line; line = gagt_get_next_page_line(line)) {
1815 		/*
1816 		 * Found if we reach a blank line, or when given an indentation to
1817 		 * check for, we find it.
1818 		 */
1819 		if (line->is_blank || (indent > 0 && line->indent == indent))
1820 			break;
1821 
1822 		match = line;
1823 	}
1824 
1825 	return match;
1826 }
1827 
gagt_find_blank_line_block_end(const gagt_lineref_t begin)1828 static gagt_lineref_t gagt_find_blank_line_block_end(const gagt_lineref_t begin) {
1829 	return gagt_find_block_end(begin, -1);
1830 }
1831 
1832 
1833 /*
1834  * gagt_find_paragraph_end()
1835  *
1836  * Find and return the apparent end of a paragraph from the page buffer,
1837  * given a start point.  The function attempts to recognize paragraphs by
1838  * the "shape" of indentation.
1839  */
gagt_find_paragraph_end(const gagt_lineref_t first_line)1840 static gagt_lineref_t gagt_find_paragraph_end(const gagt_lineref_t first_line) {
1841 	gagt_lineref_t second_line;
1842 
1843 	/*
1844 	 * If the start line is the last line in the buffer, or if the next line
1845 	 * is a blank line, return the start line as also being the end of the
1846 	 * paragraph.
1847 	 */
1848 	second_line = gagt_get_next_page_line(first_line);
1849 	if (!second_line || second_line->is_blank) {
1850 		return first_line;
1851 	}
1852 
1853 	/*
1854 	 * Time to look at line indentations...
1855 	 *
1856 	 * If either line is grossly indented, forget about trying to infer
1857 	 * anything from this, and just break the paragraph on the next blank line.
1858 	 */
1859 	if (first_line->indent > screen_width / 4
1860 	        || second_line->indent > screen_width / 4) {
1861 		return gagt_find_blank_line_block_end(second_line);
1862 	}
1863 
1864 	/*
1865 	 * If the first line is indented more than the second, end the paragraph
1866 	 * on a blank line, or on a return in indentation to the level of the
1867 	 * first line.  Here we're looking for paragraphs with the shape
1868 	 *
1869 	 *     aksjdj jfkasjd fjkasjd ajksdj fkaj djf akjsd fkjas dfs
1870 	 * kasjdlkfjkj fj aksd jfjkasj dlkfja skjdk flaks dlf jalksdf
1871 	 * ksjdf kjs kdf lasjd fkjalks jdfkjasjd flkjasl djfkasjfdkl
1872 	 */
1873 	else if (first_line->indent > second_line->indent) {
1874 		return gagt_find_block_end(second_line, first_line->indent);
1875 	}
1876 
1877 	/*
1878 	 * If the second line is more indented than the first, this may indicate
1879 	 * a title line, followed by normal indented paragraphing.  In this case,
1880 	 * use the second line indentation as the reference, and begin searching
1881 	 * at the next line.  This finds
1882 	 *
1883 	 * ksjdkfjask ksadf
1884 	 *     kajskd fksjkfj jfkj jfkslaj fksjlfj jkjskjlfa j fjksal
1885 	 * sjkkdjf sj fkjkajkdlfj lsjak dfjk djkfjskl dklf alks dfll
1886 	 * fjksja jkj dksja kjdk kaj dskfj aksjdf aksjd kfjaks fjks
1887 	 *
1888 	 * and
1889 	 *
1890 	 * asdfj kjsdf kjs
1891 	 *     akjsdkj fkjs kdjfa lskjdl fjalsj dlfjksj kdj fjkd jlsjd
1892 	 *     jalksj jfk slj lkfjsa lkjd lfjlaks dlfkjals djkj alsjd
1893 	 *     kj jfksj fjksjl alkjs dlkjf lakjsd fkjas ldkj flkja fsd
1894 	 */
1895 	else if (second_line->indent > first_line->indent) {
1896 		gagt_lineref_t third_line;
1897 
1898 		/*
1899 		 * See if we have a third buffer line to look at.  If we don't, or if
1900 		 * we do but it's blank, the paragraph ends here.
1901 		 */
1902 		third_line = gagt_get_next_page_line(second_line);
1903 		if (!third_line || third_line->is_blank) {
1904 			return second_line;
1905 		}
1906 
1907 		/* As above, give up on gross indentation. */
1908 		if (second_line->indent > screen_width / 4
1909 		        || third_line->indent > screen_width / 4) {
1910 			return gagt_find_blank_line_block_end(third_line);
1911 		}
1912 
1913 		/*
1914 		 * If the second line indentation exceeds the third, this is probably
1915 		 * a paragraph with a title line.  In this case, end the paragraph on
1916 		 * a return to the indentation of the second line.  If not, just find
1917 		 * the next blank line.
1918 		 */
1919 		else if (second_line->indent > third_line->indent) {
1920 			return gagt_find_block_end(third_line, second_line->indent);
1921 		} else {
1922 			return gagt_find_blank_line_block_end(third_line);
1923 		}
1924 	}
1925 
1926 	/*
1927 	 * Otherwise, the first and second line indentations are the same, so
1928 	 * break only on the next empty line.  This finds the simple
1929 	 *
1930 	 * ksd kjal jdljf lakjsd lkj lakjsdl jfla jsldj lfaksdj fksj
1931 	 * lskjd fja kjsdlk fjlakjs ldkjfksj lkjdf kjalskjd fkjklal
1932 	 * skjd fkaj djfkjs dkfjal sjdlkfj alksjdf lkajs ldkjf alljjf
1933 	 */
1934 	else {
1935 		assert(second_line->indent == first_line->indent);
1936 		return gagt_find_blank_line_block_end(second_line);
1937 	}
1938 }
1939 
1940 
1941 /*
1942  * gagt_paragraph_page()
1943  *
1944  * This function breaks the page buffer into what appear to be paragraphs,
1945  * based on observations of indentation and blank separator lines.
1946  */
gagt_paragraph_page()1947 static void gagt_paragraph_page() {
1948 	gagt_lineref_t start;
1949 
1950 	assert(!gagt_paragraphs_head && !gagt_paragraphs_tail);
1951 
1952 	/* Find the start of the first paragraph. */
1953 	start = gagt_find_paragraph_start(gagt_get_first_page_line());
1954 	while (start) {
1955 		gagt_paragraphref_t paragraph;
1956 		gagt_lineref_t end, line;
1957 
1958 		/* Create a new paragraph entry. */
1959 		paragraph = (gagt_paragraphref_t)gagt_malloc(sizeof(*paragraph));
1960 		paragraph->magic = GAGT_PARAGRAPH_MAGIC;
1961 		paragraph->first_line = start;
1962 		paragraph->special = NULL;
1963 		paragraph->line_count = 1;
1964 		paragraph->id = gagt_paragraphs_tail ? gagt_paragraphs_tail->id + 1 : 0;
1965 
1966 		/* Add to the list, creating a new list if necessary. */
1967 		paragraph->next = NULL;
1968 		paragraph->prior = gagt_paragraphs_tail;
1969 		if (gagt_paragraphs_head)
1970 			gagt_paragraphs_tail->next = paragraph;
1971 		else
1972 			gagt_paragraphs_head = paragraph;
1973 		gagt_paragraphs_tail = paragraph;
1974 
1975 		/* From the start, identify the paragraph end. */
1976 		end = gagt_find_paragraph_end(start);
1977 
1978 		/*
1979 		 * Set paragraph in each line identified as part of this paragraph,
1980 		 * and increment the paragraph's line count.
1981 		 */
1982 		for (line = start;
1983 		        line != end; line = gagt_get_next_page_line(line)) {
1984 			line->paragraph = paragraph;
1985 			paragraph->line_count++;
1986 		}
1987 		end->paragraph = paragraph;
1988 
1989 		/*
1990 		 * If there's another line, look for the next paragraph there,
1991 		 * otherwise we're done.
1992 		 */
1993 		line = gagt_get_next_page_line(end);
1994 		if (line)
1995 			start = gagt_find_paragraph_start(line);
1996 		else
1997 			start = NULL;
1998 	}
1999 }
2000 
2001 
2002 /*
2003  * gagt_get_first_paragraph()
2004  * gagt_get_next_paragraph()
2005  *
2006  * Iterator functions for the paragraphs list.
2007  */
gagt_get_first_paragraph()2008 static gagt_paragraphref_t gagt_get_first_paragraph() {
2009 	gagt_paragraphref_t paragraph;
2010 
2011 	paragraph = gagt_paragraphs_head;
2012 	assert(!paragraph || paragraph->magic == GAGT_PARAGRAPH_MAGIC);
2013 	return paragraph;
2014 }
2015 
gagt_get_next_paragraph(const gagt_paragraphref_t paragraph)2016 static gagt_paragraphref_t gagt_get_next_paragraph(const gagt_paragraphref_t paragraph) {
2017 	gagt_paragraphref_t next_paragraph;
2018 	assert(paragraph && paragraph->magic == GAGT_PARAGRAPH_MAGIC);
2019 
2020 	next_paragraph = paragraph->next;
2021 	assert(!next_paragraph || next_paragraph->magic == GAGT_PARAGRAPH_MAGIC);
2022 	return next_paragraph;
2023 }
2024 
2025 
2026 /*
2027  * gagt_get_first_paragraph_line()
2028  * gagt_get_next_paragraph_line()
2029  * gagt_get_prior_paragraph_line()
2030  *
2031  * Iterator functions for the page buffer.  These functions implement a
2032  * paragraph-based view of the page buffer.
2033  *
2034  * The functions find the first line of a given paragraph; given a line,
2035  * the next line in the same paragraph, or NULL if line is the last para-
2036  * graph line (or the last line in the page buffer); and given a line,
2037  * the previous line in the same paragraph, or NULL if line is the first
2038  * paragraph line (or the first line in the page buffer).
2039  */
gagt_get_first_paragraph_line(const gagt_paragraphref_t paragraph)2040 static gagt_lineref_t gagt_get_first_paragraph_line(const gagt_paragraphref_t paragraph) {
2041 	assert(paragraph && paragraph->magic == GAGT_PARAGRAPH_MAGIC);
2042 
2043 	/* Return the first line for the requested paragraph. */
2044 	return paragraph->first_line;
2045 }
2046 
gagt_get_next_paragraph_line(const gagt_lineref_t line)2047 static gagt_lineref_t gagt_get_next_paragraph_line(const gagt_lineref_t line) {
2048 	gagt_lineref_t next_line;
2049 
2050 	/* Get the next line; return it if the paragraph matches, else NULL. */
2051 	next_line = gagt_get_next_page_line(line);
2052 	if (next_line && next_line->paragraph == line->paragraph)
2053 		return next_line;
2054 	else
2055 		return NULL;
2056 }
2057 
gagt_get_prior_paragraph_line(const gagt_lineref_t line)2058 static gagt_lineref_t gagt_get_prior_paragraph_line(const gagt_lineref_t line) {
2059 	gagt_lineref_t prior_line;
2060 
2061 	/* Get the previous line; return it if the paragraph matches, else NULL. */
2062 	prior_line = gagt_get_prior_page_line(line);
2063 	if (prior_line && prior_line->paragraph == line->paragraph)
2064 		return prior_line;
2065 	else
2066 		return NULL;
2067 }
2068 
2069 
2070 /*
2071  * gagt_get_paragraph_line_count()
2072  *
2073  * Return the count of lines contained in the paragraph.
2074  */
gagt_get_paragraph_line_count(const gagt_paragraphref_t paragraph)2075 static int gagt_get_paragraph_line_count(const gagt_paragraphref_t paragraph) {
2076 	assert(paragraph && paragraph->magic == GAGT_PARAGRAPH_MAGIC);
2077 
2078 	return paragraph->line_count;
2079 }
2080 
2081 
2082 /*---------------------------------------------------------------------*/
2083 /*  Glk port page buffer analysis functions                            */
2084 /*---------------------------------------------------------------------*/
2085 
2086 /*
2087  * Threshold for consecutive punctuation/spaces before we decide that a line
2088  * is in fact part of a table, and a small selection of characters to apply
2089  * a somewhat larger threshold to when looking for punctuation (typically,
2090  * characters that appear together multiple times in non-table text).
2091  */
2092 static const int GAGT_THRESHOLD = 4,
2093 				 GAGT_COMMON_THRESHOLD = 8;
2094 static const char *const GAGT_COMMON_PUNCTUATION = ".!?";
2095 
2096 
2097 /*
2098  * gagt_line_is_standout()
2099  *
2100  * Return TRUE if a page buffer line appears to contain "standout" text.
2101  * This is one of:
2102  *    - a line where all characters have some form of AGT text attribute
2103  *      set (blinking, fixed width font, or emphasis),
2104  *    - a line where each alphabetical character is uppercase.
2105  * Typically, this describes room and other miscellaneous header lines.
2106  */
gagt_line_is_standout(const gagt_lineref_t line)2107 static int gagt_line_is_standout(const gagt_lineref_t line) {
2108 	int index, all_formatted, upper_count, lower_count;
2109 
2110 	/*
2111 	 * Look at the line, for cases where all characters in it have AGT font
2112 	 * attributes, and counting the upper and lower case characters.  Iterate
2113 	 * over only the significant characters in the string.
2114 	 */
2115 	all_formatted = TRUE;
2116 	upper_count = lower_count = 0;
2117 	for (index = line->indent;
2118 	        index < line->buffer.length - line->outdent; index++) {
2119 		gagt_attrset_t attribute_set;
2120 		unsigned char character;
2121 
2122 		gagt_unpack_attributes(line->buffer.attributes[index], &attribute_set);
2123 		character = line->buffer.data[index];
2124 
2125 		/*
2126 		 * If no AGT attribute is set for this character, then not all of the
2127 		 * line is standout text.  In this case, reset the all_formatted flag.
2128 		 */
2129 		if (!(attribute_set.blink
2130 		        || attribute_set.fixed || attribute_set.emphasis))
2131 			all_formatted = FALSE;
2132 
2133 		/* Count upper and lower case characters. */
2134 		if (islower(character))
2135 			lower_count++;
2136 		else if (isupper(character))
2137 			upper_count++;
2138 	}
2139 
2140 	/*
2141 	 * Consider standout if every character was formatted, or if the string
2142 	 * is all uppercase.
2143 	 */
2144 	return all_formatted || (upper_count > 0 && lower_count == 0);
2145 }
2146 
2147 
2148 /*
2149  * gagt_set_font_hint_proportional()
2150  * gagt_set_font_hint_proportional_newline()
2151  * gagt_set_font_hint_fixed_width()
2152  *
2153  * Helpers for assigning font hints.  Font hints have strengths, and these
2154  * functions ensure that gagt_assign_paragraph_font_hints() only increases
2155  * strengths, and doesn't need to worry about checking before setting.  In
2156  * the case of newline, the function also adds standout to the font hint if
2157  * appropriate.
2158  */
gagt_set_font_hint_proportional(gagt_lineref_t line)2159 static void gagt_set_font_hint_proportional(gagt_lineref_t line) {
2160 	/* The only weaker hint than proportional is none. */
2161 	if (line->font_hint == HINT_NONE)
2162 		line->font_hint = HINT_PROPORTIONAL;
2163 }
2164 
gagt_set_font_hint_proportional_newline(gagt_lineref_t line)2165 static void gagt_set_font_hint_proportional_newline(gagt_lineref_t line) {
2166 	/*
2167 	 * Proportional and none are weaker than newline.  Because of the way we
2168 	 * set font hints, this function can't be called with a current line hint
2169 	 * of proportional newline.
2170 	 */
2171 	if (line->font_hint == HINT_NONE || line->font_hint == HINT_PROPORTIONAL) {
2172 		if (gagt_line_is_standout(line))
2173 			line->font_hint = HINT_PROPORTIONAL_NEWLINE_STANDOUT;
2174 		else
2175 			line->font_hint = HINT_PROPORTIONAL_NEWLINE;
2176 	}
2177 }
2178 
gagt_set_font_hint_fixed_width(gagt_lineref_t line)2179 static void gagt_set_font_hint_fixed_width(gagt_lineref_t line) {
2180 	/* Fixed width font is the strongest hint. */
2181 	if (line->font_hint == HINT_NONE
2182 	        || line->font_hint == HINT_PROPORTIONAL
2183 	        || line->font_hint == HINT_PROPORTIONAL_NEWLINE
2184 	        || line->font_hint == HINT_PROPORTIONAL_NEWLINE_STANDOUT)
2185 		line->font_hint = HINT_FIXED_WIDTH;
2186 }
2187 
2188 
2189 /*
2190  * gagt_assign_paragraph_font_hints()
2191  *
2192  * For a given paragraph in the page buffer, this function looks at the text
2193  * style used, and assigns a font hint value to each line.  Font hints
2194  * indicate whether the line probably requires fixed width font, or may be
2195  * okay in variable width, and for lines that look like they might be okay
2196  * in variable width, whether the newline should probably be rendered at the
2197  * end of the line, or if it might be omitted.
2198  */
gagt_assign_paragraph_font_hints(const gagt_paragraphref_t paragraph)2199 static void gagt_assign_paragraph_font_hints(const gagt_paragraphref_t paragraph) {
2200 	static int is_initialized = FALSE;
2201 	static int threshold[BYTE_MAX_VAL + 1];
2202 
2203 	gagt_lineref_t line, first_line;
2204 	int is_table, in_list;
2205 	assert(paragraph);
2206 
2207 	/* On first call, set up the table on punctuation run thresholds. */
2208 	if (!is_initialized) {
2209 		int character;
2210 
2211 		for (character = 0; character <= BYTE_MAX_VAL; character++) {
2212 			/*
2213 			 * Set the threshold, either a normal value, or a larger one for
2214 			 * punctuation characters that tend to have consecutive runs in
2215 			 * non-table text.
2216 			 */
2217 			if (ispunct(character)) {
2218 				threshold[character] = strchr(GAGT_COMMON_PUNCTUATION, character)
2219 				                       ? GAGT_COMMON_THRESHOLD : GAGT_THRESHOLD;
2220 			}
2221 		}
2222 
2223 		is_initialized = TRUE;
2224 	}
2225 
2226 	/*
2227 	 * Note the first paragraph line.  This value is commonly used, and under
2228 	 * certain circumstances, it's also modified later on.
2229 	 */
2230 	first_line = gagt_get_first_paragraph_line(paragraph);
2231 	assert(first_line);
2232 
2233 	/*
2234 	 * Phase 1 -- look for pages that consist of just one paragraph,
2235 	 *            itself consisting of only one line.
2236 	 *
2237 	 * There is no point in attempting alignment of text in a one paragraph,
2238 	 * one line page.  This would be, for example, an error message from the
2239 	 * interpreter parser.  In this case, set the line for proportional with
2240 	 * newline, and return immediately.
2241 	 */
2242 	if (gagt_get_first_paragraph() == paragraph
2243 	        && !gagt_get_next_paragraph(paragraph)
2244 	        && !gagt_get_next_paragraph_line(first_line)) {
2245 		/*
2246 		 * Set the first paragraph line for proportional with a newline, and
2247 		 * return.
2248 		 */
2249 		gagt_set_font_hint_proportional_newline(first_line);
2250 		return;
2251 	}
2252 
2253 	/*
2254 	 * Phase 2 -- try to identify paragraphs that are tables, based on
2255 	 *            looking for runs of punctuation.
2256 	 *
2257 	 * Search for any string that has a run of apparent line drawing or other
2258 	 * formatting characters in it.  If we find one, we'll consider the
2259 	 * paragraph to be a "table", that is, it has some quality that we might
2260 	 * destroy if we used a proportional font.
2261 	 */
2262 	is_table = FALSE;
2263 	for (line = first_line;
2264 	        line && !is_table; line = gagt_get_next_paragraph_line(line)) {
2265 		int index, counts[BYTE_MAX_VAL + 1], total_counts;
2266 
2267 		/*
2268 		 * Clear the initial counts.  Using memset() here is an order of
2269 		 * magnitude or two faster than a for-loop.  Also there's a total count
2270 		 * to detect when counts needs to be recleared, or is already clear.
2271 		 */
2272 		memset(counts, 0, sizeof(counts));
2273 		total_counts = 0;
2274 
2275 		/*
2276 		 * Count consecutive punctuation in the line, excluding the indentation
2277 		 * and outdent.
2278 		 */
2279 		for (index = line->indent;
2280 		        index < line->buffer.length - line->outdent && !is_table; index++) {
2281 			int character;
2282 			character = line->buffer.data[index];
2283 
2284 			/* Test this character for punctuation. */
2285 			if (ispunct(character)) {
2286 				/*
2287 				 * Increment the count for this character, and note that
2288 				 * counts are no longer empty, then compare against threshold.
2289 				 */
2290 				counts[character]++;
2291 				total_counts++;
2292 
2293 				is_table = (counts[character] >= threshold[character]);
2294 			} else {
2295 				/*
2296 				 * Re-clear all counts, again with memset() for speed, but only
2297 				 * if they need clearing.  As they often won't, this optimization
2298 				 * saves quite a bit of work.
2299 				 */
2300 				if (total_counts > 0) {
2301 					memset(counts, 0, sizeof(counts));
2302 					total_counts = 0;
2303 				}
2304 			}
2305 		}
2306 	}
2307 
2308 	/*
2309 	 * Phase 3 -- try again to identify paragraphs that are tables, based
2310 	 *            this time on looking for runs of whitespace.
2311 	 *
2312 	 * If no evidence found so far, look again, this time searching for any
2313 	 * run of four or more spaces on the line (excluding any lead-in or
2314 	 * trailing spaces).
2315 	 */
2316 	if (!is_table) {
2317 		for (line = first_line;
2318 		        line && !is_table; line = gagt_get_next_paragraph_line(line)) {
2319 			int index, count;
2320 
2321 			/*
2322 			 * Count consecutive spaces in the line, excluding the indentation
2323 			 * and outdent.
2324 			 */
2325 			count = 0;
2326 			for (index = line->indent;
2327 			        index < line->buffer.length - line->outdent && !is_table;
2328 			        index++) {
2329 				int character;
2330 				character = line->buffer.data[index];
2331 
2332 				if (isspace(character)) {
2333 					count++;
2334 					is_table = (count >= GAGT_THRESHOLD);
2335 				} else
2336 					count = 0;
2337 			}
2338 		}
2339 	}
2340 
2341 	/*
2342 	 * If the paragraph appears to be a table, and if it consists of more than
2343 	 * just a single line, mark all lines as fixed font output and return.
2344 	 */
2345 	if (is_table && gagt_get_next_paragraph_line(first_line)) {
2346 		for (line = first_line;
2347 		        line; line = gagt_get_next_paragraph_line(line)) {
2348 			gagt_set_font_hint_fixed_width(line);
2349 		}
2350 
2351 		/* Nothing more to do. */
2352 		return;
2353 	}
2354 
2355 	/*
2356 	 * Phase 4 -- consider separating the first line from the rest of
2357 	 *            the paragraph.
2358 	 *
2359 	 * Not a table, so the choice is between proportional rendering with a
2360 	 * newline, and proportional rendering without...
2361 	 *
2362 	 * If the first paragraph line is standout or short, render it pro-
2363 	 * portionally with a newline, and don't consider it as a further part of
2364 	 * the paragraph.
2365 	 */
2366 	if (gagt_line_is_standout(first_line)
2367 	        || first_line->real_length < screen_width / 2) {
2368 		/* Set the first paragraph line for a newline. */
2369 		gagt_set_font_hint_proportional_newline(first_line);
2370 
2371 		/*
2372 		 * Disassociate this line from the rest of the paragraph by moving on
2373 		 * the value of the first_line variable.  If it turns out that there
2374 		 * is no next paragraph line, then we have a one-line paragraph, and
2375 		 * there's no more to do.
2376 		 */
2377 		first_line = gagt_get_next_paragraph_line(first_line);
2378 		if (!first_line)
2379 			return;
2380 	}
2381 
2382 	/*
2383 	 * Phase 5 -- try to identify lists by a simple initial look at line
2384 	 *            indentations.
2385 	 *
2386 	 * Look through the paragraph for apparent lists, and decide for each
2387 	 * line whether it's appropriate to output a newline, and render
2388 	 * proportionally, or just render proportionally.
2389 	 *
2390 	 * After this loop, each line will have some form of font hint assigned
2391 	 * to it.
2392 	 */
2393 	in_list = FALSE;
2394 	for (line = first_line;
2395 	        line; line = gagt_get_next_paragraph_line(line)) {
2396 		gagt_lineref_t next_line;
2397 
2398 		next_line = gagt_get_next_paragraph_line(line);
2399 
2400 		/*
2401 		 * Special last-iteration processing.  The newline is always output at
2402 		 * the end of a paragraph, so if there isn't a next line, then this
2403 		 * line is the last paragraph line.  Set its font hint appropriately,
2404 		 * and do no more for the line.
2405 		 */
2406 		if (!next_line) {
2407 			gagt_set_font_hint_proportional_newline(line);
2408 			continue;
2409 		}
2410 
2411 		/*
2412 		 * If the next line's indentation is deeper that that of the first
2413 		 * line, this paragraph looks like it is trying to be some form of a
2414 		 * list.  In this case, make newline significant for the current line,
2415 		 * and set the in_list flag so we can delay the return to proportional
2416 		 * by one line.  On return to first line indentation, make newline
2417 		 * significant for the return line.
2418 		 */
2419 		if (next_line->indent > first_line->indent) {
2420 			gagt_set_font_hint_proportional_newline(line);
2421 			in_list = TRUE;
2422 		} else {
2423 			if (in_list)
2424 				gagt_set_font_hint_proportional_newline(line);
2425 			else
2426 				gagt_set_font_hint_proportional(line);
2427 			in_list = FALSE;
2428 		}
2429 	}
2430 
2431 	/*
2432 	 * Phase 6 -- look again for lines that look like they are supposed
2433 	 *            to stand out from their neighbors.
2434 	 *
2435 	 * Now rescan the paragraph, looking this time for lines that stand out
2436 	 * from their neighbours.  Make newline significant for each such line,
2437 	 * and the line above, if there is one.
2438 	 *
2439 	 * Here we split the loop on lines so that we avoid looking at the prior
2440 	 * line of the current first line -- because of "adjustments", it may not
2441 	 * be the real paragraph first line.
2442 	 *
2443 	 * So, deal with the current first line...
2444 	 */
2445 	if (gagt_line_is_standout(first_line)) {
2446 		/* Make newline significant for this line. */
2447 		gagt_set_font_hint_proportional_newline(first_line);
2448 	}
2449 
2450 	/* ... then deal with the rest of the lines, looking for standouts. */
2451 	for (line = gagt_get_next_paragraph_line(first_line);
2452 	        line; line = gagt_get_next_paragraph_line(line)) {
2453 		if (gagt_line_is_standout(line)) {
2454 			gagt_lineref_t prior_line;
2455 
2456 			/* Make newline significant for this line. */
2457 			gagt_set_font_hint_proportional_newline(line);
2458 
2459 			/*
2460 			 * Make newline significant for the line above.  There will always
2461 			 * be one because we start the loop past the first line.
2462 			 */
2463 			prior_line = gagt_get_prior_paragraph_line(line);
2464 			gagt_set_font_hint_proportional_newline(prior_line);
2465 		}
2466 	}
2467 
2468 	/*
2469 	 * Phase 7 -- special case short lines at the paragraph start.
2470 	 *
2471 	 * Make a special case of lines that begin a paragraph, and are short and
2472 	 * followed by a much longer line.  This should catch games which output
2473 	 * room titles above descriptions without using AGT fonts/bold/whatever.
2474 	 * Without this trap, room titles and their descriptions are run together.
2475 	 * This is more programmatic guesswork than heuristics.
2476 	 */
2477 	if (gagt_get_next_paragraph_line(first_line)) {
2478 		gagt_lineref_t next_line;
2479 
2480 		next_line = gagt_get_next_paragraph_line(first_line);
2481 
2482 		/*
2483 		 * See if the first line is less than half width, and the second line
2484 		 * is more than three quarters width.  If it is, set newline as
2485 		 * significant for the first paragraph line.
2486 		 */
2487 		if (first_line->real_length < screen_width / 2
2488 		        && next_line->real_length > screen_width * 3 / 4) {
2489 			gagt_set_font_hint_proportional_newline(first_line);
2490 		}
2491 	}
2492 
2493 	/*
2494 	 * Phase 8 -- special case paragraphs of only short lines.
2495 	 *
2496 	 * Make a special case out of paragraphs where all lines are short.  This
2497 	 * catches elements like indented addresses.
2498 	 */
2499 	if (gagt_get_next_paragraph_line(first_line)) {
2500 		int all_short;
2501 
2502 		all_short = TRUE;
2503 		for (line = first_line;
2504 		        line; line = gagt_get_next_paragraph_line(line)) {
2505 			/* Clear flag if this line isn't 'short'. */
2506 			if (line->real_length >= screen_width / 2) {
2507 				all_short = FALSE;
2508 				break;
2509 			}
2510 		}
2511 
2512 		/*
2513 		 * If all lines were short, mark the complete paragraph as having
2514 		 * significant newlines.
2515 		 */
2516 		if (all_short) {
2517 			for (line = first_line;
2518 			        line; line = gagt_get_next_paragraph_line(line)) {
2519 				gagt_set_font_hint_proportional_newline(line);
2520 			}
2521 		}
2522 	}
2523 }
2524 
2525 
2526 /*
2527  * gagt_assign_font_hints()
2528  *
2529  *
2530  * Sets a font hint for each line of each page buffer paragraph that is not
2531  * a special paragraph.
2532  */
gagt_assign_font_hints()2533 static void gagt_assign_font_hints() {
2534 	gagt_paragraphref_t paragraph;
2535 
2536 	for (paragraph = gagt_get_first_paragraph();
2537 	        paragraph; paragraph = gagt_get_next_paragraph(paragraph)) {
2538 		if (!paragraph->special)
2539 			gagt_assign_paragraph_font_hints(paragraph);
2540 	}
2541 }
2542 
2543 
2544 /*---------------------------------------------------------------------*/
2545 /*  Glk port special paragraph functions and data                      */
2546 /*---------------------------------------------------------------------*/
2547 
2548 /*
2549  * It's helpful to handle some AGiliTy interpreter output specially, to im-
2550  * prove the look of the text where Glk fonts and styles are available.  We
2551  * build a table of paragraphs the interpreter can come out with, and the
2552  * replacement text we'll use when we see this paragraph.  Note that matches
2553  * are made after factoring out indentation, and replacement lines do not
2554  * automatically print with a newline.  All clear, then?  Here's the table
2555  * entry definition.
2556  */
2557 enum { GAGT_SPECIAL_MATCH_MAX = 5 };
2558 
2559 typedef const struct gagt_special_s {
2560 	const int line_count;
2561 	const char *const compare[GAGT_SPECIAL_MATCH_MAX + 1];
2562 	const char *const replace;
2563 } gagt_special_t;
2564 
2565 /*
2566  * Table of special AGiliTy interpreter strings and paragraphs -- where one
2567  * appears in game output, we'll print out its replacement instead.  Be
2568  * warned; these strings are VERY specific to AGiliTy 1.1.1.1, and are extre-
2569  * mely likely to change with any future interpreter releases.  They also
2570  * omit initializers with abandon, expecting the compiler to default these
2571  * to NULL/zero.  Replacement strings embed style encoding as |x, where x is
2572  * E(mphasized), S(ubheader), or N(ormal) for convenience.
2573  */
2574 static gagt_special_t GAGT_SPECIALS[] = {
2575 
2576 	/* Initial screen AGT game type line. */
2577 	{
2578 		1,
2579 		{"[Created with Malmberg and Welch's Adventure Game Toolkit]"},
2580 		"|ECreated with Malmberg and Welch's Adventure Game Toolkit|N\n"
2581 	},
2582 
2583 	/* Normal version of initial interpreter information block. */
2584 	{
2585 		4,
2586 		{
2587 			"This game is being executed by",
2588 			"AGiliTy: The (Mostly) Universal AGT Interpreter  version 1.1.1.1",
2589 			"Copyright (C) 1996-99,2001 by Robert Masenten",
2590 			"Glk version"
2591 		},
2592 		"This game is being executed by:\n\n"
2593 		"    |SAGiliTy, The (Mostly) Universal AGT Interpreter, Version 1.1.1.1|N\n"
2594 		"    |ECopyright (C) 1996-1999,2001 by Robert Masenten|N\n"
2595 		"    |EGlk version|N\n"
2596 	},
2597 
2598 	/* AGiliTy "information" screen header block. */
2599 	{
2600 		5,
2601 		{
2602 			"AGiliTy",
2603 			"The (Mostly) Universal AGT Interpreter, version 1.1.1.1",
2604 			"Copyright (C) 1996-1999,2001 by Robert Masenten",
2605 			"[Glk version]",
2606 			"-----------------------------------------------------------"
2607 		},
2608 		"|SAGiliTy, The (Mostly) Universal AGT Interpreter, Version 1.1.1.1|N\n"
2609 		"|ECopyright (C) 1996-1999,2001 by Robert Masenten|N\n"
2610 		"|EGlk version|N\n"
2611 	},
2612 
2613 	/* "HIT ANY KEY" message, usually displayed after a game's introduction. */
2614 	{
2615 		1,
2616 		{"--- HIT ANY KEY ---"},
2617 		"|E[Press any key...]|N"
2618 	},
2619 
2620 	/* Alternative, shrunken version of initial interpreter information block. */
2621 	{
2622 		2,
2623 		{
2624 			"Being run by AGiliTy  version 1.1.1.1, Copyright (C) 1996-99,2001"
2625 			" Robert Masenten",
2626 			"Glk version"
2627 		},
2628 		"This game is being executed by:\n\n"
2629 		"    |SAGiliTy, The (Mostly) Universal AGT Interpreter, Version 1.1.1.1|N\n"
2630 		"    |ECopyright (C) 1996-1999,2001 by Robert Masenten|N\n"
2631 		"    |EGlk version|N\n"
2632 	},
2633 
2634 	/* Alternative, minimal version of initial interpreter information block. */
2635 	{
2636 		1,
2637 		{
2638 			"Being run by AGiliTy  version 1.1.1.1, Copyright (C) 1996-99,2001"
2639 			" Robert Masenten"
2640 		},
2641 		"This game is being executed by:\n\n"
2642 		"    |SAGiliTy, The (Mostly) Universal AGT Interpreter, Version 1.1.1.1|N\n"
2643 		"    |ECopyright (C) 1996-1999,2001 by Robert Masenten|N\n"
2644 		"    |EGlk version|N\n"
2645 	},
2646 
2647 	/* Lengthy version of the "Created with..." message. */
2648 	{
2649 		2,
2650 		{
2651 			"This game was created with Malmberg and Welch's Adventure Game Toolkit;"
2652 			" it is",
2653 			"being executed by"
2654 		},
2655 		"|ECreated with Malmberg and Welch's Adventure Game Toolkit|N\n"
2656 	},
2657 
2658 	/* Three-line version of initial interpreter information block. */
2659 	{
2660 		3,
2661 		{
2662 			"AGiliTy: The (Mostly) Universal AGT Interpreter  version 1.1.1.1",
2663 			"Copyright (C) 1996-99,2001 by Robert Masenten",
2664 			"Glk version"
2665 		},
2666 		"This game is being executed by:\n\n"
2667 		"    |SAGiliTy, The (Mostly) Universal AGT Interpreter, Version 1.1.1.1|N\n"
2668 		"    |ECopyright (C) 1996-1999,2001 by Robert Masenten|N\n"
2669 		"    |EGlk version|N\n"
2670 	},
2671 
2672 	/*
2673 	 * Assorted special verb output messages, with the extra icky quality that
2674 	 * we have to spot messages that wrap because we forced screen_width to 80.
2675 	 */
2676 	{
2677 		2,
2678 		{
2679 			"[Now in BRIEF mode (room descriptions will only be printed"
2680 			" when they are entered",
2681 			"the first time)]"
2682 		},
2683 		"|E[Now in BRIEF mode: Room descriptions will only be printed"
2684 		" when rooms are entered for the first time.]|N\n"
2685 	},
2686 
2687 	{
2688 		2,
2689 		{
2690 			"[Now in VERBOSE mode (room descriptions will be printed"
2691 			" every time you enter a",
2692 			"room)]"
2693 		},
2694 		"|E[Now in VERBOSE mode: Room descriptions will be printed"
2695 		" every time you enter a room.]|N\n"
2696 	},
2697 
2698 	{
2699 		1,
2700 		{"[LISTEXIT mode on: room exits will be listed.]"},
2701 		"|E[LISTEXIT mode on: Room exits will be listed.]|N\n"
2702 	},
2703 
2704 	{
2705 		1,
2706 		{"[LISTEXIT mode off: room exits will not be listed.]"},
2707 		"|E[LISTEXIT mode off: Room exits will not be listed.]|N\n"
2708 	},
2709 
2710 	/* End of table sentinel entry.  Do not delete. */
2711 	{0, {NULL}, NULL}
2712 };
2713 
2714 
2715 /*
2716  * gagt_compare_special_line()
2717  * gagt_compare_special_paragraph()
2718  *
2719  * Helpers for gagt_find_equivalent_special().  Compare line data case-
2720  * insensitively, taking care to use lengths rather than relying on line
2721  * buffer data being NUL terminated (which it's not); and iterate a complete
2722  * special paragraph comparison.
2723  */
gagt_compare_special_line(const char * compare,const gagt_lineref_t line)2724 static int gagt_compare_special_line(const char *compare, const gagt_lineref_t line) {
2725 	/*
2726 	 * Return true if the lengths match, and the real line data (excluding
2727 	 * indent and outdent) also matches, ignoring case.
2728 	 */
2729 	return (int)strlen(compare) == line->real_length
2730 	       && gagt_strncasecmp(compare,
2731 	                           (const char *)line->buffer.data + line->indent,
2732 	                           line->real_length) == 0;
2733 }
2734 
gagt_compare_special_paragraph(const gagt_specialref_t special,const gagt_paragraphref_t paragraph)2735 static int gagt_compare_special_paragraph(const gagt_specialref_t special,
2736 		const gagt_paragraphref_t paragraph) {
2737 	/* If the line counts match, compare line by line. */
2738 	if (special->line_count == gagt_get_paragraph_line_count(paragraph)) {
2739 		gagt_lineref_t line;
2740 		int index, is_match;
2741 
2742 		is_match = TRUE;
2743 		for (index = 0, line = gagt_get_first_paragraph_line(paragraph);
2744 		        index < special->line_count && line;
2745 		        index++, line = gagt_get_next_paragraph_line(line)) {
2746 			if (!gagt_compare_special_line(special->compare[index], line)) {
2747 				is_match = FALSE;
2748 				break;
2749 			}
2750 		}
2751 
2752 		return is_match;
2753 	}
2754 
2755 	/* Line count mismatch; return FALSE. */
2756 	return FALSE;
2757 }
2758 
2759 
2760 /*
2761  * gagt_find_equivalent_special()
2762  *
2763  * Given a paragraph, see if it matches any of the special ones set up in
2764  * our array.  Returns the special, or NULL if no match.
2765  */
gagt_find_equivalent_special(gagt_paragraphref_t paragraph)2766 static gagt_specialref_t gagt_find_equivalent_special(gagt_paragraphref_t paragraph) {
2767 	gagt_specialref_t special, match;
2768 
2769 	/* Check each special paragraph entry for a match against this paragraph. */
2770 	match = NULL;
2771 	for (special = GAGT_SPECIALS; special->replace; special++) {
2772 		if (gagt_compare_special_paragraph(special, paragraph)) {
2773 			match = special;
2774 			break;
2775 		}
2776 	}
2777 
2778 	return match;
2779 }
2780 
2781 
2782 /*
2783  * gagt_mark_specials()
2784  *
2785  * Search for and mark any lines that match special paragraphs.
2786  */
gagt_mark_specials()2787 static void gagt_mark_specials() {
2788 	static int is_verified = FALSE;
2789 
2790 	/*
2791 	 * Verify special paragraphs table contents.  This checks that each entry
2792 	 * ends with a NULL comparison, has a replacement, and that the line count
2793 	 * matches.
2794 	 */
2795 	if (!is_verified) {
2796 		gagt_specialref_t special;
2797 
2798 		for (special = GAGT_SPECIALS; special->replace; special++) {
2799 			int line_count, index;
2800 
2801 			line_count = 0;
2802 			for (index = 0; special->compare[index]; index++)
2803 				line_count++;
2804 
2805 			assert(special->line_count == line_count);
2806 			assert(special->replace);
2807 			assert(!special->compare[GAGT_SPECIAL_MATCH_MAX]);
2808 		}
2809 
2810 		is_verified = TRUE;
2811 	}
2812 
2813 	/*
2814 	 * Search all paragraphs for special matches, if enabled.  When a special
2815 	 * match is found, mark the paragraph with a pointer to the matching entry.
2816 	 */
2817 	if (g_vm->gagt_replacement_enabled) {
2818 		gagt_paragraphref_t paragraph;
2819 
2820 		for (paragraph = gagt_get_first_paragraph();
2821 		        paragraph; paragraph = gagt_get_next_paragraph(paragraph)) {
2822 			paragraph->special = gagt_find_equivalent_special(paragraph);
2823 		}
2824 	}
2825 }
2826 
2827 
2828 /*
2829  * gagt_display_special()
2830  *
2831  * Display the replacement text for the specified special table entry.  The
2832  * current Glk style in force is passed in; we return the Glk style in force
2833  * after we've done.
2834  */
gagt_display_special(const gagt_specialref_t special,glui32 current_style)2835 static glui32 gagt_display_special(const gagt_specialref_t special, glui32 current_style) {
2836 	glui32 set_style;
2837 	int index, marker, length;
2838 	const char *string;
2839 	assert(special);
2840 
2841 	/* Extract replacement string and length. */
2842 	string = special->replace;
2843 	assert(string);
2844 	length = strlen(string);
2845 
2846 	set_style = current_style;
2847 
2848 	/*
2849 	 * Iterate each character in replacement string, looking for style escapes,
2850 	 * and flushing delayed output when one is found.
2851 	 */
2852 	marker = 0;
2853 	for (index = 0; index < length; index++) {
2854 		if (string[index] == '|') {
2855 			glui32 style;
2856 
2857 			/* Flush delayed output accumulated so far, excluding escape. */
2858 			g_vm->glk_put_buffer(string + marker, index - marker);
2859 			marker = index + 2;
2860 
2861 			/* Determine any new text style. */
2862 			style = set_style;
2863 			switch (string[++index]) {
2864 			case 'E':
2865 				style = style_Emphasized;
2866 				break;
2867 
2868 			case 'S':
2869 				style = style_Subheader;
2870 				break;
2871 
2872 			case 'N':
2873 				style = style_Normal;
2874 				break;
2875 
2876 			default:
2877 				gagt_fatal("GLK: Invalid replacement style escape");
2878 				gagt_exit();
2879 			}
2880 
2881 			/* If style changed, update Glk's style setting. */
2882 			if (style != set_style) {
2883 				g_vm->glk_set_style(style);
2884 				set_style = style;
2885 			}
2886 		}
2887 	}
2888 
2889 	/* Output any remaining delayed characters. */
2890 	if (marker < length)
2891 		g_vm->glk_put_buffer(string + marker, length - marker);
2892 
2893 	return set_style;
2894 }
2895 
2896 
2897 /*---------------------------------------------------------------------*/
2898 /*  Glk port output functions                                          */
2899 /*---------------------------------------------------------------------*/
2900 
2901 /*
2902  * Flag for if the user entered "help" as their last input, or if hints have
2903  * been silenced as a result of already using a Glk command.
2904  */
2905 static int gagt_help_requested = FALSE,
2906 		   gagt_help_hints_silenced = FALSE;
2907 
2908 /*
2909  * gagt_display_register_help_request()
2910  * gagt_display_silence_help_hints()
2911  * gagt_display_provide_help_hint()
2912  *
2913  * Register a request for help, and print a note of how to get Glk command
2914  * help from the interpreter unless silenced.
2915  */
gagt_display_register_help_request()2916 static void gagt_display_register_help_request() {
2917 	gagt_help_requested = TRUE;
2918 }
2919 
gagt_display_silence_help_hints()2920 static void gagt_display_silence_help_hints() {
2921 	gagt_help_hints_silenced = TRUE;
2922 }
2923 
gagt_display_provide_help_hint(glui32 current_style)2924 static glui32 gagt_display_provide_help_hint(glui32 current_style) {
2925 	if (gagt_help_requested && !gagt_help_hints_silenced) {
2926 		g_vm->glk_set_style(style_Emphasized);
2927 		g_vm->glk_put_string("[Try 'glk help' for help on special interpreter"
2928 		                     " commands]\n");
2929 
2930 		gagt_help_requested = FALSE;
2931 		return style_Emphasized;
2932 	}
2933 
2934 	return current_style;
2935 }
2936 
2937 
2938 /*
2939  * gagt_display_text_element()
2940  *
2941  * Display an element of a buffer string using matching packed attributes.
2942  * The currently set Glk style is supplied, and the function returns the
2943  * new currently set Glk style.
2944  *
2945  * The function handles a flag to coerce fixed width font.
2946  */
gagt_display_text_element(const char * string,const unsigned char * attributes,int length,glui32 current_style,int fixed_width)2947 static glui32 gagt_display_text_element(const char *string, const unsigned char *attributes,
2948 										int length, glui32 current_style, int fixed_width) {
2949 	int marker, index;
2950 	glui32 set_style;
2951 	assert(g_vm->glk_stream_get_current());
2952 
2953 	set_style = current_style;
2954 
2955 	/*
2956 	 * Iterate each character in the line range.  We actually delay output
2957 	 * until we see a change in style; that way, we can send a buffer of
2958 	 * characters to Glk, rather than sending them just one at a time.
2959 	 */
2960 	marker = 0;
2961 	for (index = 0; index < length; index++) {
2962 		gagt_attrset_t attribute_set;
2963 		glui32 style;
2964 		assert(attributes && string);
2965 
2966 		/*
2967 		 * Unpack the AGT font attributes for this character, and add fixed
2968 		 * width font coercion.
2969 		 */
2970 		gagt_unpack_attributes(attributes[index], &attribute_set);
2971 		attribute_set.fixed |= fixed_width;
2972 
2973 		/*
2974 		 * Decide on any applicable new Glk text styling.  If it's different
2975 		 * to the current style, output the delayed characters, and update
2976 		 * Glk's style setting.
2977 		 */
2978 		style = gagt_select_style(&attribute_set);
2979 		if (style != set_style) {
2980 			g_vm->glk_put_buffer(string + marker, index - marker);
2981 			marker = index;
2982 
2983 			g_vm->glk_set_style(style);
2984 			set_style = style;
2985 		}
2986 	}
2987 
2988 	/* Output any remaining delayed characters. */
2989 	if (marker < length)
2990 		g_vm->glk_put_buffer(string + marker, length - marker);
2991 
2992 	return set_style;
2993 }
2994 
2995 
2996 /*
2997  * gagt_display_line()
2998  *
2999  * Display a page buffer line, starting in the current Glk style, and
3000  * returning the new current Glk style.
3001  *
3002  * The function takes additional flags to force fixed width font, skip over
3003  * indentation and trailing line whitespace, and trim hyphens (if skipping
3004  * trailing whitespace).
3005  */
gagt_display_line(const gagt_lineref_t line,glui32 current_style,int fixed_width,int skip_indent,int skip_outdent,int trim_hyphen)3006 static glui32 gagt_display_line(const gagt_lineref_t line, glui32 current_style,
3007 								int fixed_width, int skip_indent, int skip_outdent,
3008 								int trim_hyphen) {
3009 	int start, length;
3010 	glui32 set_style;
3011 
3012 	/*
3013 	 * Check the skip indent flag to find the first character to display, and
3014 	 * the count of characters to display.
3015 	 */
3016 	start = 0;
3017 	length = line->buffer.length;
3018 	if (skip_indent) {
3019 		start += line->indent;
3020 		length -= line->indent;
3021 	}
3022 
3023 	/* Adjust length for skipping outdent and trimming hyphens. */
3024 	if (skip_outdent) {
3025 		length -= line->outdent;
3026 		if (trim_hyphen && line->is_hyphenated)
3027 			length--;
3028 	}
3029 
3030 	/* Display this line segment. */
3031 	set_style = gagt_display_text_element((const char *)line->buffer.data + start,
3032 	                                      line->buffer.attributes + start,
3033 	                                      length, current_style, fixed_width);
3034 
3035 	return set_style;
3036 }
3037 
3038 
3039 /*
3040  * gagt_display_hinted_line()
3041  *
3042  * Display a page buffer line, starting in the current Glk style, and
3043  * returning the new current Glk style.  The function uses the font hints
3044  * from the line, and receives the font hint of the prior line.
3045  */
gagt_display_hinted_line(const gagt_lineref_t line,glui32 current_style,gagt_font_hint_t prior_hint)3046 static glui32 gagt_display_hinted_line(const gagt_lineref_t line, glui32 current_style,
3047 									   gagt_font_hint_t prior_hint) {
3048 	glui32 style;
3049 
3050 	style = current_style;
3051 	switch (line->font_hint) {
3052 	case HINT_FIXED_WIDTH:
3053 		/* Force fixed width font on the line. */
3054 		style = gagt_display_line(line, style, TRUE, FALSE, FALSE, FALSE);
3055 
3056 		g_vm->glk_put_char('\n');
3057 		break;
3058 
3059 	case HINT_PROPORTIONAL:
3060 		/*
3061 		 * Permit proportional font, and suppress outdent.  Suppress indent
3062 		 * too if this line follows a line that suppressed newline, or is the
3063 		 * first line in the paragraph.  For all cases, trim the hyphen from
3064 		 * hyphenated lines.
3065 		 */
3066 		if (prior_hint == HINT_PROPORTIONAL || prior_hint == HINT_NONE)
3067 			style = gagt_display_line(line, style, FALSE, TRUE, TRUE, TRUE);
3068 		else
3069 			style = gagt_display_line(line, style, FALSE, FALSE, TRUE, TRUE);
3070 
3071 		/*
3072 		 * Where the line is not hyphenated, output a space in place of newline.
3073 		 * This lets paragraph text to flow to the full display width.
3074 		 */
3075 		if (!line->is_hyphenated)
3076 			g_vm->glk_put_char(' ');
3077 		break;
3078 
3079 	case HINT_PROPORTIONAL_NEWLINE:
3080 	case HINT_PROPORTIONAL_NEWLINE_STANDOUT:
3081 		/*
3082 		 * As above, permit proportional font, suppress outdent, and suppress
3083 		 * indent too under certain conditions; in this case, only when the
3084 		 * prior line suppressed newline.
3085 		 */
3086 		if (prior_hint == HINT_PROPORTIONAL)
3087 			style = gagt_display_line(line, style, FALSE, TRUE, TRUE, FALSE);
3088 		else
3089 			style = gagt_display_line(line, style, FALSE, FALSE, TRUE, FALSE);
3090 
3091 		g_vm->glk_put_char('\n');
3092 		break;
3093 
3094 	case HINT_NONE:
3095 		gagt_fatal("GLK: Page buffer line with no font hint");
3096 		gagt_exit();
3097 		break;
3098 
3099 	default:
3100 		gagt_fatal("GLK: Invalid font hint encountered");
3101 		gagt_exit();
3102 		break;
3103 	}
3104 
3105 	return style;
3106 }
3107 
3108 
3109 /*
3110  * gagt_display_auto()
3111  *
3112  * Display buffered output text to the Glk main window using a bunch of
3113  * occasionally rather dodgy heuristics to try to automatically set a suitable
3114  * font for the way the text is structured, while replacing special paragraphs
3115  * with altered text.
3116  */
gagt_display_auto()3117 static void gagt_display_auto() {
3118 	gagt_paragraphref_t paragraph;
3119 	glui32 style;
3120 
3121 	style = style_Normal;
3122 	g_vm->glk_set_style(style);
3123 
3124 	/* Handle each paragraph. */
3125 	for (paragraph = gagt_get_first_paragraph();
3126 	        paragraph; paragraph = gagt_get_next_paragraph(paragraph)) {
3127 		/* If a special paragraph, output replacement text instead. */
3128 		if (paragraph->special) {
3129 			style = gagt_display_special(paragraph->special, style);
3130 			g_vm->glk_put_char('\n');
3131 		} else {
3132 			gagt_lineref_t line;
3133 			gagt_font_hint_t prior_hint;
3134 
3135 			/* Get the first line of the paragraph. */
3136 			line = gagt_get_first_paragraph_line(paragraph);
3137 
3138 			/*
3139 			 * Output a blank line where the first line of the first paragraph
3140 			 * is standout; this sets it apart from the prompt.
3141 			 */
3142 			if (paragraph == gagt_get_first_paragraph()
3143 			        && line == gagt_get_first_paragraph_line(paragraph)) {
3144 				if (line->font_hint == HINT_PROPORTIONAL_NEWLINE_STANDOUT)
3145 					g_vm->glk_put_char('\n');
3146 			}
3147 
3148 			/* Handle each line of the paragraph. */
3149 			prior_hint = HINT_NONE;
3150 			for (; line; line = gagt_get_next_paragraph_line(line)) {
3151 				/*
3152 				 * Print this line according to its font hint, noting any change
3153 				 * of style and the line's font hint for use next iteration as
3154 				 * the prior hint.
3155 				 */
3156 				style = gagt_display_hinted_line(line, style, prior_hint);
3157 				prior_hint = line->font_hint;
3158 			}
3159 
3160 			/* Output the newline for the end of the paragraph. */
3161 			g_vm->glk_put_char('\n');
3162 		}
3163 	}
3164 
3165 	/* If no paragraphs at all, but a current buffer, output a newline. */
3166 	if (!gagt_get_first_paragraph() && gagt_current_buffer.length > 0)
3167 		g_vm->glk_put_char('\n');
3168 
3169 	/* Output any help hint and unterminated line from the line buffer. */
3170 	style = gagt_display_provide_help_hint(style);
3171 	style = gagt_display_text_element((const char *)gagt_current_buffer.data,
3172 	                                  gagt_current_buffer.attributes,
3173 	                                  gagt_current_buffer.length, style, FALSE);
3174 }
3175 
3176 
3177 /*
3178  * gagt_display_manual()
3179  *
3180  * Display buffered output text in the Glk main window, with either a fixed
3181  * width or a proportional font.
3182  */
gagt_display_manual(int fixed_width)3183 static void gagt_display_manual(int fixed_width) {
3184 	gagt_lineref_t line;
3185 	glui32 style;
3186 
3187 	style = style_Normal;
3188 	g_vm->glk_set_style(style);
3189 
3190 	for (line = gagt_get_first_page_line();
3191 	        line; line = gagt_get_next_page_line(line)) {
3192 		gagt_paragraphref_t paragraph;
3193 
3194 		paragraph = line->paragraph;
3195 
3196 		/*
3197 		 * If this is a special paragraph, display the replacement text on
3198 		 * its first line and ignore remaining special lines.  Otherwise,
3199 		 * display the page buffer line using either fixed or proportional
3200 		 * font, as requested.
3201 		 */
3202 		if (paragraph && paragraph->special) {
3203 			if (gagt_get_first_paragraph_line(paragraph) == line)
3204 				style = gagt_display_special(paragraph->special, style);
3205 		} else {
3206 			style = gagt_display_line(line, style, fixed_width,
3207 			                          FALSE, FALSE, FALSE);
3208 			g_vm->glk_put_char('\n');
3209 		}
3210 	}
3211 
3212 	/* Output any help hint and unterminated line from the line buffer. */
3213 	style = gagt_display_provide_help_hint(style);
3214 	style = gagt_display_text_element((const char *)gagt_current_buffer.data,
3215 	                                  gagt_current_buffer.attributes,
3216 	                                  gagt_current_buffer.length,
3217 	                                  style, fixed_width);
3218 }
3219 
3220 
3221 /*
3222  * gagt_display_debug()
3223  *
3224  * Display the analyzed page buffer in a form that shows all of its gory
3225  * detail.
3226  */
gagt_display_debug()3227 static void gagt_display_debug() {
3228 	gagt_lineref_t line;
3229 	char buffer[256];
3230 
3231 	g_vm->glk_set_style(style_Preformatted);
3232 	for (line = gagt_get_first_page_line();
3233 	        line; line = gagt_get_next_page_line(line)) {
3234 		gagt_paragraphref_t paragraph;
3235 
3236 		paragraph = line->paragraph;
3237 		sprintf(buffer,
3238 		        "%2d:%2d->%2ld A=%-3d L=%-2d I=%-2d O=%-2d R=%-2d %c%c| ",
3239 		        paragraph ? paragraph->id + 1 : 0,
3240 		        paragraph ? paragraph->line_count : 0,
3241 		        paragraph && paragraph->special
3242 		        ? paragraph->special - GAGT_SPECIALS + 1 : 0,
3243 		        line->buffer.allocation, line->buffer.length,
3244 		        line->indent, line->outdent,
3245 		        line->real_length,
3246 		        line->is_hyphenated ? 'h' : '_',
3247 		        line->is_blank ? 'b' :
3248 		        line->font_hint == HINT_PROPORTIONAL ? 'P' :
3249 		        line->font_hint == HINT_PROPORTIONAL_NEWLINE ? 'N' :
3250 		        line->font_hint == HINT_PROPORTIONAL_NEWLINE_STANDOUT ? 'S' :
3251 		        line->font_hint == HINT_FIXED_WIDTH ? 'F' : '_');
3252 		g_vm->glk_put_string(buffer);
3253 
3254 		g_vm->glk_put_buffer((const char *)line->buffer.data, line->buffer.length);
3255 		g_vm->glk_put_char('\n');
3256 	}
3257 
3258 	if (gagt_current_buffer.length > 0) {
3259 		sprintf(buffer,
3260 		        "__,__->__ A=%-3d L=%-2d I=__ O=__ R=__ %s| ",
3261 		        gagt_current_buffer.allocation, gagt_current_buffer.length,
3262 		        gagt_help_requested ? "HR" : "__");
3263 		g_vm->glk_put_string(buffer);
3264 
3265 		g_vm->glk_put_buffer((const char *)gagt_current_buffer.data, gagt_current_buffer.length);
3266 	}
3267 
3268 	gagt_help_requested = FALSE;
3269 }
3270 
3271 
3272 /*
3273  * gagt_output_flush()
3274  *
3275  * Flush any buffered output text to the Glk main window, and clear the
3276  * buffer ready for new output text.  The function concerns itself with
3277  * both the page buffer and any unterminated line in the line buffer.
3278  */
gagt_output_flush()3279 static void gagt_output_flush() {
3280 	/*
3281 	 * Run the analysis of page buffer contents.  This will fill in the
3282 	 * paragraph and font hints fields, any any applicable special pointer,
3283 	 * for every line held in the buffer.
3284 	 */
3285 	gagt_paragraph_page();
3286 	gagt_mark_specials();
3287 	gagt_assign_font_hints();
3288 
3289 	/*
3290 	 * Select the appropriate display routine to use, and call it.  The display
3291 	 * routines present somewhat different output, and are responsible for
3292 	 * displaying both the page buffer _and_ any buffered current line text.
3293 	 */
3294 	switch (g_vm->gagt_font_mode) {
3295 	case FONT_AUTOMATIC:
3296 		gagt_display_auto();
3297 		break;
3298 
3299 	case FONT_FIXED_WIDTH:
3300 		gagt_display_manual(TRUE);
3301 		break;
3302 
3303 	case FONT_PROPORTIONAL:
3304 		gagt_display_manual(FALSE);
3305 		break;
3306 
3307 	case FONT_DEBUG:
3308 		gagt_display_debug();
3309 		break;
3310 
3311 	default:
3312 		gagt_fatal("GLK: Invalid font mode encountered");
3313 		gagt_exit();
3314 	}
3315 
3316 	/* Empty the buffer, ready for new game strings. */
3317 	gagt_paragraphs_delete();
3318 	gagt_output_delete();
3319 }
3320 
3321 
3322 /*
3323  * agt_clrscr()
3324  *
3325  * Clear the main playing area window.  Although there may be little point
3326  * in flushing (rather than emptying) the buffers, nevertheless that is
3327  * what we do.
3328  */
agt_clrscr()3329 void agt_clrscr() {
3330 	if (!BATCH_MODE) {
3331 		/* Update the apparent (virtual) window x position. */
3332 		curr_x = 0;
3333 
3334 		/* Flush any pending buffered output, and clear the main window. */
3335 		gagt_output_flush();
3336 		g_vm->glk_window_clear(g_vm->gagt_main_window);
3337 
3338 		/* Add a series of newlines to any script file. */
3339 		if (script_on)
3340 			textputs(scriptfile, "\n\n\n\n");
3341 
3342 		gagt_debug("agt_clrscr", "");
3343 	}
3344 }
3345 
3346 
3347 /*
3348  * gagt_styled_string()
3349  * gagt_styled_char()
3350  * gagt_standout_string()
3351  * gagt_standout_char()
3352  * gagt_normal_string()
3353  * gagt_normal_char()
3354  * gagt_header_string()
3355  *
3356  * Convenience functions to print strings in assorted styles.  A standout
3357  * string is one that hints that it's from the interpreter, not the game.
3358  */
gagt_styled_string(glui32 style,const char * message)3359 static void gagt_styled_string(glui32 style, const char *message) {
3360 	assert(message);
3361 
3362 	g_vm->glk_set_style(style);
3363 	g_vm->glk_put_string(message);
3364 	g_vm->glk_set_style(style_Normal);
3365 }
3366 
gagt_styled_char(glui32 style,char c)3367 static void gagt_styled_char(glui32 style, char c) {
3368 	char buffer[2];
3369 
3370 	buffer[0] = c;
3371 	buffer[1] = '\0';
3372 	gagt_styled_string(style, buffer);
3373 }
3374 
gagt_standout_string(const char * message)3375 static void gagt_standout_string(const char *message) {
3376 	gagt_styled_string(style_Emphasized, message);
3377 }
3378 
gagt_standout_char(char c)3379 static void gagt_standout_char(char c) {
3380 	gagt_styled_char(style_Emphasized, c);
3381 }
3382 
gagt_normal_string(const char * message)3383 static void gagt_normal_string(const char *message) {
3384 	gagt_styled_string(style_Normal, message);
3385 }
3386 
gagt_normal_char(char c)3387 static void gagt_normal_char(char c) {
3388 	gagt_styled_char(style_Normal, c);
3389 }
3390 
gagt_header_string(const char * message)3391 static void gagt_header_string(const char *message) {
3392 	gagt_styled_string(style_Header, message);
3393 }
3394 
3395 
3396 /*---------------------------------------------------------------------*/
3397 /*  Glk port delay functions                                           */
3398 /*---------------------------------------------------------------------*/
3399 
3400 /* Number of milliseconds in a second (traditionally, 1000). */
3401 static const int GAGT_MS_PER_SEC = 1000;
3402 
3403 /*
3404  * Number of milliseconds to timeout.  Because of jitter in the way Glk
3405  * generates timeouts, it's worthwhile implementing a delay using a number
3406  * of shorter timeouts.  This minimizes inaccuracies in the actual delay.
3407  */
3408 static const glui32 GAGT_DELAY_TIMEOUT = 50;
3409 
3410 /* The character key that can be pressed to cancel, and suspend, delays. */
3411 static const char GAGT_DELAY_SUSPEND = ' ';
3412 
3413 /*
3414  * Flag to temporarily turn off all delays.  This is set when the user
3415  * cancels a delay with a keypress, and remains set until the next time
3416  * that AGiliTy requests user input.  This way, games that call agt_delay()
3417  * sequentially don't require multiple keypresses to jump out of delay
3418  * sections.
3419  */
3420 static int gagt_delays_suspended = FALSE;
3421 
3422 
3423 /*
3424  * agt_delay()
3425  *
3426  * Delay for the specified number of seconds.  The delay can be canceled
3427  * by a user keypress.
3428  */
agt_delay(int seconds)3429 void agt_delay(int seconds) {
3430 	glui32 milliseconds, delayed;
3431 	int delay_completed;
3432 
3433 	/* Suppress delay if in fast replay or batch mode. */
3434 	if (fast_replay || BATCH_MODE)
3435 		return;
3436 
3437 	/*
3438 	 * Do nothing if Glk doesn't have timers, if the delay state is set to
3439 	 * ignore delays, if a zero or negative delay was specified, or if delays
3440 	 * are currently temporarily suspended.
3441 	 */
3442 	if (!g_vm->glk_gestalt(gestalt_Timer, 0)
3443 	        || g_vm->gagt_delay_mode == DELAY_OFF
3444 	        || seconds <= 0 || gagt_delays_suspended)
3445 		return;
3446 
3447 	/* Flush any pending buffered output, and refresh status to show waiting. */
3448 	gagt_output_flush();
3449 	gagt_status_in_delay(TRUE);
3450 
3451 	/* Calculate the number of milliseconds to delay. */
3452 	milliseconds = (seconds * GAGT_MS_PER_SEC)
3453 	               / (g_vm->gagt_delay_mode == DELAY_SHORT ? 2 : 1);
3454 
3455 	/* Request timer events, and let a keypress cancel the delay. */
3456 	g_vm->glk_request_char_event(g_vm->gagt_main_window);
3457 	g_vm->glk_request_timer_events(GAGT_DELAY_TIMEOUT);
3458 
3459 	/*
3460 	 * Implement the delay using a sequence of shorter Glk timeouts, with an
3461 	 * option to cancel the delay with a keypress.
3462 	 */
3463 	delay_completed = TRUE;
3464 	for (delayed = 0; delayed < milliseconds; delayed += GAGT_DELAY_TIMEOUT) {
3465 		event_t event;
3466 
3467 		/* Wait for the next timeout, or a character. */
3468 		gagt_event_wait_2(evtype_CharInput, evtype_Timer, &event);
3469 		if (event.type == evtype_CharInput) {
3470 			/*
3471 			 * If suspend requested, stop the delay, and set the delay
3472 			 * suspension flag, and a note that the delay loop didn't complete.
3473 			 * Otherwise, reissue the character input request.
3474 			 */
3475 			if (event.val1 == GAGT_DELAY_SUSPEND) {
3476 				gagt_delays_suspended = TRUE;
3477 				delay_completed = FALSE;
3478 				break;
3479 			} else
3480 				g_vm->glk_request_char_event(g_vm->gagt_main_window);
3481 		}
3482 	}
3483 
3484 	/* Cancel any pending character input, and timer events. */
3485 	if (delay_completed)
3486 		g_vm->glk_cancel_char_event(g_vm->gagt_main_window);
3487 	g_vm->glk_request_timer_events(0);
3488 
3489 	/* Clear the waiting indicator. */
3490 	gagt_status_in_delay(FALSE);
3491 
3492 	gagt_debug("agt_delay", "seconds=%d [%lu mS] -> %s", seconds, milliseconds,
3493 	           delay_completed ? "completed" : "canceled");
3494 }
3495 
3496 
3497 /*
3498  * gagt_delay_resume()
3499  *
3500  * Unsuspend delays.  This function should be called by agt_input() and
3501  * agt_getkey(), to re-enable delays when the interpreter next requests
3502  * user input.
3503  */
gagt_delay_resume()3504 static void gagt_delay_resume() {
3505 	gagt_delays_suspended = FALSE;
3506 }
3507 
3508 
3509 /*---------------------------------------------------------------------*/
3510 /*  Glk port box drawing functions                                     */
3511 /*---------------------------------------------------------------------*/
3512 
3513 /* Saved details of any current box dimensions and flags. */
3514 static unsigned long gagt_box_flags = 0;
3515 static int gagt_box_busy = FALSE,
3516 		   gagt_box_width = 0,
3517 		   gagt_box_height = 0,
3518 		   gagt_box_startx = 0;
3519 
3520 
3521 /*
3522  * gagt_box_rule()
3523  * gagt_box_position()
3524  *
3525  * Draw a line at the top or bottom of a box, and position the cursor
3526  * with a box indent.
3527  */
gagt_box_rule(int width)3528 static void gagt_box_rule(int width) {
3529 	char *ruler;
3530 
3531 	/* Write a +--...--+ ruler to delimit a box. */
3532 	ruler = (char *)gagt_malloc(width + 2 + 1);
3533 	memset(ruler + 1, '-', width);
3534 	ruler[0] = ruler[width + 1] = '+';
3535 	ruler[width + 2] = '\0';
3536 	agt_puts(ruler);
3537 	free(ruler);
3538 }
3539 
gagt_box_position(int indent)3540 static void gagt_box_position(int indent) {
3541 	char *spaces;
3542 
3543 	/* Write a newline before the indent. */
3544 	agt_newline();
3545 
3546 	/* Write the indent to the start of box text. */
3547 	spaces = (char *)gagt_malloc(indent + 1);
3548 	memset(spaces, ' ', indent);
3549 	spaces[indent] = '\0';
3550 	agt_puts(spaces);
3551 	free(spaces);
3552 }
3553 
3554 
3555 /*
3556  * agt_makebox()
3557  * agt_qnewline()
3558  * agt_endbox()
3559  *
3560  * Start a box of given width, height, and with given flags.  Write a new
3561  * line in the box.  And end the box.
3562  */
agt_makebox(int width,int height,unsigned long flags)3563 void agt_makebox(int width, int height, unsigned long flags) {
3564 	assert(!gagt_box_busy);
3565 
3566 	gagt_box_busy = TRUE;
3567 	gagt_box_flags = flags;
3568 	gagt_box_width = width;
3569 	gagt_box_height = height;
3570 
3571 	/* If no centering requested, set the indent to zero. */
3572 	if (gagt_box_flags & TB_NOCENT)
3573 		gagt_box_startx = 0;
3574 	else {
3575 		int centering_width;
3576 
3577 		/*
3578 		 * Calculate the indent for centering, adding 4 characters for borders.
3579 		 * Here, since screen_width is artificial, we'll center off status_width
3580 		 * if it is less than screen width, otherwise we'll center by using
3581 		 * screen_width.  The reason for shrinking to screen_width is that if
3582 		 * we don't, we could drive curr_x to beyond screen_width with our box
3583 		 * indentations, and that confuses AGiliTy.
3584 		 */
3585 		if (status_width < screen_width)
3586 			centering_width = status_width;
3587 		else
3588 			centering_width = screen_width;
3589 		if (gagt_box_flags & TB_BORDER)
3590 			gagt_box_startx = (centering_width - gagt_box_width - 4) / 2;
3591 		else
3592 			gagt_box_startx = (centering_width - gagt_box_width) / 2;
3593 
3594 		/* If the box turns out wider than the window, abandon centering. */
3595 		if (gagt_box_startx < 0)
3596 			gagt_box_startx = 0;
3597 	}
3598 
3599 	/*
3600 	 * When in a box, we'll coerce fixed width font by setting it in the AGT
3601 	 * font attributes.  This ensures that the box displays as accurately as
3602 	 * we're able to achieve.
3603 	 */
3604 	gagt_coerce_fixed_font(TRUE);
3605 
3606 	/* Position the cursor for the box, and if bordered, write the rule. */
3607 	gagt_box_position(gagt_box_startx);
3608 	if (gagt_box_flags & TB_BORDER) {
3609 		gagt_box_rule(gagt_box_width + 2);
3610 		gagt_box_position(gagt_box_startx);
3611 		agt_puts("| ");
3612 	}
3613 
3614 	gagt_debug("agt_makebox", "width=%d, height=%d, flags=0x%lx",
3615 	           width, height, flags);
3616 }
3617 
agt_qnewline()3618 void agt_qnewline() {
3619 	assert(gagt_box_busy);
3620 
3621 	/* Write box characters for the current and next line. */
3622 	if (gagt_box_flags & TB_BORDER) {
3623 		agt_puts(" |");
3624 		gagt_box_position(gagt_box_startx);
3625 		agt_puts("| ");
3626 	} else
3627 		gagt_box_position(gagt_box_startx);
3628 
3629 	gagt_debug("agt_qnewline", "");
3630 }
3631 
agt_endbox()3632 void agt_endbox() {
3633 	assert(gagt_box_busy);
3634 
3635 	/* Finish off the current box. */
3636 	if (gagt_box_flags & TB_BORDER) {
3637 		agt_puts(" |");
3638 		gagt_box_position(gagt_box_startx);
3639 		gagt_box_rule(gagt_box_width + 2);
3640 	}
3641 	agt_newline();
3642 
3643 	/* An extra newline here improves the appearance. */
3644 	agt_newline();
3645 
3646 	/* Back to allowing proportional font output again. */
3647 	gagt_coerce_fixed_font(FALSE);
3648 
3649 	gagt_box_busy = FALSE;
3650 	gagt_box_flags = gagt_box_width = gagt_box_startx = 0;
3651 
3652 	gagt_debug("agt_endbox", "");
3653 }
3654 
3655 
3656 /*---------------------------------------------------------------------*/
3657 /*  Glk command escape functions                                       */
3658 /*---------------------------------------------------------------------*/
3659 
3660 /*
3661  * gagt_command_script()
3662  *
3663  * Turn game output scripting (logging) on and off.
3664  */
gagt_command_script(const char * argument)3665 static void gagt_command_script(const char *argument) {
3666 	assert(argument);
3667 
3668 	if (gagt_strcasecmp(argument, "on") == 0) {
3669 		frefid_t fileref;
3670 
3671 		if (g_vm->gagt_transcript_stream) {
3672 			gagt_normal_string("Glk transcript is already on.\n");
3673 			return;
3674 		}
3675 
3676 		fileref = g_vm->glk_fileref_create_by_prompt(fileusage_Transcript
3677 		          | fileusage_TextMode,
3678 		          filemode_WriteAppend, 0);
3679 		if (!fileref) {
3680 			gagt_standout_string("Glk transcript failed.\n");
3681 			return;
3682 		}
3683 
3684 		g_vm->gagt_transcript_stream = g_vm->glk_stream_open_file(fileref,
3685 		                         filemode_WriteAppend, 0);
3686 		g_vm->glk_fileref_destroy(fileref);
3687 		if (!g_vm->gagt_transcript_stream) {
3688 			gagt_standout_string("Glk transcript failed.\n");
3689 			return;
3690 		}
3691 
3692 		g_vm->glk_window_set_echo_stream(g_vm->gagt_main_window, g_vm->gagt_transcript_stream);
3693 
3694 		gagt_normal_string("Glk transcript is now on.\n");
3695 	}
3696 
3697 	else if (gagt_strcasecmp(argument, "off") == 0) {
3698 		if (!g_vm->gagt_transcript_stream) {
3699 			gagt_normal_string("Glk transcript is already off.\n");
3700 			return;
3701 		}
3702 
3703 		g_vm->glk_stream_close(g_vm->gagt_transcript_stream, NULL);
3704 		g_vm->gagt_transcript_stream = NULL;
3705 
3706 		g_vm->glk_window_set_echo_stream(g_vm->gagt_main_window, NULL);
3707 
3708 		gagt_normal_string("Glk transcript is now off.\n");
3709 	}
3710 
3711 	else if (strlen(argument) == 0) {
3712 		gagt_normal_string("Glk transcript is ");
3713 		gagt_normal_string(g_vm->gagt_transcript_stream ? "on" : "off");
3714 		gagt_normal_string(".\n");
3715 	}
3716 
3717 	else {
3718 		gagt_normal_string("Glk transcript can be ");
3719 		gagt_standout_string("on");
3720 		gagt_normal_string(", or ");
3721 		gagt_standout_string("off");
3722 		gagt_normal_string(".\n");
3723 	}
3724 }
3725 
3726 
3727 /*
3728  * gagt_command_inputlog()
3729  *
3730  * Turn game input logging on and off.
3731  */
gagt_command_inputlog(const char * argument)3732 static void gagt_command_inputlog(const char *argument) {
3733 	assert(argument);
3734 
3735 	if (gagt_strcasecmp(argument, "on") == 0) {
3736 		frefid_t fileref;
3737 
3738 		if (g_vm->gagt_inputlog_stream) {
3739 			gagt_normal_string("Glk input logging is already on.\n");
3740 			return;
3741 		}
3742 
3743 		fileref = g_vm->glk_fileref_create_by_prompt(fileusage_InputRecord
3744 		          | fileusage_BinaryMode,
3745 		          filemode_WriteAppend, 0);
3746 		if (!fileref) {
3747 			gagt_standout_string("Glk input logging failed.\n");
3748 			return;
3749 		}
3750 
3751 		g_vm->gagt_inputlog_stream = g_vm->glk_stream_open_file(fileref,
3752 		                       filemode_WriteAppend, 0);
3753 		g_vm->glk_fileref_destroy(fileref);
3754 		if (!g_vm->gagt_inputlog_stream) {
3755 			gagt_standout_string("Glk input logging failed.\n");
3756 			return;
3757 		}
3758 
3759 		gagt_normal_string("Glk input logging is now on.\n");
3760 	}
3761 
3762 	else if (gagt_strcasecmp(argument, "off") == 0) {
3763 		if (!g_vm->gagt_inputlog_stream) {
3764 			gagt_normal_string("Glk input logging is already off.\n");
3765 			return;
3766 		}
3767 
3768 		g_vm->glk_stream_close(g_vm->gagt_inputlog_stream, NULL);
3769 		g_vm->gagt_inputlog_stream = NULL;
3770 
3771 		gagt_normal_string("Glk input log is now off.\n");
3772 	}
3773 
3774 	else if (strlen(argument) == 0) {
3775 		gagt_normal_string("Glk input logging is ");
3776 		gagt_normal_string(g_vm->gagt_inputlog_stream ? "on" : "off");
3777 		gagt_normal_string(".\n");
3778 	}
3779 
3780 	else {
3781 		gagt_normal_string("Glk input logging can be ");
3782 		gagt_standout_string("on");
3783 		gagt_normal_string(", or ");
3784 		gagt_standout_string("off");
3785 		gagt_normal_string(".\n");
3786 	}
3787 }
3788 
3789 
3790 /*
3791  * gagt_command_readlog()
3792  *
3793  * Set the game input log, to read input from a file.
3794  */
gagt_command_readlog(const char * argument)3795 static void gagt_command_readlog(const char *argument) {
3796 	assert(argument);
3797 
3798 	if (gagt_strcasecmp(argument, "on") == 0) {
3799 		frefid_t fileref;
3800 
3801 		if (g_vm->gagt_readlog_stream) {
3802 			gagt_normal_string("Glk read log is already on.\n");
3803 			return;
3804 		}
3805 
3806 		fileref = g_vm->glk_fileref_create_by_prompt(fileusage_InputRecord
3807 		          | fileusage_BinaryMode,
3808 		          filemode_Read, 0);
3809 		if (!fileref) {
3810 			gagt_standout_string("Glk read log failed.\n");
3811 			return;
3812 		}
3813 
3814 		if (!g_vm->glk_fileref_does_file_exist(fileref)) {
3815 			g_vm->glk_fileref_destroy(fileref);
3816 			gagt_standout_string("Glk read log failed.\n");
3817 			return;
3818 		}
3819 
3820 		g_vm->gagt_readlog_stream = g_vm->glk_stream_open_file(fileref, filemode_Read, 0);
3821 		g_vm->glk_fileref_destroy(fileref);
3822 		if (!g_vm->gagt_readlog_stream) {
3823 			gagt_standout_string("Glk read log failed.\n");
3824 			return;
3825 		}
3826 
3827 		gagt_normal_string("Glk read log is now on.\n");
3828 	}
3829 
3830 	else if (gagt_strcasecmp(argument, "off") == 0) {
3831 		if (!g_vm->gagt_readlog_stream) {
3832 			gagt_normal_string("Glk read log is already off.\n");
3833 			return;
3834 		}
3835 
3836 		g_vm->glk_stream_close(g_vm->gagt_readlog_stream, NULL);
3837 		g_vm->gagt_readlog_stream = NULL;
3838 
3839 		gagt_normal_string("Glk read log is now off.\n");
3840 	}
3841 
3842 	else if (strlen(argument) == 0) {
3843 		gagt_normal_string("Glk read log is ");
3844 		gagt_normal_string(g_vm->gagt_readlog_stream ? "on" : "off");
3845 		gagt_normal_string(".\n");
3846 	}
3847 
3848 	else {
3849 		gagt_normal_string("Glk read log can be ");
3850 		gagt_standout_string("on");
3851 		gagt_normal_string(", or ");
3852 		gagt_standout_string("off");
3853 		gagt_normal_string(".\n");
3854 	}
3855 }
3856 
3857 
3858 /*
3859  * gagt_command_abbreviations()
3860  *
3861  * Turn abbreviation expansions on and off.
3862  */
gagt_command_abbreviations(const char * argument)3863 static void gagt_command_abbreviations(const char *argument) {
3864 	assert(argument);
3865 
3866 	if (gagt_strcasecmp(argument, "on") == 0) {
3867 		if (g_vm->gagt_abbreviations_enabled) {
3868 			gagt_normal_string("Glk abbreviation expansions are already on.\n");
3869 			return;
3870 		}
3871 
3872 		g_vm->gagt_abbreviations_enabled = TRUE;
3873 		gagt_normal_string("Glk abbreviation expansions are now on.\n");
3874 	}
3875 
3876 	else if (gagt_strcasecmp(argument, "off") == 0) {
3877 		if (!g_vm->gagt_abbreviations_enabled) {
3878 			gagt_normal_string("Glk abbreviation expansions are already off.\n");
3879 			return;
3880 		}
3881 
3882 		g_vm->gagt_abbreviations_enabled = FALSE;
3883 		gagt_normal_string("Glk abbreviation expansions are now off.\n");
3884 	}
3885 
3886 	else if (strlen(argument) == 0) {
3887 		gagt_normal_string("Glk abbreviation expansions are ");
3888 		gagt_normal_string(g_vm->gagt_abbreviations_enabled ? "on" : "off");
3889 		gagt_normal_string(".\n");
3890 	}
3891 
3892 	else {
3893 		gagt_normal_string("Glk abbreviation expansions can be ");
3894 		gagt_standout_string("on");
3895 		gagt_normal_string(", or ");
3896 		gagt_standout_string("off");
3897 		gagt_normal_string(".\n");
3898 	}
3899 }
3900 
3901 
3902 /*
3903  * gagt_command_fonts()
3904  *
3905  * Set the value for g_vm->gagt_font_mode depending on the argument from the
3906  * user's command escape.
3907  *
3908  * Despite our best efforts, font control may still be wrong in some games.
3909  * This command gives us a chance to correct that.
3910  */
gagt_command_fonts(const char * argument)3911 static void gagt_command_fonts(const char *argument) {
3912 	assert(argument);
3913 
3914 	if (gagt_strcasecmp(argument, "fixed") == 0) {
3915 		if (g_vm->gagt_font_mode == FONT_FIXED_WIDTH) {
3916 			gagt_normal_string("Glk font control is already 'fixed'.\n");
3917 			return;
3918 		}
3919 
3920 		g_vm->gagt_font_mode = FONT_FIXED_WIDTH;
3921 		gagt_normal_string("Glk font control is now 'fixed'.\n");
3922 	}
3923 
3924 	else if (gagt_strcasecmp(argument, "variable") == 0
3925 	         || gagt_strcasecmp(argument, "proportional") == 0) {
3926 		if (g_vm->gagt_font_mode == FONT_PROPORTIONAL) {
3927 			gagt_normal_string("Glk font control is already 'proportional'.\n");
3928 			return;
3929 		}
3930 
3931 		g_vm->gagt_font_mode = FONT_PROPORTIONAL;
3932 		gagt_normal_string("Glk font control is now 'proportional'.\n");
3933 	}
3934 
3935 	else if (gagt_strcasecmp(argument, "auto") == 0
3936 	         || gagt_strcasecmp(argument, "automatic") == 0) {
3937 		if (g_vm->gagt_font_mode == FONT_AUTOMATIC) {
3938 			gagt_normal_string("Glk font control is already 'automatic'.\n");
3939 			return;
3940 		}
3941 
3942 		g_vm->gagt_font_mode = FONT_AUTOMATIC;
3943 		gagt_normal_string("Glk font control is now 'automatic'.\n");
3944 	}
3945 
3946 	else if (gagt_strcasecmp(argument, "debug") == 0) {
3947 		if (g_vm->gagt_font_mode == FONT_DEBUG) {
3948 			gagt_normal_string("Glk font control is already 'debug'.\n");
3949 			return;
3950 		}
3951 
3952 		g_vm->gagt_font_mode = FONT_DEBUG;
3953 		gagt_normal_string("Glk font control is now 'debug'.\n");
3954 	}
3955 
3956 	else if (strlen(argument) == 0) {
3957 		gagt_normal_string("Glk font control is set to '");
3958 		switch (g_vm->gagt_font_mode) {
3959 		case FONT_AUTOMATIC:
3960 			gagt_normal_string("automatic");
3961 			break;
3962 
3963 		case FONT_FIXED_WIDTH:
3964 			gagt_normal_string("fixed");
3965 			break;
3966 
3967 		case FONT_PROPORTIONAL:
3968 			gagt_normal_string("proportional");
3969 			break;
3970 
3971 		case FONT_DEBUG:
3972 			gagt_normal_string("debug");
3973 			break;
3974 
3975 		default:
3976 			gagt_fatal("GLK: Invalid font mode encountered");
3977 			gagt_exit();
3978 		}
3979 		gagt_normal_string("'.\n");
3980 	}
3981 
3982 	else {
3983 		/* Avoid mentioning the debug setting. */
3984 		gagt_normal_string("Glk font control can be ");
3985 		gagt_standout_string("fixed");
3986 		gagt_normal_string(", ");
3987 		gagt_standout_string("proportional");
3988 		gagt_normal_string(", or ");
3989 		gagt_standout_string("automatic");
3990 		gagt_normal_string(".\n");
3991 	}
3992 }
3993 
3994 
3995 /*
3996  * gagt_command_delays()
3997  *
3998  * Set a value for g_vm->gagt_delay_mode depending on the argument from
3999  * the user's command escape.
4000  */
gagt_command_delays(const char * argument)4001 static void gagt_command_delays(const char *argument) {
4002 	assert(argument);
4003 
4004 	if (!g_vm->glk_gestalt(gestalt_Timer, 0)) {
4005 		gagt_normal_string("Glk delays are not available.\n");
4006 		return;
4007 	}
4008 
4009 	if (gagt_strcasecmp(argument, "full") == 0
4010 	        || gagt_strcasecmp(argument, "on") == 0) {
4011 		if (g_vm->gagt_delay_mode == DELAY_FULL) {
4012 			gagt_normal_string("Glk delay mode is already 'full'.\n");
4013 			return;
4014 		}
4015 
4016 		g_vm->gagt_delay_mode = DELAY_FULL;
4017 		gagt_normal_string("Glk delay mode is now 'full'.\n");
4018 	}
4019 
4020 	else if (gagt_strcasecmp(argument, "short") == 0
4021 	         || gagt_strcasecmp(argument, "half") == 0) {
4022 		if (g_vm->gagt_delay_mode == DELAY_SHORT) {
4023 			gagt_normal_string("Glk delay mode is already 'short'.\n");
4024 			return;
4025 		}
4026 
4027 		g_vm->gagt_delay_mode = DELAY_SHORT;
4028 		gagt_normal_string("Glk delay mode is now 'short'.\n");
4029 	}
4030 
4031 	else if (gagt_strcasecmp(argument, "none") == 0
4032 	         || gagt_strcasecmp(argument, "off") == 0) {
4033 		if (g_vm->gagt_delay_mode == DELAY_OFF) {
4034 			gagt_normal_string("Glk delay mode is already 'none'.\n");
4035 			return;
4036 		}
4037 
4038 		g_vm->gagt_delay_mode = DELAY_OFF;
4039 		gagt_normal_string("Glk delay mode is now 'none'.\n");
4040 	}
4041 
4042 	else if (strlen(argument) == 0) {
4043 		gagt_normal_string("Glk delay mode is set to '");
4044 		switch (g_vm->gagt_delay_mode) {
4045 		case DELAY_FULL:
4046 			gagt_normal_string("full");
4047 			break;
4048 
4049 		case DELAY_SHORT:
4050 			gagt_normal_string("short");
4051 			break;
4052 
4053 		case DELAY_OFF:
4054 			gagt_normal_string("none");
4055 			break;
4056 
4057 		default:
4058 			gagt_fatal("GLK: Invalid delay mode encountered");
4059 			gagt_exit();
4060 		}
4061 		gagt_normal_string("'.\n");
4062 	}
4063 
4064 	else {
4065 		gagt_normal_string("Glk delay mode can be ");
4066 		gagt_standout_string("full");
4067 		gagt_normal_string(", ");
4068 		gagt_standout_string("short");
4069 		gagt_normal_string(", or ");
4070 		gagt_standout_string("none");
4071 		gagt_normal_string(".\n");
4072 	}
4073 }
4074 
4075 
4076 /*
4077  * gagt_command_width()
4078  *
4079  * Print out the (approximate) display width, from status_width.  It's
4080  * approximate because the main window might include a scrollbar that
4081  * the status window doesn't have, may use a different size font, and so
4082  * on.  But the main window won't tell us a width at all - it always
4083  * returns zero.  If we don't happen to have a status window available
4084  * to us, there's not much we can say.
4085  *
4086  * Note that this function uses the interpreter variable status_width,
4087  * so it's important to keep this updated with the current window size at
4088  * all times.
4089  */
gagt_command_width(const char * argument)4090 static void gagt_command_width(const char *argument) {
4091 	char buffer[16];
4092 	assert(argument);
4093 
4094 	if (!g_vm->gagt_status_window) {
4095 		gagt_normal_string("Glk's current display width is unknown.\n");
4096 		return;
4097 	}
4098 
4099 	gagt_normal_string("Glk's current display width is approximately ");
4100 	sprintf(buffer, "%d", status_width);
4101 	gagt_normal_string(buffer);
4102 	gagt_normal_string(status_width == 1 ? " character" : " characters");
4103 	gagt_normal_string(".\n");
4104 }
4105 
4106 
4107 /*
4108  * gagt_command_replacements()
4109  *
4110  * Turn Glk special paragraph replacement on and off.
4111  */
gagt_command_replacements(const char * argument)4112 static void gagt_command_replacements(const char *argument) {
4113 	assert(argument);
4114 
4115 	if (gagt_strcasecmp(argument, "on") == 0) {
4116 		if (g_vm->gagt_replacement_enabled) {
4117 			gagt_normal_string("Glk replacements are already on.\n");
4118 			return;
4119 		}
4120 
4121 		g_vm->gagt_replacement_enabled = TRUE;
4122 		gagt_normal_string("Glk replacements are now on.\n");
4123 	}
4124 
4125 	else if (gagt_strcasecmp(argument, "off") == 0) {
4126 		if (!g_vm->gagt_replacement_enabled) {
4127 			gagt_normal_string("Glk replacements are already off.\n");
4128 			return;
4129 		}
4130 
4131 		g_vm->gagt_replacement_enabled = FALSE;
4132 		gagt_normal_string("Glk replacements are now off.\n");
4133 	}
4134 
4135 	else if (strlen(argument) == 0) {
4136 		gagt_normal_string("Glk replacements are ");
4137 		gagt_normal_string(g_vm->gagt_replacement_enabled ? "on" : "off");
4138 		gagt_normal_string(".\n");
4139 	}
4140 
4141 	else {
4142 		gagt_normal_string("Glk replacements can be ");
4143 		gagt_standout_string("on");
4144 		gagt_normal_string(", or ");
4145 		gagt_standout_string("off");
4146 		gagt_normal_string(".\n");
4147 	}
4148 }
4149 
4150 
4151 /*
4152  * gagt_command_statusline()
4153  *
4154  * Turn the extended status line on and off.
4155  */
gagt_command_statusline(const char * argument)4156 static void gagt_command_statusline(const char *argument) {
4157 	assert(argument);
4158 
4159 	if (!g_vm->gagt_status_window) {
4160 		gagt_normal_string("Glk status window is not available.\n");
4161 		return;
4162 	}
4163 
4164 	if (gagt_strcasecmp(argument, "extended") == 0
4165 	        || gagt_strcasecmp(argument, "full") == 0) {
4166 		if (g_vm->gagt_extended_status_enabled) {
4167 			gagt_normal_string("Glk status line mode is already 'extended'.\n");
4168 			return;
4169 		}
4170 
4171 		/* Expand the status window down to a second line. */
4172 		g_vm->glk_window_set_arrangement(g_vm->glk_window_get_parent(g_vm->gagt_status_window),
4173 		                                 winmethod_Above | winmethod_Fixed, 2, NULL);
4174 		g_vm->gagt_extended_status_enabled = TRUE;
4175 
4176 		gagt_normal_string("Glk status line mode is now 'extended'.\n");
4177 	}
4178 
4179 	else if (gagt_strcasecmp(argument, "short") == 0
4180 	         || gagt_strcasecmp(argument, "normal") == 0) {
4181 		if (!g_vm->gagt_extended_status_enabled) {
4182 			gagt_normal_string("Glk status line mode is already 'short'.\n");
4183 			return;
4184 		}
4185 
4186 		/* Shrink the status window down to one line. */
4187 		g_vm->glk_window_set_arrangement(g_vm->glk_window_get_parent(g_vm->gagt_status_window),
4188 		                                 winmethod_Above | winmethod_Fixed, 1, NULL);
4189 		g_vm->gagt_extended_status_enabled = FALSE;
4190 
4191 		gagt_normal_string("Glk status line mode is now 'short'.\n");
4192 	}
4193 
4194 	else if (strlen(argument) == 0) {
4195 		gagt_normal_string("Glk status line mode is set to '");
4196 		gagt_normal_string(g_vm->gagt_extended_status_enabled ? "extended" : "short");
4197 		gagt_normal_string("'.\n");
4198 	}
4199 
4200 	else {
4201 		gagt_normal_string("Glk status line can be ");
4202 		gagt_standout_string("extended");
4203 		gagt_normal_string(", or ");
4204 		gagt_standout_string("short");
4205 		gagt_normal_string(".\n");
4206 	}
4207 }
4208 
4209 
4210 /*
4211  * gagt_command_print_version_number()
4212  * gagt_command_version()
4213  *
4214  * Print out the Glk library version number.
4215  */
gagt_command_print_version_number(glui32 version)4216 static void gagt_command_print_version_number(glui32 version) {
4217 	char buffer[64];
4218 
4219 	sprintf(buffer, "%u.%u.%u",
4220 	        version >> 16, (version >> 8) & 0xff, version & 0xff);
4221 	gagt_normal_string(buffer);
4222 }
4223 
gagt_command_version(const char * argument)4224 static void gagt_command_version(const char *argument) {
4225 	glui32 version;
4226 	assert(argument);
4227 
4228 	gagt_normal_string("This is version ");
4229 	gagt_command_print_version_number(GAGT_PORT_VERSION);
4230 	gagt_normal_string(" of the Glk AGiliTy port.\n");
4231 
4232 	version = g_vm->glk_gestalt(gestalt_Version, 0);
4233 	gagt_normal_string("The Glk library version is ");
4234 	gagt_command_print_version_number(version);
4235 	gagt_normal_string(".\n");
4236 }
4237 
4238 
4239 /*
4240  * gagt_command_commands()
4241  *
4242  * Turn command escapes off.  Once off, there's no way to turn them back on.
4243  * Commands must be on already to enter this function.
4244  */
gagt_command_commands(const char * argument)4245 static void gagt_command_commands(const char *argument) {
4246 	assert(argument);
4247 
4248 	if (gagt_strcasecmp(argument, "on") == 0) {
4249 		gagt_normal_string("Glk commands are already on.\n");
4250 	}
4251 
4252 	else if (gagt_strcasecmp(argument, "off") == 0) {
4253 		g_vm->gagt_commands_enabled = FALSE;
4254 		gagt_normal_string("Glk commands are now off.\n");
4255 	}
4256 
4257 	else if (strlen(argument) == 0) {
4258 		gagt_normal_string("Glk commands are ");
4259 		gagt_normal_string(g_vm->gagt_commands_enabled ? "on" : "off");
4260 		gagt_normal_string(".\n");
4261 	}
4262 
4263 	else {
4264 		gagt_normal_string("Glk commands can be ");
4265 		gagt_standout_string("on");
4266 		gagt_normal_string(", or ");
4267 		gagt_standout_string("off");
4268 		gagt_normal_string(".\n");
4269 	}
4270 }
4271 
4272 /* Glk subcommands and handler functions. */
4273 struct gagt_command_t {
4274 	const char *const command;                      /* Glk subcommand. */
4275 	void (* const handler)(const char *argument);   /* Subcommand handler. */
4276 	const int takes_argument;                       /* Argument flag. */
4277 } ;
4278 typedef const gagt_command_t *gagt_commandref_t;
4279 
4280 static void gagt_command_summary(const char *argument);
4281 static void gagt_command_help(const char *argument);
4282 
4283 static gagt_command_t GAGT_COMMAND_TABLE[] = {
4284 	{"summary",        gagt_command_summary,        FALSE},
4285 	{"script",         gagt_command_script,         TRUE},
4286 	{"inputlog",       gagt_command_inputlog,       TRUE},
4287 	{"readlog",        gagt_command_readlog,        TRUE},
4288 	{"abbreviations",  gagt_command_abbreviations,  TRUE},
4289 	{"fonts",          gagt_command_fonts,          TRUE},
4290 	{"delays",         gagt_command_delays,         TRUE},
4291 	{"width",          gagt_command_width,          FALSE},
4292 	{"replacements",   gagt_command_replacements,   TRUE},
4293 	{"statusline",     gagt_command_statusline,     TRUE},
4294 	{"version",        gagt_command_version,        FALSE},
4295 	{"commands",       gagt_command_commands,       TRUE},
4296 	{"help",           gagt_command_help,           TRUE},
4297 	{NULL, NULL, FALSE}
4298 };
4299 
4300 
4301 /*
4302  * gagt_command_summary()
4303  *
4304  * Report all current Glk settings.
4305  */
gagt_command_summary(const char * argument)4306 static void gagt_command_summary(const char *argument) {
4307 	gagt_commandref_t entry;
4308 	assert(argument);
4309 
4310 	/*
4311 	 * Call handlers that have status to report with an empty argument,
4312 	 * prompting each to print its current setting.
4313 	 */
4314 	for (entry = GAGT_COMMAND_TABLE; entry->command; entry++) {
4315 		if (entry->handler == gagt_command_summary
4316 		        || entry->handler == gagt_command_help)
4317 			continue;
4318 
4319 		entry->handler("");
4320 	}
4321 }
4322 
4323 
4324 /*
4325  * gagt_command_help()
4326  *
4327  * Document the available Glk cmds.
4328  */
gagt_command_help(const char * cmd)4329 static void gagt_command_help(const char *cmd) {
4330 	gagt_commandref_t entry, matched;
4331 	assert(cmd);
4332 
4333 	if (strlen(cmd) == 0) {
4334 		gagt_normal_string("Glk cmds are");
4335 		for (entry = GAGT_COMMAND_TABLE; entry->command; entry++) {
4336 			gagt_commandref_t next;
4337 
4338 			next = entry + 1;
4339 			gagt_normal_string(next->command ? " " : " and ");
4340 			gagt_standout_string(entry->command);
4341 			gagt_normal_string(next->command ? "," : ".\n\n");
4342 		}
4343 
4344 		gagt_normal_string("Glk cmds may be abbreviated, as long as"
4345 		                   " the abbreviation is unambiguous.  Use ");
4346 		gagt_standout_string("glk help");
4347 		gagt_normal_string(" followed by a Glk cmd name for help on that"
4348 		                   " cmd.\n");
4349 		return;
4350 	}
4351 
4352 	matched = NULL;
4353 	for (entry = GAGT_COMMAND_TABLE; entry->command; entry++) {
4354 		if (gagt_strncasecmp(cmd, entry->command, strlen(cmd)) == 0) {
4355 			if (matched) {
4356 				gagt_normal_string("The Glk cmd ");
4357 				gagt_standout_string(cmd);
4358 				gagt_normal_string(" is ambiguous.  Try ");
4359 				gagt_standout_string("glk help");
4360 				gagt_normal_string(" for more information.\n");
4361 				return;
4362 			}
4363 			matched = entry;
4364 		}
4365 	}
4366 	if (!matched) {
4367 		gagt_normal_string("The Glk cmd ");
4368 		gagt_standout_string(cmd);
4369 		gagt_normal_string(" is not valid.  Try ");
4370 		gagt_standout_string("glk help");
4371 		gagt_normal_string(" for more information.\n");
4372 		return;
4373 	}
4374 
4375 	if (matched->handler == gagt_command_summary) {
4376 		gagt_normal_string("Prints a summary of all the current Glk AGiliTy"
4377 		                   " settings.\n");
4378 	}
4379 
4380 	else if (matched->handler == gagt_command_script) {
4381 		gagt_normal_string("Logs the game's output to a file.\n\nUse ");
4382 		gagt_standout_string("glk script on");
4383 		gagt_normal_string(" to begin logging game output, and ");
4384 		gagt_standout_string("glk script off");
4385 		gagt_normal_string(" to end it.  Glk AGiliTy will ask you for a file"
4386 		                   " when you turn scripts on.\n");
4387 	}
4388 
4389 	else if (matched->handler == gagt_command_inputlog) {
4390 		gagt_normal_string("Records the cmds you type into a game.\n\nUse ");
4391 		gagt_standout_string("glk inputlog on");
4392 		gagt_normal_string(", to begin recording your cmds, and ");
4393 		gagt_standout_string("glk inputlog off");
4394 		gagt_normal_string(" to turn off input logs.  You can play back"
4395 		                   " recorded cmds into a game with the ");
4396 		gagt_standout_string("glk readlog");
4397 		gagt_normal_string(" cmd.\n");
4398 	}
4399 
4400 	else if (matched->handler == gagt_command_readlog) {
4401 		gagt_normal_string("Plays back cmds recorded with ");
4402 		gagt_standout_string("glk inputlog on");
4403 		gagt_normal_string(".\n\nUse ");
4404 		gagt_standout_string("glk readlog on");
4405 		gagt_normal_string(".  cmd play back stops at the end of the"
4406 		                   " file.  You can also play back cmds from a"
4407 		                   " text file created using any standard editor.\n");
4408 	}
4409 
4410 	else if (matched->handler == gagt_command_abbreviations) {
4411 		gagt_normal_string("Controls abbreviation expansion.\n\nGlk AGiliTy"
4412 		                   " automatically expands several standard single"
4413 		                   " letter abbreviations for you; for example, \"x\""
4414 		                   " becomes \"examine\".  Use ");
4415 		gagt_standout_string("glk abbreviations on");
4416 		gagt_normal_string(" to turn this feature on, and ");
4417 		gagt_standout_string("glk abbreviations off");
4418 		gagt_normal_string(" to turn it off.  While the feature is on, you"
4419 		                   " can bypass abbreviation expansion for an"
4420 		                   " individual game cmd by prefixing it with a"
4421 		                   " single quote.\n");
4422 	}
4423 
4424 	else if (matched->handler == gagt_command_fonts) {
4425 		gagt_normal_string("Controls the way Glk AGiliTy uses fonts.\n\n"
4426 		                   "AGT games normally assume 80x25 monospaced font"
4427 		                   " displays.  Glk can often use proportional fonts."
4428 		                   "  To try to improve text display, Glk AGiliTy will"
4429 		                   " attempt to automatically detect when game text"
4430 		                   " can be displayed safely in a proportional font,"
4431 		                   " and when fixed width fonts are required.  For"
4432 		                   " some games, however, you may need to override"
4433 		                   " it.  Use ");
4434 		gagt_standout_string("glk fonts automatic");
4435 		gagt_normal_string(", ");
4436 		gagt_standout_string("glk fonts proportional");
4437 		gagt_normal_string(", and ");
4438 		gagt_standout_string("glk fonts fixed");
4439 		gagt_normal_string(" to switch between Glk AGiliTy font modes.\n");
4440 	}
4441 
4442 	else if (matched->handler == gagt_command_delays) {
4443 		gagt_normal_string("Shortens, or eliminates, AGT game delays.\n\nUse ");
4444 		gagt_standout_string("glk delays full");
4445 		gagt_normal_string(", ");
4446 		gagt_standout_string("glk delays short");
4447 		gagt_normal_string(", or ");
4448 		gagt_standout_string("glk delays none");
4449 		gagt_normal_string(".  In Glk AGiliTy, you can also end an AGT game's"
4450 		                   " delay early, by pressing Space while the game is"
4451 		                   " delaying.\n");
4452 	}
4453 
4454 	else if (matched->handler == gagt_command_width) {
4455 		gagt_normal_string("Prints the screen width available for fixed font"
4456 		                   " display.\n\nEven though Glk AGiliTy tries to handle"
4457 		                   " issues surrounding proportional font displays for"
4458 		                   " you automatically, some game elements may still"
4459 		                   " need to display in fixed width fonts.  These"
4460 		                   " elements will be happiest if the available screen"
4461 		                   " width is at least 80 columns.\n");
4462 	}
4463 
4464 	else if (matched->handler == gagt_command_replacements) {
4465 		gagt_normal_string("Controls game text scanning and replacement.\n\n"
4466 		                   "Glk AGiliTy can monitor the game's output, and"
4467 		                   " replace a few selected standard messages with"
4468 		                   " equivalents, printed using a style that stands"
4469 		                   " out better in Glk displays.  Use ");
4470 		gagt_standout_string("glk replacements on");
4471 		gagt_normal_string(" to turn this feature on, and ");
4472 		gagt_standout_string("glk replacements off");
4473 		gagt_normal_string(" to turn it off.\n");
4474 	}
4475 
4476 	else if (matched->handler == gagt_command_statusline) {
4477 		gagt_normal_string("Controls the Glk AGiliTy status line display.\n\n"
4478 		                   "Use ");
4479 		gagt_standout_string("glk statusline extended");
4480 		gagt_normal_string(" to display a full, two line status display, and ");
4481 		gagt_standout_string("glk statusline short");
4482 		gagt_normal_string(" for a single line status display.\n");
4483 	}
4484 
4485 	else if (matched->handler == gagt_command_version) {
4486 		gagt_normal_string("Prints the version numbers of the Glk library"
4487 		                   " and the Glk AGiliTy port.\n");
4488 	}
4489 
4490 	else if (matched->handler == gagt_command_commands) {
4491 		gagt_normal_string("Turn off Glk cmds.\n\nUse ");
4492 		gagt_standout_string("glk cmds off");
4493 		gagt_normal_string(" to disable all Glk cmds, including this one."
4494 		                   "  Once turned off, there is no way to turn Glk"
4495 		                   " cmds back on while inside the game.\n");
4496 	}
4497 
4498 	else if (matched->handler == gagt_command_help)
4499 		gagt_command_help("");
4500 
4501 	else
4502 		gagt_normal_string("There is no help available on that Glk cmd."
4503 		                   "  Sorry.\n");
4504 }
4505 
4506 
4507 /*
4508  * gagt_command_escape()
4509  *
4510  * This function is handed each input line.  If the line contains a specific
4511  * Glk port command, handle it and return TRUE, otherwise return FALSE.
4512  */
gagt_command_escape(const char * string)4513 static int gagt_command_escape(const char *string) {
4514 	int posn;
4515 	char *string_copy, *cmd, *argument;
4516 	assert(string);
4517 
4518 	/*
4519 	 * Return FALSE if the string doesn't begin with the Glk command escape
4520 	 * introducer.
4521 	 */
4522 	posn = strspn(string, "\t ");
4523 	if (gagt_strncasecmp(string + posn, "glk", strlen("glk")) != 0)
4524 		return FALSE;
4525 
4526 	/* Take a copy of the string, without any leading space or introducer. */
4527 	string_copy = (char *)gagt_malloc(strlen(string + posn) + 1 - strlen("glk"));
4528 	strcpy(string_copy, string + posn + strlen("glk"));
4529 
4530 	/*
4531 	 * Find the subcommand; the first word in the string copy.  Find its end,
4532 	 * and ensure it terminates with a NUL.
4533 	 */
4534 	posn = strspn(string_copy, "\t ");
4535 	cmd = string_copy + posn;
4536 	posn += strcspn(string_copy + posn, "\t ");
4537 	if (string_copy[posn] != '\0')
4538 		string_copy[posn++] = '\0';
4539 
4540 	/*
4541 	 * Now find any argument data for the command, ensuring it too terminates
4542 	 * with a NUL.
4543 	 */
4544 	posn += strspn(string_copy + posn, "\t ");
4545 	argument = string_copy + posn;
4546 	posn += strcspn(string_copy + posn, "\t ");
4547 	string_copy[posn] = '\0';
4548 
4549 	/*
4550 	 * Try to handle the command and argument as a Glk subcommand.  If it
4551 	 * doesn't run unambiguously, print command usage.  Treat an empty command
4552 	 * as "help".
4553 	 */
4554 	if (strlen(cmd) > 0) {
4555 		gagt_commandref_t entry, matched;
4556 		int matches;
4557 
4558 		/*
4559 		 * Search for the first unambiguous table cmd string matching
4560 		 * the cmd passed in.
4561 		 */
4562 		matches = 0;
4563 		matched = NULL;
4564 		for (entry = GAGT_COMMAND_TABLE; entry->command; entry++) {
4565 			if (gagt_strncasecmp(cmd, entry->command, strlen(cmd)) == 0) {
4566 				matches++;
4567 				matched = entry;
4568 			}
4569 		}
4570 
4571 		/* If the match was unambiguous, call the command handler. */
4572 		if (matches == 1) {
4573 			gagt_normal_char('\n');
4574 			matched->handler(argument);
4575 
4576 			if (!matched->takes_argument && strlen(argument) > 0) {
4577 				gagt_normal_string("[The ");
4578 				gagt_standout_string(matched->command);
4579 				gagt_normal_string(" cmd ignores arguments.]\n");
4580 			}
4581 		}
4582 
4583 		/* No match, or the cmd was ambiguous. */
4584 		else {
4585 			gagt_normal_string("\nThe Glk cmd ");
4586 			gagt_standout_string(cmd);
4587 			gagt_normal_string(" is ");
4588 			gagt_normal_string(matches == 0 ? "not valid" : "ambiguous");
4589 			gagt_normal_string(".  Try ");
4590 			gagt_standout_string("glk help");
4591 			gagt_normal_string(" for more information.\n");
4592 		}
4593 	} else {
4594 		gagt_normal_char('\n');
4595 		gagt_command_help("");
4596 	}
4597 
4598 	/* The string contained a Glk cmd; return TRUE. */
4599 	free(string_copy);
4600 	return TRUE;
4601 }
4602 
4603 
4604 /*---------------------------------------------------------------------*/
4605 /*  Glk port input functions                                           */
4606 /*---------------------------------------------------------------------*/
4607 
4608 /* Longest line we're going to buffer for input. */
4609 enum { GAGT_INPUTBUFFER_LENGTH = 256 };
4610 
4611 /* Table of single-character command abbreviations. */
4612 typedef const struct {
4613 	const char abbreviation;       /* Abbreviation character. */
4614 	const char *const expansion;   /* Expansion string. */
4615 } gagt_abbreviation_t;
4616 typedef gagt_abbreviation_t *gagt_abbreviationref_t;
4617 
4618 static gagt_abbreviation_t GAGT_ABBREVIATIONS[] = {
4619 	{'c', "close"},    {'g', "again"},  {'i', "inventory"},
4620 	{'k', "attack"},   {'l', "look"},   {'p', "open"},
4621 	{'q', "quit"},     {'r', "drop"},   {'t', "take"},
4622 	{'x', "examine"},  {'y', "yes"},    {'z', "wait"},
4623 	{'\0', NULL}
4624 };
4625 
4626 
4627 /*
4628  * gagt_expand_abbreviations()
4629  *
4630  * Expand a few common one-character abbreviations commonly found in other
4631  * game systems, but not always normal in AGT games.
4632  */
gagt_expand_abbreviations(char * buffer,int size)4633 static void gagt_expand_abbreviations(char *buffer, int size) {
4634 	char *command_, abbreviation;
4635 	const char *expansion;
4636 	gagt_abbreviationref_t entry;
4637 	assert(buffer);
4638 
4639 	/* Ignore anything that isn't a single letter command_. */
4640 	command_ = buffer + strspn(buffer, "\t ");
4641 	if (!(strlen(command_) == 1
4642 	        || (strlen(command_) > 1 && isspace(command_[1]))))
4643 		return;
4644 
4645 	/* Scan the abbreviations table for a match. */
4646 	abbreviation = g_vm->glk_char_to_lower((unsigned char) command_[0]);
4647 	expansion = NULL;
4648 	for (entry = GAGT_ABBREVIATIONS; entry->expansion; entry++) {
4649 		if (entry->abbreviation == abbreviation) {
4650 			expansion = entry->expansion;
4651 			break;
4652 		}
4653 	}
4654 
4655 	/*
4656 	 * If a match found, check for a fit, then replace the character with the
4657 	 * expansion string.
4658 	 */
4659 	if (expansion) {
4660 		if ((int)strlen(buffer) + (int)strlen(expansion) - 1 >= size)
4661 			return;
4662 
4663 		memmove(command_ + strlen(expansion) - 1, command_, strlen(command_) + 1);
4664 		memcpy(command_, expansion, strlen(expansion));
4665 
4666 		gagt_standout_string("[");
4667 		gagt_standout_char(abbreviation);
4668 		gagt_standout_string(" -> ");
4669 		gagt_standout_string(expansion);
4670 		gagt_standout_string("]\n");
4671 	}
4672 }
4673 
4674 
4675 /*
4676  * agt_input()
4677  *
4678  * Read a line from the keyboard, allocating space for it using malloc.
4679  * AGiliTy defines the following for the in_type argument:
4680  *
4681  *   in_type: 0=command, 1=number, 2=question, 3=userstr, 4=filename,
4682  *               5=RESTART,RESTORE,UNDO,QUIT
4683  *   Negative values are for internal use by the interface (i.e. this module)
4684  *   and so are free to be defined by the porter.
4685  *
4686  * Since it's unclear what use we can make of this information in Glk,
4687  * for the moment the argument is ignored.  It seems that no-one else
4688  * uses it, either.
4689  */
agt_input(int in_type)4690 char *agt_input(int in_type) {
4691 	event_t event;
4692 	int length;
4693 	char *buffer;
4694 
4695 	/*
4696 	 * Update the current status line display, and flush any pending buffered
4697 	 * output.  Release any suspension of delays.
4698 	 */
4699 	gagt_status_notify();
4700 	gagt_output_flush();
4701 	gagt_delay_resume();
4702 
4703 	/* Reset current x, as line input implies a newline. */
4704 	curr_x = 0;
4705 
4706 	/* Allocate a line input buffer, allowing 256 characters and a NUL. */
4707 	length = GAGT_INPUTBUFFER_LENGTH + 1;
4708 	buffer = (char *)gagt_malloc(length);
4709 
4710 	/*
4711 	 * If we have an input log to read from, use that until it is exhausted.
4712 	 * On end of file, close the stream and resume input from line requests.
4713 	 */
4714 	if (g_vm->gagt_readlog_stream) {
4715 		glui32 chars;
4716 
4717 		/* Get the next line from the log stream. */
4718 		chars = g_vm->glk_get_line_stream(g_vm->gagt_readlog_stream, buffer, length);
4719 		if (chars > 0) {
4720 			/* Echo the line just read in input style. */
4721 			g_vm->glk_set_style(style_Input);
4722 			g_vm->glk_put_buffer(buffer, chars);
4723 			g_vm->glk_set_style(style_Normal);
4724 
4725 			/*
4726 			 * Convert the string from Glk's ISO 8859 Latin-1 to IBM cp 437,
4727 			 * add to any script, and return it.
4728 			 */
4729 			gagt_iso_to_cp((const uchar *)buffer, (uchar *)buffer);
4730 			if (script_on)
4731 				textputs(scriptfile, buffer);
4732 			return buffer;
4733 		}
4734 
4735 		/*
4736 		 * We're at the end of the log stream.  Close it, and then continue
4737 		 * on to request a line from Glk.
4738 		 */
4739 		g_vm->glk_stream_close(g_vm->gagt_readlog_stream, NULL);
4740 		g_vm->gagt_readlog_stream = NULL;
4741 	}
4742 
4743 	/* Set this up as a read buffer for the main window, and wait. */
4744 	g_vm->glk_request_line_event(g_vm->gagt_main_window, buffer, length - 1, 0);
4745 	gagt_event_wait(evtype_LineInput, &event);
4746 	if (g_vm->shouldQuit()) {
4747 		g_vm->glk_cancel_line_event(g_vm->gagt_main_window, &event);
4748 		return nullptr;
4749 	}
4750 
4751 	/* Terminate the input line with a NUL. */
4752 	assert((int)event.val1 < length);
4753 	buffer[event.val1] = '\0';
4754 
4755 	/*
4756 	 * If neither abbreviations nor local commands are enabled, use the data
4757 	 * read above without further massaging.
4758 	 */
4759 	if (g_vm->gagt_abbreviations_enabled || g_vm->gagt_commands_enabled) {
4760 		char *cmd;
4761 
4762 		/*
4763 		 * If the first non-space input character is a quote, bypass all
4764 		 * abbreviation expansion and local command recognition, and use the
4765 		 * unadulterated input, less introductory quote.
4766 		 */
4767 		cmd = buffer + strspn(buffer, "\t ");
4768 		if (cmd[0] == '\'') {
4769 			/* Delete the quote with memmove(). */
4770 			memmove(cmd, cmd + 1, strlen(cmd));
4771 		} else {
4772 			/* Check for, and expand, any abbreviated commands. */
4773 			if (g_vm->gagt_abbreviations_enabled)
4774 				gagt_expand_abbreviations(buffer, length);
4775 
4776 			/*
4777 			 * Check for standalone "help", then for Glk port special commands;
4778 			 * suppress the interpreter's use of this input for Glk commands.
4779 			 */
4780 			if (g_vm->gagt_commands_enabled) {
4781 				int posn;
4782 
4783 				posn = strspn(buffer, "\t ");
4784 				if (gagt_strncasecmp(buffer + posn, "help", strlen("help")) == 0) {
4785 					if (strspn(buffer + posn + strlen("help"), "\t ")
4786 					        == strlen(buffer + posn + strlen("help"))) {
4787 						gagt_display_register_help_request();
4788 					}
4789 				}
4790 
4791 				if (gagt_command_escape(buffer)) {
4792 					gagt_display_silence_help_hints();
4793 					buffer[0] = '\0';
4794 					return buffer;
4795 				}
4796 			}
4797 		}
4798 	}
4799 
4800 	/*
4801 	 * If there is an input log active, log this input string to it.  Note that
4802 	 * by logging here we get any abbreviation expansions but we won't log glk
4803 	 * special commands, nor any input read from a current open input log.
4804 	 */
4805 	if (g_vm->gagt_inputlog_stream) {
4806 		g_vm->glk_put_string_stream(g_vm->gagt_inputlog_stream, buffer);
4807 		g_vm->glk_put_char_stream(g_vm->gagt_inputlog_stream, '\n');
4808 	}
4809 
4810 	/*
4811 	 * Convert from Glk's ISO 8859 Latin-1 to IBM cp 437, and add to any script.
4812 	 */
4813 	gagt_iso_to_cp((const uchar *)buffer, (uchar *)buffer);
4814 	if (script_on)
4815 		textputs(scriptfile, buffer);
4816 
4817 	gagt_debug("agt_input", "in_type=%d -> '%s'", in_type, buffer);
4818 	return buffer;
4819 }
4820 
4821 
4822 /*
4823  * agt_getkey()
4824  *
4825  * Read a single character and return it.  AGiliTy defines the echo_char
4826  * argument as:
4827  *
4828  *   If echo_char=1, echo character. If 0, then the character is not
4829  *   required to be echoed (and ideally shouldn't be).
4830  *
4831  * However, I've found that not all other ports really do this, and in
4832  * practice it doesn't always look right.  So for Glk, the character is
4833  * always echoed to the main window.
4834  */
agt_getkey(rbool echo_char)4835 char agt_getkey(rbool echo_char) {
4836 	event_t event;
4837 	char buffer[3];
4838 	assert(g_vm->glk_stream_get_current());
4839 
4840 	/*
4841 	 * Update the current status line display, and flush any pending buffered
4842 	 * output.  Release any suspension of delays.
4843 	 */
4844 	gagt_status_notify();
4845 	gagt_output_flush();
4846 	gagt_delay_resume();
4847 
4848 	/* Reset current x, as echoed character input implies a newline. */
4849 	curr_x = 0;
4850 
4851 	/*
4852 	 * If we have an input log to read from, use that as above until it is
4853 	 * exhausted.  We take just the first character of a given line.
4854 	 */
4855 	if (g_vm->gagt_readlog_stream) {
4856 		glui32 chars;
4857 		char logbuffer[GAGT_INPUTBUFFER_LENGTH + 1];
4858 
4859 		/* Get the next line from the log stream. */
4860 		chars = g_vm->glk_get_line_stream(g_vm->gagt_readlog_stream,
4861 		                                  logbuffer, sizeof(logbuffer));
4862 		if (chars > 0) {
4863 			/* Take just the first character, adding a newline if necessary. */
4864 			buffer[0] = logbuffer[0];
4865 			buffer[1] = buffer[0] == '\n' ? '\0' : '\n';
4866 			buffer[2] = '\0';
4867 
4868 			/* Echo the character just read in input style. */
4869 			g_vm->glk_set_style(style_Input);
4870 			g_vm->glk_put_string(buffer);
4871 			g_vm->glk_set_style(style_Normal);
4872 
4873 			/*
4874 			 * Convert from Glk's ISO 8859 Latin-1 to IBM cp 437, add to any
4875 			 * script, and return the character.
4876 			 */
4877 			gagt_iso_to_cp((const uchar *)buffer, (uchar *)buffer);
4878 			if (script_on)
4879 				textputs(scriptfile, buffer);
4880 			return buffer[0];
4881 		}
4882 
4883 		/*
4884 		 * We're at the end of the log stream.  Close it, and then continue
4885 		 * on to request a character from Glk.
4886 		 */
4887 		g_vm->glk_stream_close(g_vm->gagt_readlog_stream, NULL);
4888 		g_vm->gagt_readlog_stream = NULL;
4889 	}
4890 
4891 	/*
4892 	 * Request a single character from main window, and wait.  Ignore non-
4893 	 * ASCII codes that Glk returns for special function keys; we just want
4894 	 * one ASCII return value.  (Glk does treat Return as a special key,
4895 	 * though, and we want to pass that back as ASCII return.)
4896 	 */
4897 	do {
4898 		g_vm->glk_request_char_event(g_vm->gagt_main_window);
4899 		gagt_event_wait(evtype_CharInput, &event);
4900 	} while (event.val1 > BYTE_MAX_VAL && event.val1 != keycode_Return);
4901 
4902 	/*
4903 	 * Save the character into a short string buffer, converting Return
4904 	 * to newline, and adding a newline if not Return.
4905 	 */
4906 	buffer[0] = event.val1 == keycode_Return ? '\n' : event.val1;
4907 	buffer[1] = buffer[0] == '\n' ? '\0' : '\n';
4908 	buffer[2] = '\0';
4909 
4910 	/* If there is an input log active, log this input string to it. */
4911 	if (g_vm->gagt_inputlog_stream)
4912 		g_vm->glk_put_string_stream(g_vm->gagt_inputlog_stream, buffer);
4913 
4914 	/*
4915 	 * No matter what echo_char says, as it happens, the output doesn't look
4916 	 * great if we don't write out the character, and also a newline (c.f.
4917 	 * the "Yes/No" confirmation of the QUIT command)...
4918 	 */
4919 	g_vm->glk_set_style(style_Input);
4920 	g_vm->glk_put_string(buffer);
4921 	g_vm->glk_set_style(style_Normal);
4922 
4923 	/*
4924 	 * Convert from Glk's ISO 8859 Latin-1 to IBM cp 437, and add to any
4925 	 * script.
4926 	 */
4927 	gagt_iso_to_cp((const uchar *)buffer, (uchar *)buffer);
4928 	if (script_on)
4929 		textputs(scriptfile, buffer);
4930 
4931 	gagt_debug("agt_getkey", "echo_char=%d -> '%c'",
4932 	           echo_char, buffer[0] == '\n' ? '$' : buffer[0]);
4933 	return buffer[0];
4934 }
4935 
4936 
4937 /*---------------------------------------------------------------------*/
4938 /*  Glk port event functions                                           */
4939 /*---------------------------------------------------------------------*/
4940 
4941 /*
4942  * We have some clever atexit() finalizer handling for exit() calls that
4943  * come from the core interpreter.  However, an exit() call could also come
4944  * from Glk; Xkill for example.  To tell the difference, we'll have the
4945  * event wait functions set a flag to indicate when g_vm->glk_select() is active.
4946  */
4947 static int gagt_in_glk_select = FALSE;
4948 
4949 /*
4950  * gagt_event_wait_2()
4951  * gagt_event_wait()
4952  *
4953  * Process Glk events until one of the expected type, or types, arrives.
4954  * Return the event of that type.
4955  */
gagt_event_wait_2(glui32 wait_type_1,glui32 wait_type_2,event_t * event)4956 static void gagt_event_wait_2(glui32 wait_type_1, glui32 wait_type_2, event_t *event) {
4957 	assert(event);
4958 
4959 	do {
4960 		gagt_in_glk_select = TRUE;
4961 		g_vm->glk_select(event);
4962 		gagt_in_glk_select = FALSE;
4963 
4964 		switch (event->type) {
4965 		case evtype_Arrange:
4966 		case evtype_Redraw:
4967 			gagt_status_redraw();
4968 			break;
4969 		case evtype_Quit:
4970 			return;
4971 		default:
4972 			break;
4973 		}
4974 	} while (!(event->type == (EvType)wait_type_1 || event->type == (EvType)wait_type_2));
4975 }
4976 
gagt_event_wait(glui32 wait_type,event_t * event)4977 static void gagt_event_wait(glui32 wait_type, event_t *event) {
4978 	assert(event);
4979 	gagt_event_wait_2(wait_type, evtype_None, event);
4980 }
4981 
4982 
4983 /*
4984  * gagt_event_in_glk_select()
4985  *
4986  * Return TRUE if we're currently awaiting an event in g_vm->glk_select().  Used
4987  * by the finalizer to distinguish interpreter and glk exit() calls.
4988  */
gagt_event_in_glk_select()4989 static int gagt_event_in_glk_select() {
4990 	return gagt_in_glk_select;
4991 }
4992 
4993 
4994 /*---------------------------------------------------------------------*/
4995 /*  Miscellaneous Glk port startup and options functions               */
4996 /*---------------------------------------------------------------------*/
4997 
4998 /*
4999  * Default screen height and width, and also a default status width for
5000  * use with Glk libraries that don't support separate windows.
5001  */
5002 static const int GAGT_DEFAULT_SCREEN_WIDTH = 80,
5003 				 GAGT_DEFAULT_SCREEN_HEIGHT = 25,
5004 				 GAGT_DEFAULT_STATUS_WIDTH = 76;
5005 
5006 
5007 /*
5008  * agt_option()
5009  *
5010  * Platform-specific setup and options handling.  AGiliTy defines the
5011  * arguments and options as:
5012  *
5013  *   If setflag is 0, then the option was prefixed with NO_. Return 1 if
5014  *   the option is recognized.
5015  *
5016  * The Glk port has no options file handling, so none of this is
5017  * implemented here.
5018  */
agt_option(int optnum,char * optstr[],rbool setflag)5019 rbool agt_option(int optnum, char *optstr[], rbool setflag) {
5020 	gagt_debug("agt_option", "optnum=%d, optstr=%s, setflag=%d",
5021 	           optnum, optstr[0], setflag);
5022 	return 0;
5023 }
5024 
5025 
5026 /*
5027  * agt_globalfile()
5028  *
5029  * Global options file handle handling.  For now, this is a stub, since
5030  * there is no .agilrc for this port.
5031  */
agt_globalfile(int fid)5032 genfile agt_globalfile(int fid) {
5033 	gagt_debug("agt_globalfile", "fid=%d", fid);
5034 	return badfile(fCFG);
5035 }
5036 
5037 
5038 /*
5039  * init_interface()
5040  *
5041  * General initialization for the module; sets some variables, and creates
5042  * the Glk windows to work in.  Called from the AGiliTy main().
5043  */
init_interface()5044 void init_interface() {
5045 	glui32 status_height;
5046 
5047 	/*
5048 	 * Begin with some default values for global variables that this module
5049 	 * is somehow responsible for.
5050 	 */
5051 	script_on = center_on = par_fill_on = FALSE;
5052 	scriptfile = badfile(fSCR);
5053 	debugfile = nullptr; // stderr;
5054 
5055 	/*
5056 	 * Set up AGT-specific Glk styles.  This needs to be done before any Glk
5057 	 * window is opened.
5058 	 */
5059 	gagt_init_user_styles();
5060 
5061 	/*
5062 	 * Create the main game window.  The main game window creation must succeed.
5063 	 * If it fails, we'll return, and the caller can detect this by looking
5064 	 * for a NULL main window.
5065 	 */
5066 	g_vm->gagt_main_window = g_vm->glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
5067 	if (!g_vm->gagt_main_window)
5068 		return;
5069 
5070 	/*
5071 	 * Set the main window to be the default window, for convenience.  We do
5072 	 * this again in glk_main() -- this call is here just in case this version
5073 	 * of init_interface() is ever called by AGiliTy's main.
5074 	 */
5075 	g_vm->glk_set_window(g_vm->gagt_main_window);
5076 
5077 	/*
5078 	 * Screen height is something we don't use.  Linux Xglk returns dimensions
5079 	 * of 0x0 for text buffer windows, so we can't measure the main window
5080 	 * height anyway.  But... the height does come into play in AGiliTy's
5081 	 * agil.c, when the interpreter is deciding how to output game titles, and
5082 	 * how much of its own subsequent verbiage to output.  This gives us a
5083 	 * problem, since this "verbiage" is stuff we look for and replace with
5084 	 * our own special text.  So... sigh, set 25, and try to cope in the
5085 	 * special text we've set up with all the variations that ensue.
5086 	 *
5087 	 * Screen width does get used, but so, so many games, and for that matter
5088 	 * the interpreter itself, assume 80 chars, so it's simplest just to set,
5089 	 * and keep, this, and put up with the minor odd effects (making it match
5090 	 * status_width, or making it something like MAX_INT to defeat the game's
5091 	 * own wrapping, gives a lot of odder effects, trust me on this one...).
5092 	 */
5093 	screen_width = GAGT_DEFAULT_SCREEN_WIDTH;
5094 	screen_height = GAGT_DEFAULT_SCREEN_HEIGHT;
5095 
5096 	/*
5097 	 * Create a status window, with one or two lines as selected by user
5098 	 * options or flags.  We can live without a status window if we have to.
5099 	 */
5100 	status_height = g_vm->gagt_extended_status_enabled ? 2 : 1;
5101 	g_vm->glk_stylehint_set(wintype_TextGrid, style_User1, stylehint_ReverseColor, 1);
5102 	g_vm->gagt_status_window = g_vm->glk_window_open(g_vm->gagt_main_window,
5103 	                     winmethod_Above | winmethod_Fixed,
5104 	                     status_height, wintype_TextGrid, 0);
5105 	if (g_vm->gagt_status_window) {
5106 		/*
5107 		 * Call gagt_status_redraw() to set the interpreter's status_width
5108 		 * variable initial value.
5109 		 */
5110 		gagt_status_redraw();
5111 	} else {
5112 		/*
5113 		 * No status window, so set a suitable default status width.  In this
5114 		 * case, we're using a value four characters less than the set screen
5115 		 * width.  AGiliTy's status line code will fill to this width with
5116 		 * justified text, and we add two characters of bracketing when
5117 		 * displaying status lines for Glks that don't support separate windows,
5118 		 * making a total of 78 characters, which should be fairly standard.
5119 		 */
5120 		status_width = GAGT_DEFAULT_STATUS_WIDTH;
5121 	}
5122 
5123 	agt_clrscr();
5124 }
5125 
5126 
5127 /*---------------------------------------------------------------------*/
5128 /*  Replacement interface.c functions                                  */
5129 /*---------------------------------------------------------------------*/
5130 
5131 /* Get_user_file() type codes. */
5132 enum {
5133 	AGT_SCRIPT = 0,
5134 	AGT_SAVE = 1,
5135 	AGT_RESTORE = 2,
5136 	AGT_LOG_READ = 3,
5137 	AGT_LOG_WRITE = 4
5138 };
5139 
5140 /* Longest acceptable filename. */
5141 enum { GAGT_MAX_PATH = 1024 };
5142 
5143 
5144 #ifdef GLK_ANSI_ONLY
5145 /*
5146  * gagt_confirm()
5147  *
5148  * Print a confirmation prompt, and read a single input character, taking
5149  * only [YyNn] input.  If the character is 'Y' or 'y', return TRUE.
5150  *
5151  * This function is only required for the ANSI version of get_user_file().
5152  */
5153 static int
gagt_confirm(const char * prompt)5154 gagt_confirm(const char *prompt) {
5155 	event_t event;
5156 	unsigned char response;
5157 	assert(prompt);
5158 
5159 	/*
5160 	 * Print the confirmation prompt, in a style that hints that it's from the
5161 	 * interpreter, not the game.
5162 	 */
5163 	gagt_standout_string(prompt);
5164 
5165 	/* Wait for a single 'Y' or 'N' character response. */
5166 	response = ' ';
5167 	do {
5168 		g_vm->glk_request_char_event(g_vm->gagt_main_window);
5169 		gagt_event_wait(evtype_CharInput, &event);
5170 
5171 		if (event.val1 <= BYTE_MAX_VAL)
5172 			response = g_vm->glk_char_to_upper(event.val1);
5173 	} while (!(response == 'Y' || response == 'N'));
5174 
5175 	/* Echo the confirmation response, and a blank line. */
5176 	g_vm->glk_set_style(style_Input);
5177 	g_vm->glk_put_string(response == 'Y' ? "Yes" : "No");
5178 	g_vm->glk_set_style(style_Normal);
5179 	g_vm->glk_put_string("\n");
5180 
5181 	return response == 'Y';
5182 }
5183 #endif
5184 
5185 
5186 /*
5187  * gagt_get_user_file()
5188  *
5189  * Alternative versions of functions to get a file name from the user, and
5190  * return a file stream structure.  These functions are front-ended by the
5191  * main get_user_file() function, which first converts the AGT file type
5192  * into Glk usage and filemode, and also a mode for fopen()/fdopen().
5193  *
5194  * The ANSI version of the function prompts for the file using the simple
5195  * method of querying the user through input in the main window.  It then
5196  * constructs a file stream around the path entered, and returns it.
5197  *
5198  * The non-ANSI, Glk version is more sneaky.  It prompts for a file using
5199  * Glk's functions to get filenames by prompt, file selection dialog, or
5200  * whatever.  Then it attempts to uncover which file descriptor Glk opened
5201  * its file on, dup's it, closes the Glk stream, and returns a file stream
5202  * built on this file descriptor.  This is all highly non-ANSI, requiring
5203  * dup() and fdopen(), and making some assumptions about the way that dup,
5204  * open, and friends work.  It works on Linux, and on Mac (CodeWarrior).
5205  * It may also work for you, but if it doesn't, or if your system lacks
5206  * things like dup or fdopen, define g_vm->glk_ANSI_ONLY and use the safe version.
5207  *
5208  * If GARGLK is used, non-ansi version calls garglk_fileref_get_name()
5209  * instead, and opens a file the highly portable way, but still with a
5210  * Glkily nice prompt dialog.
5211  */
5212 #ifdef GLK_ANSI_ONLY
5213 static genfile
gagt_get_user_file(glui32 usage,glui32 fmode,const char * fdtype)5214 gagt_get_user_file(glui32 usage, glui32 fmode, const char *fdtype) {
5215 	char filepath[GAGT_MAX_PATH];
5216 	event_t event;
5217 	int index, all_spaces;
5218 	genfile retfile;
5219 	assert(fdtype);
5220 
5221 	/* Prompt in a similar way to Glk. */
5222 	switch (usage) {
5223 	case fileusage_SavedGame:
5224 		gagt_normal_string("Enter saved game");
5225 		break;
5226 
5227 	case fileusage_Transcript:
5228 		gagt_normal_string("Enter transcript file");
5229 		break;
5230 
5231 	case fileusage_InputRecord:
5232 		gagt_normal_string("Enter command record file");
5233 		break;
5234 	}
5235 	switch (fmode) {
5236 	case filemode_Read:
5237 		gagt_normal_string(" to load: ");
5238 		break;
5239 
5240 	case filemode_Write:
5241 		gagt_normal_string(" to store: ");
5242 		break;
5243 	}
5244 
5245 	/* Get the path to the file from the user. */
5246 	g_vm->glk_request_line_event(g_vm->gagt_main_window, filepath, sizeof(filepath) - 1, 0);
5247 	gagt_event_wait(evtype_LineInput, &event);
5248 
5249 	/* Terminate the file path with a NUL. */
5250 	assert(event.val1 < sizeof(filepath));
5251 	filepath[event.val1] = '\0';
5252 
5253 	/* Reject file paths that only contain any whitespace characters. */
5254 	all_spaces = TRUE;
5255 	for (index = 0; index < strlen(filepath); index++) {
5256 		if (!isspace(filepath[index])) {
5257 			all_spaces = FALSE;
5258 			break;
5259 		}
5260 	}
5261 	if (all_spaces)
5262 		return badfile(fSAV);
5263 
5264 	/* Confirm overwrite of any existing file. */
5265 	if (fmode == filemode_Write) {
5266 		genfile file;
5267 
5268 		file = fopen(filepath, "r");
5269 		if (file) {
5270 			fclose(file);
5271 
5272 			if (!gagt_confirm("Overwrite existing file? [y/n] "))
5273 				return badfile(fSAV);
5274 		}
5275 	}
5276 
5277 	/* Open and return a FILE* stream, or badfile if this fails. */
5278 	retfile = fopen(filepath, fdtype);
5279 	return retfile ? retfile : badfile(fSAV);
5280 }
5281 #endif
5282 
5283 #ifndef GLK_ANSI_ONLY
gagt_get_user_file(glui32 usage,glui32 fmode,const char * fdtype)5284 static genfile gagt_get_user_file(glui32 usage, glui32 fmode, const char *fdtype) {
5285 	frefid_t fileref;
5286 	genfile retfile;
5287 	assert(fdtype);
5288 
5289 	/* Try to get a Glk file reference with these attributes. */
5290 	fileref = g_vm->glk_fileref_create_by_prompt(usage, (FileMode)fmode, 0);
5291 	if (!fileref)
5292 		return badfile(fSAV);
5293 
5294 	/*
5295 	 * Reject the file reference if we're expecting to read from it,
5296 	 * and the referenced file doesn't exist.
5297 	 */
5298 	if (fmode == filemode_Read && !g_vm->glk_fileref_does_file_exist(fileref)) {
5299 		g_vm->glk_fileref_destroy(fileref);
5300 		return badfile(fSAV);
5301 	}
5302 
5303 	/*
5304 	 * Now, it gets ugly.  Glk assumes that the interpreter will do all of
5305 	 * its reading and writing using the Glk streams read/write functions.
5306 	 * It won't; at least, not without major surgery.  So here we're going
5307 	 * to do some dangerous stuff...
5308 	 *
5309 	 * Since a Glk stream is opaque, it's hard to tell what the underlying
5310 	 * file descriptor is for it.  We can get it if we want to play around
5311 	 * in the internals of the strid_t structure, but it's unpleasant.
5312 	 * The alternative is, arguably, no more pleasant, but it makes for
5313 	 * (perhaps) more portable code.  What we'll do is to dup a file, then
5314 	 * immediately close it, and call g_vm->glk_stream_open_file().  The open()
5315 	 * in g_vm->glk_stream_open_file() will return the same file descriptor number
5316 	 * that we just close()d (in theory...).  This makes the following two
5317 	 * major assumptions:
5318 	 *
5319 	 *  1) g_vm->glk_stream_open_file() opens precisely one file with open()
5320 	 *  2) open() always uses the lowest available file descriptor number,
5321 	 *     like dup()
5322 	 *
5323 	 * Believe it or not, this is better than the alternatives.  There is
5324 	 * no Glk function to return the filename from a frefid_t, and it
5325 	 * moves about in different Glk libraries so we can't just take it
5326 	 * from a given offset.  And there is no Glk function to return the
5327 	 * underlying file descriptor or FILE* from a Glk stream either. :-(
5328 	 */
5329 
5330 #ifdef GARGLK
5331 	retfile = fopen(g_vm->garglk_fileref_get_name(fileref), fdtype);
5332 #else
5333 	strid_t stream;
5334 	int tryfd, glkfd, dupfd, retfd;
5335 
5336 	/* So, start by dup()'ing the first file descriptor we can, ... */
5337 	glkfd = -1;
5338 	for (tryfd = 0; tryfd < FD_SETSIZE; tryfd++) {
5339 		glkfd = fcntl(tryfd, F_DUPFD, 0);
5340 		if (glkfd != -1)
5341 			break;
5342 	}
5343 	if (tryfd >= FD_SETSIZE) {
5344 		g_vm->glk_fileref_destroy(fileref);
5345 		return badfile(fSAV);
5346 	}
5347 
5348 	/* ...then closing it, ... */
5349 	close(glkfd);
5350 
5351 	/* ...now open the Glk stream, assuming it opens on file 'glkfd', ... */
5352 	stream = g_vm->glk_stream_open_file(fileref, fmode, 0);
5353 	if (!stream) {
5354 		g_vm->glk_fileref_destroy(fileref);
5355 		return badfile(fSAV);
5356 	}
5357 
5358 	/* ...dup() the Glk file onto another file descriptor, ... */
5359 	dupfd = fcntl(glkfd, F_DUPFD, 0);
5360 	assert(dupfd != -1);
5361 
5362 	/* ...close and destroy the Glk edifice for this file, ... */
5363 	g_vm->glk_stream_close(stream, NULL);
5364 	g_vm->glk_fileref_destroy(fileref);
5365 
5366 	/* ...for neatness, dup() back to the old Glk file descriptor, ... */
5367 	retfd = fcntl(dupfd, F_DUPFD, 0);
5368 	assert(retfd != -1 && retfd == glkfd);
5369 	close(dupfd);
5370 
5371 	/* ...and finally, open a FILE* stream onto the return descriptor. */
5372 	retfile = fdopen(retfd, fdtype);
5373 	if (!retfile)
5374 		return badfile(fSAV);
5375 #endif /* GARGLK */
5376 
5377 	/*
5378 	 * The result of all of this should now be that retfile is a FILE* wrapper
5379 	 * round a file descriptor open on a file indicated by the user through Glk.
5380 	 * Return it.
5381 	 */
5382 	return retfile;
5383 }
5384 #endif
5385 
5386 
5387 /*
5388  * get_user_file()
5389  *
5390  * Get a file name from the user, and return the file stream structure.
5391  * This is a front-end to ANSI and non-ANSI variants of the function.
5392  */
get_user_file(int type)5393 genfile get_user_file(int type) {
5394 	glui32 usage = 0, fmode = 0;
5395 	const char *fdtype;
5396 	genfile retfile;
5397 
5398 	gagt_output_flush();
5399 
5400 	/* Map AGiliTy type to Glk usage and filemode. */
5401 	switch (type) {
5402 	case AGT_SCRIPT:
5403 		usage = fileusage_Transcript;
5404 		fmode = filemode_Write;
5405 		break;
5406 
5407 	case AGT_SAVE:
5408 		usage = fileusage_SavedGame;
5409 		fmode = filemode_Write;
5410 		break;
5411 
5412 	case AGT_RESTORE:
5413 		usage = fileusage_SavedGame;
5414 		fmode = filemode_Read;
5415 		break;
5416 
5417 	case AGT_LOG_READ:
5418 		usage = fileusage_InputRecord;
5419 		fmode = filemode_Read;
5420 		break;
5421 
5422 	case AGT_LOG_WRITE:
5423 		usage = fileusage_InputRecord;
5424 		fmode = filemode_Write;
5425 		break;
5426 
5427 	default:
5428 		gagt_fatal("GLK: Unknown file type encountered");
5429 		gagt_exit();
5430 	}
5431 
5432 	/* From these, determine a mode type for the f[d]open() call. */
5433 	if (fmode == filemode_Write)
5434 		fdtype = usage == fileusage_SavedGame ? "wb" : "w";
5435 	else
5436 		fdtype = usage == fileusage_SavedGame ? "rb" : "r";
5437 
5438 	/* Get a file stream from these using the appropriate function. */
5439 	retfile = gagt_get_user_file(usage, fmode, fdtype);
5440 
5441 	gagt_debug("get_user_file", "type=%d -> %p", type, retfile);
5442 	return retfile;
5443 }
5444 
5445 
5446 /*
5447  * set_default_filenames()
5448  *
5449  * Set defaults for last save, log, and script filenames.
5450  */
set_default_filenames(fc_type fc)5451 void set_default_filenames(fc_type fc) {
5452 	/*
5453 	 * There is nothing to do in this function, since Glk has its own ideas on
5454 	 * default names for files obtained with a prompt.
5455 	 */
5456 	gagt_debug("set_default_filenames", "fc=%p", fc);
5457 }
5458 
5459 
5460 /*---------------------------------------------------------------------*/
5461 /*  Functions intercepted by link-time wrappers                        */
5462 /*---------------------------------------------------------------------*/
5463 
5464 /*
5465  * __wrap_toupper()
5466  * __wrap_tolower()
5467  *
5468  * Wrapper functions around toupper(), tolower(), and fatal().  The Linux
5469  * linker's --wrap option will convert calls to mumble() to __wrap_mumble()
5470  * if we give it the right options.  We'll use this feature to translate
5471  * all toupper() and tolower() calls in the interpreter code into calls to
5472  * Glk's versions of these functions.
5473  *
5474  * It's not critical that we do this.  If a linker, say a non-Linux one,
5475  * won't do --wrap, then just do without it.  It's unlikely that there
5476  * will be much noticeable difference.
5477  */
__wrap_toupper(int ch)5478 int __wrap_toupper(int ch) {
5479 	unsigned char uch;
5480 
5481 	uch = g_vm->glk_char_to_upper((unsigned char) ch);
5482 	return (int) uch;
5483 }
5484 
__wrap_tolower(int ch)5485 int __wrap_tolower(int ch) {
5486 	unsigned char lch;
5487 
5488 	lch = g_vm->glk_char_to_lower((unsigned char) ch);
5489 	return (int) lch;
5490 }
5491 
5492 
5493 /*---------------------------------------------------------------------*/
5494 /*  Replacements for AGiliTy main() and options parsing                */
5495 /*---------------------------------------------------------------------*/
5496 
5497 /* External declaration of interface.c's set default options function. */
5498 extern void set_default_options();
5499 
5500 
5501 /*
5502  * gagt_startup_code()
5503  * gagt_main()
5504  *
5505  * Together, these functions take the place of the original AGiliTy main().
5506  * The first one is called from glkunix_startup_code().  The second is called
5507  * from glk_main(), and does the real work of running the game.
5508  */
gagt_startup_code()5509 bool gagt_startup_code() {
5510 	/* Make the mandatory call for initialization. */
5511 	set_default_options();
5512 
5513 	/* All startup options were handled successfully. */
5514 	return TRUE;
5515 }
5516 
gagt_main()5517 static void gagt_main() {
5518 	fc_type fc;
5519 
5520 	/*
5521 	 * Initialize the interface.  As it happens, init_interface() is in our
5522 	 * module here (above), and ignores argc and argv, but since the main() in
5523 	 * AGiliTy passes them, we'll do so here, just in case we ever want to go
5524 	 * back to using AGiliTy's main() function.
5525 	 *
5526 	 * init_interface() can fail if there is a problem creating the main
5527 	 * window.  As it doesn't return status, we have to detect this by checking
5528 	 * that g_vm->gagt_main_window is not NULL.
5529 	 */
5530 	init_interface();
5531 	if (!g_vm->gagt_main_window) {
5532 		gagt_fatal("GLK: Can't open main window");
5533 		gagt_exit();
5534 	}
5535 	g_vm->glk_window_clear(g_vm->gagt_main_window);
5536 	g_vm->glk_set_window(g_vm->gagt_main_window);
5537 	g_vm->glk_set_style(style_Normal);
5538 
5539 	/*
5540 	 * Create a game file context, and try to ensure it will open successfully
5541 	 * in run_game().
5542 	 */
5543 	fc = init_file_context(g_vm->gagt_gamefile, fDA1);
5544 	if (!(gagt_workround_fileexist(fc, fAGX)
5545 	        || gagt_workround_fileexist(fc, fDA1))) {
5546 		if (g_vm->gagt_status_window)
5547 			g_vm->glk_window_close(g_vm->gagt_status_window, NULL);
5548 		gagt_header_string("Glk AGiliTy Error\n\n");
5549 		gagt_normal_string("Can't find or open game '");
5550 		gagt_normal_string(g_vm->gagt_gamefile);
5551 		gagt_normal_char('\'');
5552 		gagt_normal_char('\n');
5553 		gagt_exit();
5554 	}
5555 
5556 	/*
5557 	 * Run the game interpreter in AGiliTy.  run_game() releases the file
5558 	 * context, so we don't have to, don't want to, and shouldn't.
5559 	 */
5560 	run_game(fc);
5561 
5562 	/*
5563 	 * Handle any updated status, and flush all remaining buffered output;
5564 	 * this also frees all malloc'ed memory in the buffers.
5565 	 */
5566 	gagt_status_notify();
5567 	gagt_output_flush();
5568 
5569 	/*
5570 	 * Free any temporary memory that may have been used by status line
5571 	 * functions.
5572 	 */
5573 	gagt_status_cleanup();
5574 
5575 	/* Close any open transcript, input log, and/or read log. */
5576 	if (g_vm->gagt_transcript_stream) {
5577 		g_vm->glk_stream_close(g_vm->gagt_transcript_stream, NULL);
5578 		g_vm->gagt_transcript_stream = NULL;
5579 	}
5580 	if (g_vm->gagt_inputlog_stream) {
5581 		g_vm->glk_stream_close(g_vm->gagt_inputlog_stream, NULL);
5582 		g_vm->gagt_inputlog_stream = NULL;
5583 	}
5584 	if (g_vm->gagt_readlog_stream) {
5585 		g_vm->glk_stream_close(g_vm->gagt_readlog_stream, NULL);
5586 		g_vm->gagt_readlog_stream = NULL;
5587 	}
5588 }
5589 
5590 
5591 /*---------------------------------------------------------------------*/
5592 /*  Linkage between Glk entry/exit calls and the AGiliTy interpreter   */
5593 /*---------------------------------------------------------------------*/
5594 
5595 /*
5596  * Safety flags, to ensure we always get startup before main, and that
5597  * we only get a call to main once.
5598  */
5599 static int gagt_startup_called = FALSE,
5600 		   gagt_main_called = FALSE;
5601 
5602 /*
5603  * We try to catch calls to exit() from the interpreter, and redirect them
5604  * to g_vm->glk_exit().  To help tell these calls from a call to exit() from
5605  * g_vm->glk_exit() itself, we need to monitor when interpreter code is running,
5606  * and when not.
5607  */
5608 static int gagt_agility_running = FALSE;
5609 
5610 
5611 /*
5612  * gagt_finalizer()
5613  *
5614  * ANSI atexit() handler.  This is the first part of trying to catch and re-
5615  * direct the calls the core AGiliTy interpreter makes to exit() -- we really
5616  * want it to call g_vm->glk_exit(), but it's hard to achieve.  There are three
5617  * basic approaches possible, and all have drawbacks:
5618  *
5619  *   o #define exit to gagt_something, and provide the gagt_something()
5620  *     function.  This type of macro definition is portable for the most
5621  *     part, but tramples the code badly, and messes up the build of the
5622  *     non-interpreter "support" binaries.
5623  *   o Use ld's --wrap to wrapper exit.  This only works with Linux's linker
5624  *     and so isn't at all portable.
5625  *   o Register an exit handler with atexit(), and try to cope in it after
5626  *     exit() has been called.
5627  *
5628  * Here we try the last of these.  The one sticky part of it is that in our
5629  * exit handler we'll want to call g_vm->glk_exit(), which will in all likelihood
5630  * call exit().  And multiple calls to exit() from a program are "undefined".
5631  *
5632  * In practice, C runtimes tend to do one of three things: they treat the
5633  * exit() call from the exit handler as if it was a return; they recurse
5634  * indefinitely through the hander; or they do something ugly (abort, for
5635  * example).  The first of these is fine, ideal in fact, and seems to be the
5636  * Linux and SVR4 behavior.  The second we can avoid with a flag.  The last
5637  * is the problem case, seen only with SVR3 (and even then, it occurs only
5638  * on program exit, after everything's cleaned up, and for that matter only
5639  * on abnormal exit).
5640  *
5641  * Note that here we're not expecting to get a call to this routine, and if
5642  * we do, and interpreter code is still running, it's a sign that we need
5643  * to take actions we'd hoped not to have to take.
5644  */
gagt_finalizer()5645 void gagt_finalizer() {
5646 	/*
5647 	 * If interpreter code is still active, and we're not in a g_vm->glk_select(),
5648 	 * the core interpreter code called exit().  Handle cleanup.
5649 	 */
5650 	if (gagt_agility_running && !gagt_event_in_glk_select()) {
5651 		event_t event;
5652 
5653 		/*
5654 		 * If we have a main window, try to update status (which may go to the
5655 		 * status window, or to the main window) and flush any pending buffered
5656 		 * output.
5657 		 */
5658 		if (g_vm->gagt_main_window) {
5659 			gagt_status_notify();
5660 			gagt_output_flush();
5661 		}
5662 
5663 		/*
5664 		 * Clear the flag to avoid recursion, and call g_vm->glk_exit() to clean up
5665 		 * Glk and terminate.  This is the call that probably re-calls exit(),
5666 		 * and thus prods "undefined" bits of the C runtime, so we'll make it
5667 		 * configurable and overrideable for problem cases.
5668 		 */
5669 		gagt_agility_running = FALSE;
5670 
5671 		/*
5672 		 * We've decided not to take the dangerous route.
5673 		 *
5674 		 * In that case, providing we have a main window, fake a Glk-like-ish
5675 		 * hit-any-key-and-wait message using a simple string in the main
5676 		 * window.  Not great, but usable where we're forced into bypassing
5677 		 * g_vm->glk_exit().  If we have no main window, there's no point in doing
5678 		 * anything more.
5679 		 */
5680 		if (g_vm->gagt_main_window) {
5681 			g_vm->glk_cancel_char_event(g_vm->gagt_main_window);
5682 			g_vm->glk_cancel_line_event(g_vm->gagt_main_window, NULL);
5683 
5684 			g_vm->glk_set_style(style_Alert);
5685 			g_vm->glk_put_string("\n\nHit any key to exit.\n");
5686 			g_vm->glk_request_char_event(g_vm->gagt_main_window);
5687 			gagt_event_wait(evtype_CharInput, &event);
5688 		}
5689 	}
5690 }
5691 
5692 
5693 /*
5694  * gagt_exit()
5695  *
5696  * g_vm->glk_exit() local wrapper.  This is the second part of trying to catch
5697  * and redirect calls to exit().  g_vm->glk_finalizer() above needs to know that
5698  * we called g_vm->glk_exit() already from here, so it doesn't try to do it again.
5699  */
gagt_exit()5700 static void gagt_exit() {
5701 	assert(gagt_agility_running);
5702 
5703 	/*
5704 	 * Clear the running flag to neutralize gagt_finalizer(), throw out any
5705 	 * buffered output data, and then call the real g_vm->glk_exit().
5706 	 */
5707 	gagt_agility_running = FALSE;
5708 	gagt_output_delete();
5709 	g_vm->glk_exit();
5710 }
5711 
5712 
5713 /*
5714  * __wrap_exit()
5715  *
5716  * Exit() wrapper where a linker does --wrap.  This is the third part of
5717  * trying to catch and redirect calls to exit().
5718  *
5719  * This function is for use only with IFP, and avoids a nasty attempt at
5720  * reusing a longjmp buffer.   IFP will redirect calls to exit() into
5721  * g_vm->glk_exit() as a matter of course.  It also handles atexit(), and we've
5722  * registered a function with atexit() that calls g_vm->glk_exit(), and
5723  * IFP redirects g_vm->glk_exit() to be an effective return from glk_main().  At
5724  * that point it calls finalizers.  So without doing something special for
5725  * IFP, we'll find ourselves calling g_vm->glk_exit() twice -- once as the IFP
5726  * redirected exit(), and once from our finalizer.  Two returns from the
5727  * function glk_main() is a recipe for unpleasantness.
5728  *
5729  * As IFP is Linux-only, at present, --wrap will always be available to IFP
5730  * plugin builds.  So here, we'll wrap exit() before IFP can get to it, and
5731  * handle it safely.  For non-IFP/non-wrap links, this is just an unused
5732  * function definition, and can be safely ignored...
5733  */
__wrap_exit(int status)5734 void __wrap_exit(int status) {
5735 	assert(gagt_agility_running);
5736 
5737 	/*
5738 	 * In an IFP plugin, only the core interpreter code could have called exit()
5739 	 * here -- we don't, and IFP redirects g_vm->glk_exit(), the only other potential
5740 	 * caller of exit().  (It also redirects exit() if we don't get to it here
5741 	 * first.)
5742 	 *
5743 	 * So, if we have a main window, flush it.  This is the same cleanup as
5744 	 * done by the finalizer.
5745 	 */
5746 	if (g_vm->gagt_main_window) {
5747 		gagt_status_notify();
5748 		gagt_output_flush();
5749 	}
5750 
5751 	/* Clear the running flag, and transform exit() into a g_vm->glk_exit(). */
5752 	gagt_agility_running = FALSE;
5753 	g_vm->glk_exit();
5754 }
5755 
5756 
5757 /*
5758  * glk_main)
5759  *
5760  * Main entry point for Glk.  Here, all startup is done, and we call our
5761  * function to run the game.
5762  */
glk_main()5763 void glk_main() {
5764 	assert(gagt_startup_called && !gagt_main_called);
5765 	gagt_main_called = TRUE;
5766 
5767 	/*
5768 	 * If we're testing for a clean exit, deliberately call exit() to see what
5769 	 * happens.  We're hoping for a clean process termination, but our exit
5770 	 * code explores "undefined" ANSI.  If we get something ugly, like a core
5771 	 * dump, we'll want to set GLK[AGIL]_CLEAN_EXIT.
5772 	 */
5773 	if (g_vm->gagt_clean_exit_test) {
5774 		gagt_agility_running = TRUE;
5775 		return;
5776 	}
5777 
5778 	/*
5779 	 * The final part of trapping exit().  Set the running flag, and call the
5780 	 * interpreter main function.  Clear the flag when the main function returns.
5781 	 */
5782 	gagt_agility_running = TRUE;
5783 	gagt_main();
5784 	gagt_agility_running = FALSE;
5785 }
5786 
5787 
5788 /*---------------------------------------------------------------------*/
5789 /*  Glk linkage relevant only to the UNIX platform                     */
5790 /*---------------------------------------------------------------------*/
5791 
5792 /*
5793  * Glk arguments for UNIX versions of the Glk interpreter.
5794  */
5795 /*
5796 glkunix_argumentlist_t glkunix_arguments[] = {
5797  {(char *) "-gf", glkunix_arg_NoValue,
5798   (char *) "-gf        Force Glk to use only a fixed width font"},
5799  {(char *) "-gp", glkunix_arg_NoValue,
5800   (char *) "-gp        Allow Glk to use only a proportional font"},
5801  {(char *) "-ga", glkunix_arg_NoValue,
5802   (char *) "-ga        Try to use a suitable Glk font automatically"},
5803  {(char *) "-gd", glkunix_arg_NoValue,
5804   (char *) "-gd        Delay for the full period in Glk"},
5805  {(char *) "-gh", glkunix_arg_NoValue,
5806   (char *) "-gh        Delay for approximately half the period in Glk"},
5807  {(char *) "-gn", glkunix_arg_NoValue,
5808   (char *) "-gn        Turn off all game delays in Glk"},
5809  {(char *) "-gr", glkunix_arg_NoValue,
5810   (char *) "-gr        Turn off Glk text replacement"},
5811  {(char *) "-gx", glkunix_arg_NoValue,
5812   (char *) "-gx        Turn off Glk abbreviation expansions"},
5813  {(char *) "-gs", glkunix_arg_NoValue,
5814   (char *) "-gs        Display a short status window in Glk"},
5815  {(char *) "-gl", glkunix_arg_NoValue,
5816   (char *) "-gl        Display an extended status window in Glk"},
5817  {(char *) "-gc", glkunix_arg_NoValue,
5818   (char *) "-gc        Turn off Glk command escapes in games"},
5819  {(char *) "-gD", glkunix_arg_NoValue,
5820   (char *) "-gD        Turn on Glk port module debug tracing"},
5821  {(char *) "-g#", glkunix_arg_NoValue,
5822   (char *) "-g#        Test for clean exit (Glk module debugging only)"},
5823  {(char *) "-1", glkunix_arg_NoValue,
5824   (char *) "-1         IRUN Mode: Print messages in first person"},
5825  {(char *) "-d", glkunix_arg_NoValue,
5826   (char *) "-d         Debug metacommand execution"},
5827  {(char *) "-t", glkunix_arg_NoValue,
5828   (char *) "-t         Test mode"},
5829  {(char *) "-c", glkunix_arg_NoValue,
5830   (char *) "-c         Create test file"},
5831  {(char *) "-m", glkunix_arg_NoValue,
5832   (char *) "-m         Force descriptions to be loaded from disk"},
5833 #ifdef OPEN_AS_TEXT
5834  {(char *) "-b", glkunix_arg_NoValue,
5835   (char *) "-b         Open data files as binary files"},
5836 #endif
5837  {(char *) "-p", glkunix_arg_NoValue,
5838   (char *) "-p         Debug parser"},
5839  {(char *) "-x", glkunix_arg_NoValue,
5840   (char *) "-x         Debug verb execution loop"},
5841  {(char *) "-a", glkunix_arg_NoValue,
5842   (char *) "-a         Debug disambiguation system"},
5843  {(char *) "-s", glkunix_arg_NoValue,
5844   (char *) "-s         Debug STANDARD message handler"},
5845 #ifdef MEM_INFO
5846  {(char *) "-M", glkunix_arg_NoValue,
5847   (char *) "-M         Debug memory allocation"},
5848 #endif
5849  {(char *) "", glkunix_arg_ValueCanFollow,
5850   (char *) "filename   game to run"},
5851  {NULL, glkunix_arg_End, NULL}
5852 };
5853 
5854 */
5855 /*
5856  * glkunix_startup_code()
5857  *
5858  * Startup entry point for UNIX versions of Glk AGiliTy.  Glk will call
5859  * glkunix_startup_code() to pass in arguments.  On startup, we call our
5860  * function to parse arguments and generally set stuff up.
5861  */
5862 
glk_startup_code()5863 int glk_startup_code() {
5864 	assert(!gagt_startup_called);
5865 	gagt_startup_called = TRUE;
5866 
5867 	return gagt_startup_code();
5868 }
5869 
5870 } // End of namespace AGT
5871 } // End of namespace Glk
5872