1 /*
2  * winhelp.c   a module to generate Windows .HLP files
3  *
4  * Documentation of the .HLP file format comes from the excellent
5  * HELPFILE.TXT, published alongside the Help decompiler HELPDECO
6  * by Manfred Winterhoff. This code would not have been possible
7  * without his efforts. Many thanks.
8  */
9 
10 /*
11  * Potential future features:
12  *
13  *  - perhaps LZ77 compression? This appears to cause a phase order
14  *    problem: it's hard to do the compression until the data to be
15  *    compressed is finalised, and yet you can't finalise the data
16  *    to be compressed until you know how much of it is going into
17  *    which TOPICBLOCK in order to work out the offsets in the
18  *    topic headers - for which you have to have already done the
19  *    compression. Perhaps the thing to do is to implement an LZ77
20  *    compressor that can guarantee to leave particular bytes in
21  *    the stream as literals, and then go back and fix the offsets
22  *    up later. Not pleasant.
23  *
24  *  - It would be good to find out what relation (if any) the LCID
25  *    record in the |SYSTEM section bears to the codepage used in
26  *    the actual help text, so as to be able to vary that if the
27  *    user needs it. For the moment I suspect we're stuck with
28  *    Win1252.
29  *
30  *  - tables might be nice.
31  *
32  * Unlikely future features:
33  *
34  *  - Phrase compression sounds harder. It's reasonably easy
35  *    (though space-costly) to analyse all the text in the file to
36  *    determine the one key phrase which would save most space if
37  *    replaced by a reference everywhere it appears; but finding
38  *    the _1024_ most effective phrases seems much harder since a
39  *    naive analysis might find lots of phrases that all overlap
40  *    (so you wouldn't get the saving you expected, as after taking
41  *    out the first phrase the rest would never crop up). In
42  *    addition, MS hold US patent number 4955066 which may cover
43  *    phrase compression, so perhaps it's best just to leave it.
44  *
45  * Cleanup work:
46  *
47  *  - sort out begin_topic. Ideally we should have a separate
48  *    topic_macro function that adds to the existing linkdata for
49  *    the topic, because that's more flexible than a variadic
50  *    function. This will be fiddly, though: if it's called before
51  *    whlp_begin_topic then we must buffer macros, and if it's
52  *    called afterwards then we must be able to go back and modify
53  *    the linkdata2 of the topic start block. Foo.
54  *
55  *  - find out what should happen if a single topiclink crosses
56  *    _two_ topicblock boundaries.
57  *
58  *  - What is the BlockSize in a topic header (first 4 bytes of
59  *    LinkData1 in a type 2 record) supposed to mean? How on earth
60  *    is it measured? The help file doesn't become perceptibly
61  *    corrupt if I frob it randomly; and on some occasions taking a
62  *    bit _out_ of the help file _increases_ that value. I have a
63  *    feeling it's completely made up and/or vestigial, so for the
64  *    moment I'm just making up a plausible value as I go along.
65  */
66 
67 #include <stdlib.h>
68 #include <stdio.h>
69 #include <string.h>
70 #include <assert.h>
71 #include <time.h>
72 #include <stdarg.h>
73 
74 #include "halibut.h"
75 #include "winhelp.h"
76 #include "tree234.h"
77 
78 #ifdef WINHELP_TESTMODE
79 /*
80  * This lot is useful for testing. Something like it will also be
81  * needed to use this module standalone.
82  */
83 #define smalloc malloc
84 #define srealloc realloc
85 #define sfree free
86 #define snew(type) ( (type *) smalloc (sizeof (type)) )
87 #define snewn(number, type) ( (type *) smalloc ((number) * sizeof (type)) )
88 #define sresize(array, len, type) \
89 		( (type *) srealloc ((array), (len) * sizeof (type)) )
90 #define lenof(array) ( sizeof(array) / sizeof(*(array)) )
dupstr(char * s)91 char *dupstr(char *s) {
92     char *r = snewn(1+strlen(s), char); strcpy(r,s); return r;
93 }
94 #endif
95 
96 #define UNUSEDARG(x) ( (x) = (x) )
97 
98 #define GET_32BIT_LSB_FIRST(cp) \
99   (((unsigned long)(unsigned char)(cp)[0]) | \
100   ((unsigned long)(unsigned char)(cp)[1] << 8) | \
101   ((unsigned long)(unsigned char)(cp)[2] << 16) | \
102   ((unsigned long)(unsigned char)(cp)[3] << 24))
103 
104 #define PUT_32BIT_LSB_FIRST(cp, value) do { \
105   (cp)[0] = 0xFF & (value); \
106   (cp)[1] = 0xFF & ((value) >> 8); \
107   (cp)[2] = 0xFF & ((value) >> 16); \
108   (cp)[3] = 0xFF & ((value) >> 24); } while (0)
109 
110 #define GET_16BIT_LSB_FIRST(cp) \
111   (((unsigned long)(unsigned char)(cp)[0]) | \
112   ((unsigned long)(unsigned char)(cp)[1] << 8))
113 
114 #define PUT_16BIT_LSB_FIRST(cp, value) do { \
115   (cp)[0] = 0xFF & (value); \
116   (cp)[1] = 0xFF & ((value) >> 8); } while (0)
117 
118 #define MAX_PAGE_SIZE 0x800	       /* max page size in any B-tree */
119 #define TOPIC_BLKSIZE 4096	       /* implied by version/flags combo */
120 
121 typedef struct WHLP_TOPIC_tag context;
122 
123 struct file {
124     char *name;			       /* file name, will need freeing */
125     unsigned char *data;	       /* file data, will need freeing */
126     int pos;			       /* position for adding data */
127     int len;			       /* # of meaningful bytes in data */
128     int size;			       /* # of allocated bytes in data */
129     int fileoffset;		       /* offset in the real .HLP file */
130 };
131 
132 struct indexrec {
133     char *term;                        /* index term, will need freeing */
134     context *topic;                    /* topic it links to */
135     int count, offset;                 /* used when building |KWDATA */
136 };
137 
138 struct topiclink {
139     int topicoffset, topicpos;	       /* for referencing from elsewhere */
140     int recordtype;
141     int len1, len2;
142     unsigned char *data1, *data2;
143     context *context;
144     struct topiclink *nonscroll, *scroll, *nexttopic;
145     int block_size;		       /* for the topic header - *boggle* */
146 };
147 
148 struct WHLP_TOPIC_tag {
149     char *name;			       /* needs freeing */
150     unsigned long hash;
151     struct topiclink *link;	       /* this provides TOPICOFFSET */
152     context *browse_next, *browse_prev;
153     char *title;		       /* needs freeing */
154     int index;                         /* arbitrary number */
155 };
156 
157 struct fontdesc {
158     char *font;
159     int family, rendition, halfpoints;
160     int r, g, b;
161 };
162 
163 struct WHLP_tag {
164     tree234 *files;		       /* stores `struct file' */
165     tree234 *pre_contexts;	       /* stores `context' */
166     tree234 *contexts;		       /* also stores `context' */
167     tree234 *titles;		       /* _also_ stores `context' */
168     tree234 *text;		       /* stores `struct topiclink' */
169     tree234 *index;		       /* stores `struct indexrec' */
170     tree234 *tabstops;                 /* stores `int' */
171     tree234 *fontnames;		       /* stores `char *' */
172     tree234 *fontdescs;		       /* stores `struct fontdesc' */
173     struct file *systemfile;	       /* the |SYSTEM internal file */
174     context *ptopic;		       /* primary topic */
175     struct topiclink *prevtopic;       /* to link type-2 records together */
176     struct topiclink *link;	       /* while building a topiclink */
177     unsigned char linkdata1[TOPIC_BLKSIZE];   /* while building a topiclink */
178     unsigned char linkdata2[TOPIC_BLKSIZE];   /* while building a topiclink */
179     int topicblock_remaining;	       /* while building |TOPIC section */
180     int lasttopiclink;		       /* while building |TOPIC section */
181     int firsttopiclink_offset;	       /* while building |TOPIC section */
182     int lasttopicstart;		       /* while building |TOPIC section */
183     int para_flags;
184     int para_attrs[7];
185     int ncontexts;
186     int picture_index;
187 };
188 
189 /* Functions to return the index and leaf data for B-tree contents. */
190 typedef int (*bt_index_fn)(const void *item, unsigned char *outbuf);
191 typedef int (*bt_leaf_fn)(const void *item, unsigned char *outbuf);
192 
193 /* Forward references. */
194 static void whlp_para_reset(WHLP h);
195 static struct file *whlp_new_file(WHLP h, char *name);
196 static void whlp_file_add(struct file *f, const void *data, int len);
197 static void whlp_file_add_char(struct file *f, int data);
198 static void whlp_file_add_short(struct file *f, int data);
199 static void whlp_file_add_long(struct file *f, int data);
200 static void whlp_file_add_cushort(struct file *f, int data);
201 #if 0 /* currently unused */
202 static void whlp_file_add_csshort(struct file *f, int data);
203 #endif
204 static void whlp_file_add_culong(struct file *f, int data);
205 #if 0 /* currently unused */
206 static void whlp_file_add_cslong(struct file *f, int data);
207 #endif
208 static void whlp_file_fill(struct file *f, int len);
209 static void whlp_file_seek(struct file *f, int pos, int whence);
210 static int whlp_file_offset(struct file *f);
211 
212 /* ----------------------------------------------------------------------
213  * Fiddly little functions: B-tree compare, index and leaf functions.
214  */
215 
216 /* The master index maps file names to help-file offsets. */
217 
filecmp(void * av,void * bv)218 static int filecmp(void *av, void *bv)
219 {
220     const struct file *a = (const struct file *)av;
221     const struct file *b = (const struct file *)bv;
222     return strcmp(a->name, b->name);
223 }
224 
fileindex(const void * av,unsigned char * outbuf)225 static int fileindex(const void *av, unsigned char *outbuf)
226 {
227     const struct file *a = (const struct file *)av;
228     int len = 1+strlen(a->name);
229     memcpy(outbuf, a->name, len);
230     return len;
231 }
232 
fileleaf(const void * av,unsigned char * outbuf)233 static int fileleaf(const void *av, unsigned char *outbuf)
234 {
235     const struct file *a = (const struct file *)av;
236     int len = 1+strlen(a->name);
237     memcpy(outbuf, a->name, len);
238     PUT_32BIT_LSB_FIRST(outbuf+len, a->fileoffset);
239     return len+4;
240 }
241 
242 /* The |CONTEXT internal file maps help context hashes to TOPICOFFSETs. */
243 
ctxcmp(void * av,void * bv)244 static int ctxcmp(void *av, void *bv)
245 {
246     const context *a = (const context *)av;
247     const context *b = (const context *)bv;
248     if ((signed long)a->hash < (signed long)b->hash)
249 	return -1;
250     if ((signed long)a->hash > (signed long)b->hash)
251 	return +1;
252     return 0;
253 }
254 
ctxindex(const void * av,unsigned char * outbuf)255 static int ctxindex(const void *av, unsigned char *outbuf)
256 {
257     const context *a = (const context *)av;
258     PUT_32BIT_LSB_FIRST(outbuf, a->hash);
259     return 4;
260 }
261 
ctxleaf(const void * av,unsigned char * outbuf)262 static int ctxleaf(const void *av, unsigned char *outbuf)
263 {
264     const context *a = (const context *)av;
265     PUT_32BIT_LSB_FIRST(outbuf, a->hash);
266     PUT_32BIT_LSB_FIRST(outbuf+4, a->link->topicoffset);
267     return 8;
268 }
269 
270 /* The |TTLBTREE internal file maps TOPICOFFSETs to title strings. */
271 
ttlcmp(void * av,void * bv)272 static int ttlcmp(void *av, void *bv)
273 {
274     const context *a = (const context *)av;
275     const context *b = (const context *)bv;
276     if (a->link->topicoffset < b->link->topicoffset)
277 	return -1;
278     if (a->link->topicoffset > b->link->topicoffset)
279 	return +1;
280     return 0;
281 }
282 
ttlindex(const void * av,unsigned char * outbuf)283 static int ttlindex(const void *av, unsigned char *outbuf)
284 {
285     const context *a = (const context *)av;
286     PUT_32BIT_LSB_FIRST(outbuf, a->link->topicoffset);
287     return 4;
288 }
289 
ttlleaf(const void * av,unsigned char * outbuf)290 static int ttlleaf(const void *av, unsigned char *outbuf)
291 {
292     const context *a = (const context *)av;
293     int slen;
294     PUT_32BIT_LSB_FIRST(outbuf, a->link->topicoffset);
295     slen = 1+strlen(a->title);
296     memcpy(outbuf+4, a->title, slen);
297     return 4+slen;
298 }
299 
300 /* The |KWBTREE internal file maps index strings to TOPICOFFSETs. */
301 
idxcmp(void * av,void * bv)302 static int idxcmp(void *av, void *bv)
303 {
304     const struct indexrec *a = (const struct indexrec *)av;
305     const struct indexrec *b = (const struct indexrec *)bv;
306     int cmp;
307     if ( (cmp = strcmp(a->term, b->term)) != 0)
308         return cmp;
309     /* Now sort on the index field of the topics. */
310     if (a->topic->index < b->topic->index)
311 	return -1;
312     if (a->topic->index > b->topic->index)
313 	return +1;
314     return 0;
315 }
316 
idxindex(const void * av,unsigned char * outbuf)317 static int idxindex(const void *av, unsigned char *outbuf)
318 {
319     const struct indexrec *a = (const struct indexrec *)av;
320     int len = 1+strlen(a->term);
321     memcpy(outbuf, a->term, len);
322     return len;
323 }
324 
idxleaf(const void * av,unsigned char * outbuf)325 static int idxleaf(const void *av, unsigned char *outbuf)
326 {
327     const struct indexrec *a = (const struct indexrec *)av;
328     int len = 1+strlen(a->term);
329     memcpy(outbuf, a->term, len);
330     PUT_16BIT_LSB_FIRST(outbuf+len, a->count);
331     PUT_32BIT_LSB_FIRST(outbuf+len+2, a->offset);
332     return len+6;
333 }
334 
335 /*
336  * The internal `tabstops' B-tree stores pointers-to-int. Sorting
337  * is by the low 16 bits of the number (above that is flags).
338  */
339 
tabcmp(void * av,void * bv)340 static int tabcmp(void *av, void *bv)
341 {
342     const int *a = (const int *)av;
343     const int *b = (const int *)bv;
344     if ((*a & 0xFFFF) < (*b & 0xFFFF))
345 	return -1;
346     if ((*a & 0xFFFF) > (*b & 0xFFFF))
347 	return +1;
348     return 0;
349 }
350 
351 /* The internal `fontnames' B-tree stores strings. */
fontcmp(void * av,void * bv)352 static int fontcmp(void *av, void *bv)
353 {
354     const char *a = (const char *)av;
355     const char *b = (const char *)bv;
356     return strcmp(a,b);
357 }
358 
359 /* ----------------------------------------------------------------------
360  * Manage help contexts and topics.
361  */
362 
363 /*
364  * This is the code to compute the hash of a context name. Copied
365  * straight from Winterhoff's documentation.
366  */
context_hash(char * context)367 static unsigned long context_hash(char *context)
368 {
369     signed char bytemapping[256] =
370 	"\x00\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF"
371 	"\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF"
372 	"\xF0\x0B\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\x0C\xFF"
373 	"\x0A\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"
374 	"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
375 	"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2A\x0B\x0C\x0D\x0E\x0D"
376 	"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
377 	"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2A\x2B\x2C\x2D\x2E\x2F"
378 	"\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5A\x5B\x5C\x5D\x5E\x5F"
379 	"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6A\x6B\x6C\x6D\x6E\x6F"
380 	"\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7A\x7B\x7C\x7D\x7E\x7F"
381 	"\x80\x81\x82\x83\x0B\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F"
382 	"\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F"
383 	"\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF"
384 	"\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF"
385 	"\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF";
386     unsigned long hash;
387 
388     /*
389      * The hash algorithm starts the hash at 0 and updates it with
390      * each character. Therefore, logically, the hash of an empty
391      * string should be 0 (it starts at 0 and is never updated);
392      * but Winterhoff says it is in fact 1. Shouldn't matter, since
393      * I never plan to use empty context names, but I'll stick the
394      * special case in here anyway.
395      */
396     if (!*context)
397 	return 1;
398 
399     /*
400      * Now compute the hash in the normal way.
401      */
402     hash = 0;
403     while (*context) {
404         /*
405          * Be careful of overflowing `unsigned long', for maximum
406          * portability.
407          */
408 
409         /*
410          * Multiply `hash' by 43.
411          */
412         {
413             unsigned long bottom, top;
414             bottom = (hash & 0xFFFFUL) * 43;
415             top = ((hash >> 16) & 0xFFFFUL) * 43;
416             top += (bottom >> 16);
417             bottom &= 0xFFFFUL;
418             top &= 0xFFFFUL;
419             hash = (top << 16) | bottom;
420         }
421 
422         /*
423          * Add the mapping value for this byte to `hash'.
424          */
425         {
426             int val = bytemapping[(unsigned char)*context];
427 
428             if (val > 0 && hash > (0xFFFFFFFFUL - val)) {
429                 hash -= (0xFFFFFFFFUL - val) + 1;
430             } else if (val < 0 && hash < (unsigned long)-val) {
431                 hash += (0xFFFFFFFFUL + val) + 1;
432             } else
433                 hash += val;
434         }
435 
436 	context++;
437     }
438     return hash;
439 }
440 
whlp_register_topic(WHLP h,char * context_name,char ** clash)441 WHLP_TOPIC whlp_register_topic(WHLP h, char *context_name, char **clash)
442 {
443     context *ctx = snew(context);
444     context *otherctx;
445 
446     /*
447      * Index contexts in order of creation, just so there's some
448      * sort of non-arbitrary ordering in the index B-tree. Call me
449      * fussy, but I don't like indexing on pointer values because I
450      * prefer the code to be deterministic when run under different
451      * C libraries.
452      */
453     ctx->index = h->ncontexts++;
454     ctx->browse_prev = ctx->browse_next = NULL;
455 
456     if (context_name) {
457 	/*
458 	 * We have a context name, which means we can put this
459 	 * context straight into the `contexts' tree.
460 	 */
461 	ctx->name = dupstr(context_name);
462 	ctx->hash = context_hash(context_name);
463 	otherctx = add234(h->contexts, ctx);
464 	if (otherctx != ctx) {
465 	    /*
466 	     * Hash clash. Destroy the new context and return NULL,
467 	     * providing the clashing string.
468 	     */
469 	    sfree(ctx->name);
470 	    sfree(ctx);
471 	    if (clash) *clash = otherctx->name;
472 	    return NULL;
473 	}
474     } else {
475 	/*
476 	 * We have no context name yet. Enter this into the
477 	 * pre_contexts tree of anonymous topics, which we will go
478 	 * through later and allocate unique context names and hash
479 	 * values.
480 	 */
481 	ctx->name = NULL;
482 	addpos234(h->pre_contexts, ctx, count234(h->pre_contexts));
483     }
484     return ctx;
485 }
486 
whlp_prepare(WHLP h)487 void whlp_prepare(WHLP h)
488 {
489     /*
490      * We must go through pre_contexts and allocate a context ID to
491      * each anonymous context, making sure it doesn't clash with
492      * the existing contexts.
493      *
494      * Our own context IDs will just be of the form `t00000001',
495      * and we'll increment the number each time and skip over any
496      * IDs that clash with existing context names.
497      */
498     int ctx_num = 0;
499     context *ctx, *otherctx;
500 
501     while ( (ctx = index234(h->pre_contexts, 0)) != NULL ) {
502 	delpos234(h->pre_contexts, 0);
503 	ctx->name = snewn(20, char);
504 	do {
505 	    sprintf(ctx->name, "t%08d", ctx_num++);
506 	    ctx->hash = context_hash(ctx->name);
507 	    otherctx = add234(h->contexts, ctx);
508 	} while (otherctx != ctx);
509     }
510 
511     /*
512      * Ensure paragraph attributes are clear for the start of text
513      * output.
514      */
515     whlp_para_reset(h);
516 }
517 
whlp_topic_id(WHLP_TOPIC topic)518 char *whlp_topic_id(WHLP_TOPIC topic)
519 {
520     return topic->name;
521 }
522 
whlp_begin_topic(WHLP h,WHLP_TOPIC topic,char * title,...)523 void whlp_begin_topic(WHLP h, WHLP_TOPIC topic, char *title, ...)
524 {
525     struct topiclink *link = snew(struct topiclink);
526     int len, slen;
527     char *macro;
528     va_list ap;
529 
530     link->nexttopic = NULL;
531     if (h->prevtopic)
532 	h->prevtopic->nexttopic = link;
533     h->prevtopic = link;
534 
535     link->nonscroll = link->scroll = NULL;
536     link->context = topic;
537     link->block_size = 0;
538 
539     link->recordtype = 2;	       /* topic header */
540     link->len1 = 4*7;		       /* standard linkdata1 size */
541     link->data1 = snewn(link->len1, unsigned char);
542 
543     slen = strlen(title);
544     assert(slen+1 <= TOPIC_BLKSIZE);
545     memcpy(h->linkdata2, title, slen+1);
546     len = slen+1;
547 
548     va_start(ap, title);
549     while ( (macro = va_arg(ap, char *)) != NULL) {
550 	slen = strlen(macro);
551 	assert(len+slen+1 <= TOPIC_BLKSIZE);
552 	memcpy(h->linkdata2+len, macro, slen+1);
553 	len += slen+1;
554     }
555     va_end(ap);
556     len--;			       /* lose the last \0 on the last macro */
557 
558     link->len2 = len;
559     link->data2 = snewn(link->len2, unsigned char);
560     memcpy(link->data2, h->linkdata2, link->len2);
561 
562     topic->title = dupstr(title);
563     topic->link = link;
564 
565     addpos234(h->text, link, count234(h->text));
566 }
567 
whlp_browse_link(WHLP h,WHLP_TOPIC before,WHLP_TOPIC after)568 void whlp_browse_link(WHLP h, WHLP_TOPIC before, WHLP_TOPIC after)
569 {
570     UNUSEDARG(h);
571 
572     /*
573      * See if the `before' topic is already linked to another one,
574      * and break the link to that if so. Likewise the `after'
575      * topic.
576      */
577     if (before->browse_next)
578         before->browse_next->browse_prev = NULL;
579     if (after->browse_prev)
580         after->browse_prev->browse_next = NULL;
581     before->browse_next = after;
582     after->browse_prev = before;
583 }
584 
585 /* ----------------------------------------------------------------------
586  * Manage the actual generation of paragraph and text records.
587  */
588 
whlp_linkdata(WHLP h,int which,int c)589 static void whlp_linkdata(WHLP h, int which, int c)
590 {
591     int *len = (which == 1 ? &h->link->len1 : &h->link->len2);
592     unsigned char *data = (which == 1 ? h->linkdata1 : h->linkdata2);
593     assert(*len < TOPIC_BLKSIZE);
594     data[(*len)++] = c;
595 }
596 
whlp_linkdata_short(WHLP h,int which,int data)597 static void whlp_linkdata_short(WHLP h, int which, int data)
598 {
599     whlp_linkdata(h, which, data & 0xFF);
600     whlp_linkdata(h, which, (data >> 8) & 0xFF);
601 }
602 
whlp_linkdata_long(WHLP h,int which,int data)603 static void whlp_linkdata_long(WHLP h, int which, int data)
604 {
605     whlp_linkdata(h, which, data & 0xFF);
606     whlp_linkdata(h, which, (data >> 8) & 0xFF);
607     whlp_linkdata(h, which, (data >> 16) & 0xFF);
608     whlp_linkdata(h, which, (data >> 24) & 0xFF);
609 }
610 
whlp_linkdata_cushort(WHLP h,int which,int data)611 static void whlp_linkdata_cushort(WHLP h, int which, int data)
612 {
613     if (data <= 0x7F) {
614 	whlp_linkdata(h, which, data*2);
615     } else {
616 	whlp_linkdata(h, which, 1 + (data%128 * 2));
617 	whlp_linkdata(h, which, data/128);
618     }
619 }
620 
whlp_linkdata_csshort(WHLP h,int which,int data)621 static void whlp_linkdata_csshort(WHLP h, int which, int data)
622 {
623     if (data >= -0x40 && data <= 0x3F)
624 	whlp_linkdata_cushort(h, which, data+64);
625     else
626 	whlp_linkdata_cushort(h, which, data+16384);
627 }
628 
whlp_linkdata_culong(WHLP h,int which,int data)629 static void whlp_linkdata_culong(WHLP h, int which, int data)
630 {
631     if (data <= 0x7FFF) {
632 	whlp_linkdata_short(h, which, data*2);
633     } else {
634 	whlp_linkdata_short(h, which, 1 + (data%32768 * 2));
635 	whlp_linkdata_short(h, which, data/32768);
636     }
637 }
638 
whlp_linkdata_cslong(WHLP h,int which,int data)639 static void whlp_linkdata_cslong(WHLP h, int which, int data)
640 {
641     if (data >= -0x4000 && data <= 0x3FFF)
642 	whlp_linkdata_culong(h, which, data+16384);
643     else
644 	whlp_linkdata_culong(h, which, data+67108864);
645 }
646 
whlp_para_reset(WHLP h)647 static void whlp_para_reset(WHLP h)
648 {
649     int *p;
650 
651     h->para_flags = 0;
652 
653     while ( (p = index234(h->tabstops, 0)) != NULL) {
654         delpos234(h->tabstops, 0);
655         sfree(p);
656     }
657 }
658 
whlp_para_attr(WHLP h,int attr_id,int attr_param)659 void whlp_para_attr(WHLP h, int attr_id, int attr_param)
660 {
661     if (attr_id >= WHLP_PARA_SPACEABOVE &&
662 	attr_id <= WHLP_PARA_FIRSTLINEINDENT) {
663 	h->para_flags |= 1 << attr_id;
664 	h->para_attrs[attr_id] = attr_param;
665     } else if (attr_id == WHLP_PARA_ALIGNMENT) {
666 	h->para_flags &= ~0xC00;
667 	if (attr_param == WHLP_ALIGN_RIGHT)
668 	    h->para_flags |= 0x400;
669 	else if (attr_param == WHLP_ALIGN_CENTRE)
670 	    h->para_flags |= 0x800;
671     }
672 }
673 
whlp_set_tabstop(WHLP h,int tabstop,int alignment)674 void whlp_set_tabstop(WHLP h, int tabstop, int alignment)
675 {
676     int *p;
677 
678     if (alignment == WHLP_ALIGN_CENTRE)
679         tabstop |= 0x20000;
680     if (alignment == WHLP_ALIGN_RIGHT)
681         tabstop |= 0x10000;
682 
683     p = snew(int);
684     *p = tabstop;
685     add234(h->tabstops, p);
686     h->para_flags |= 0x0200;
687 }
688 
whlp_begin_para(WHLP h,int para_type)689 void whlp_begin_para(WHLP h, int para_type)
690 {
691     struct topiclink *link = snew(struct topiclink);
692     int i;
693 
694     /*
695      * Clear these to NULL out of paranoia, although in records
696      * that aren't type 2 they should never actually be needed.
697      */
698     link->nexttopic = NULL;
699     link->context = NULL;
700     link->nonscroll = link->scroll = NULL;
701 
702     link->recordtype = 32;	       /* text record */
703 
704     h->link = link;
705     link->len1 = link->len2 = 0;
706     link->data1 = h->linkdata1;
707     link->data2 = h->linkdata2;
708 
709     if (para_type == WHLP_PARA_NONSCROLL && h->prevtopic &&
710 	!h->prevtopic->nonscroll)
711 	h->prevtopic->nonscroll = link;
712     if (para_type == WHLP_PARA_SCROLL && h->prevtopic &&
713 	!h->prevtopic->scroll)
714 	h->prevtopic->scroll = link;
715 
716     /*
717      * Now we're ready to start accumulating stuff in linkdata1 and
718      * linkdata2. Next we build up the paragraph info. Note that
719      * the TopicSize (cslong: size of LinkData1 minus the topicsize
720      * and topiclength fields) and TopicLength (cushort: size of
721      * LinkData2) fields are missing; we will put those on when we
722      * end the paragraph.
723      */
724     whlp_linkdata(h, 1, 0);	       /* must-be-0x00 */
725     whlp_linkdata(h, 1, 0x80);	       /* must-be-0x80 */
726     whlp_linkdata_short(h, 1, 0); /* Winterhoff says `id'; always 0 AFAICT */
727     whlp_linkdata_short(h, 1, h->para_flags);
728     for (i = WHLP_PARA_SPACEABOVE; i <= WHLP_PARA_FIRSTLINEINDENT; i++) {
729 	if (h->para_flags & (1<<i))
730 	    whlp_linkdata_csshort(h, 1, h->para_attrs[i]);
731     }
732     if (h->para_flags & 0x0200) {
733         int ntabs;
734         /*
735          * Write out tab stop data.
736          */
737         ntabs = count234(h->tabstops);
738         whlp_linkdata_csshort(h, 1, ntabs);
739         for (i = 0; i < ntabs; i++) {
740             int tab, *tabp;
741             tabp = index234(h->tabstops, i);
742             tab = *tabp;
743             if (tab & 0x30000)
744                 tab |= 0x4000;
745             whlp_linkdata_cushort(h, 1, tab & 0xFFFF);
746             if (tab & 0x4000)
747                 whlp_linkdata_cushort(h, 1, tab >> 16);
748         }
749     }
750 
751     /*
752      * Fine. Now we're ready to start writing actual text and
753      * formatting commands.
754      */
755 }
756 
whlp_set_font(WHLP h,int font_id)757 void whlp_set_font(WHLP h, int font_id)
758 {
759     /*
760      * Write a NUL into linkdata2 to cause the reader to flip over
761      * to linkdata1 to see the formatting command.
762      */
763     whlp_linkdata(h, 2, 0);
764     /*
765      * Now the formatting command is 0x80 followed by a short.
766      */
767     whlp_linkdata(h, 1, 0x80);
768     whlp_linkdata_short(h, 1, font_id);
769 }
770 
whlp_start_hyperlink(WHLP h,WHLP_TOPIC target)771 void whlp_start_hyperlink(WHLP h, WHLP_TOPIC target)
772 {
773     /*
774      * Write a NUL into linkdata2.
775      */
776     whlp_linkdata(h, 2, 0);
777     /*
778      * Now the formatting command is 0xE3 followed by the context
779      * hash.
780      */
781     whlp_linkdata(h, 1, 0xE3);
782     whlp_linkdata_long(h, 1, target->hash);
783 }
784 
whlp_end_hyperlink(WHLP h)785 void whlp_end_hyperlink(WHLP h)
786 {
787     /*
788      * Write a NUL into linkdata2.
789      */
790     whlp_linkdata(h, 2, 0);
791     /*
792      * Now the formatting command is 0x89.
793      */
794     whlp_linkdata(h, 1, 0x89);
795 }
796 
whlp_tab(WHLP h)797 void whlp_tab(WHLP h)
798 {
799     /*
800      * Write a NUL into linkdata2.
801      */
802     whlp_linkdata(h, 2, 0);
803     /*
804      * Now the formatting command is 0x83.
805      */
806     whlp_linkdata(h, 1, 0x83);
807 }
808 
whlp_add_picture(WHLP h,int wd,int ht,const void * vpicdata,const unsigned long * palette)809 int whlp_add_picture(WHLP h, int wd, int ht, const void *vpicdata,
810 		     const unsigned long *palette)
811 {
812     struct file *f;
813     char filename[80];
814     const unsigned char *picdata = (const unsigned char *)vpicdata;
815     int picstart, picoff, imgoff, imgstart;
816     int palettelen;
817     int i, index;
818     int wdrounded;
819 
820     /*
821      * Determine the limit of the colour palette.
822      */
823     palettelen = -1;
824     for (i = 0; i < wd*ht; i++)
825 	if (palettelen < picdata[i])
826 	    palettelen = picdata[i];
827     palettelen++;
828 
829     /*
830      * Round up the width to the next multiple of 4.
831      */
832     wdrounded = (wd + 3) & ~3;
833 
834     index = h->picture_index++;
835     sprintf(filename, "bm%d", index);
836 
837     f = whlp_new_file(h, filename);
838     whlp_file_add_short(f, 0x706C);    /* magic number */
839     whlp_file_add_short(f, 1);	       /* number of pictures */
840     picoff = whlp_file_offset(f);
841     whlp_file_add_long(f, 0);	       /* offset of first (only) picture */
842     picstart = whlp_file_offset(f);
843     whlp_file_add_char(f, 6);	       /* DIB */
844     whlp_file_add_char(f, 0);	       /* no packing */
845     whlp_file_add_culong(f, 100);      /* xdpi */
846     whlp_file_add_culong(f, 100);      /* ydpi */
847     whlp_file_add_cushort(f, 1);       /* planes (?) */
848     whlp_file_add_cushort(f, 8);       /* bitcount */
849     whlp_file_add_culong(f, wd);       /* width */
850     whlp_file_add_culong(f, ht);       /* height */
851     whlp_file_add_culong(f, palettelen);/* colours used */
852     whlp_file_add_culong(f, palettelen);/* colours important */
853     whlp_file_add_culong(f, wdrounded*ht);    /* `compressed' data size */
854     whlp_file_add_culong(f, 0);	       /* hotspot size (no hotspots) */
855     imgoff = whlp_file_offset(f);
856     whlp_file_add_long(f, 0);	       /* offset of `compressed' data */
857     whlp_file_add_long(f, 0);	       /* offset of hotspot data (none) */
858     for (i = 0; i < palettelen; i++)
859 	whlp_file_add_long(f, palette[i]);
860     imgstart = whlp_file_offset(f);
861     /*
862      * Windows Help files, like BMP, start from the bottom scanline.
863      */
864     for (i = ht; i-- > 0 ;) {
865 	whlp_file_add(f, picdata + i*wd, wd);
866 	if (wd < wdrounded)
867 	    whlp_file_add(f, "\0\0\0", wdrounded - wd);
868     }
869 
870     /* Now go back and fix up internal offsets */
871     whlp_file_seek(f, picoff, 0);
872     whlp_file_add_long(f, picstart);
873     whlp_file_seek(f, imgoff, 0);
874     whlp_file_add_long(f, imgstart - picstart);
875     whlp_file_seek(f, 0, 2);
876 
877     return index;
878 }
879 
whlp_ref_picture(WHLP h,int picid)880 void whlp_ref_picture(WHLP h, int picid)
881 {
882     /*
883      * Write a NUL into linkdata2.
884      */
885     whlp_linkdata(h, 2, 0);
886     /*
887      * Write the formatting command and its followup data to
888      * specify a picture in a separate file.
889      */
890     whlp_linkdata(h, 1, 0x86);
891     whlp_linkdata(h, 1, 3);	       /* type (picture without hotspots) */
892     whlp_linkdata_cslong(h, 1, 4);
893     whlp_linkdata_short(h, 1, 0);
894     whlp_linkdata_short(h, 1, picid);
895 }
896 
whlp_text(WHLP h,char * text)897 void whlp_text(WHLP h, char *text)
898 {
899     while (*text) {
900 	whlp_linkdata(h, 2, *text++);
901     }
902 }
903 
whlp_end_para(WHLP h)904 void whlp_end_para(WHLP h)
905 {
906     int data1cut;
907 
908     /*
909      * Round off the paragraph with 0x82 and 0xFF formatting
910      * commands. Each requires a NUL in linkdata2.
911      */
912     whlp_linkdata(h, 2, 0);
913     whlp_linkdata(h, 1, 0x82);
914     whlp_linkdata(h, 2, 0);
915     whlp_linkdata(h, 1, 0xFF);
916 
917     /*
918      * Now finish up: create the header of linkdata1 (TopicLength
919      * and TopicSize fields), allocate the real linkdata1 and
920      * linkdata2 fields, and copy them out of the buffers in h.
921      * Then insert the finished topiclink into the `text' tree, and
922      * clean up.
923      */
924     data1cut = h->link->len1;
925     whlp_linkdata_cslong(h, 1, data1cut);
926     whlp_linkdata_cushort(h, 1, h->link->len2);
927 
928     h->link->data1 = snewn(h->link->len1, unsigned char);
929     memcpy(h->link->data1, h->linkdata1 + data1cut, h->link->len1 - data1cut);
930     memcpy(h->link->data1 + h->link->len1 - data1cut, h->linkdata1, data1cut);
931     h->link->data2 = snewn(h->link->len2, unsigned char);
932     memcpy(h->link->data2, h->linkdata2, h->link->len2);
933 
934     addpos234(h->text, h->link, count234(h->text));
935 
936     /* Hack: accumulate the `blocksize' parameter in the topic header. */
937     if (h->prevtopic)
938 	h->prevtopic->block_size += 21 + h->link->len1 + h->link->len2;
939 
940     h->link = NULL;		       /* this is now in the tree */
941 
942     whlp_para_reset(h);
943 }
944 
945 /* ----------------------------------------------------------------------
946  * Manage the layout and generation of the |TOPIC section.
947  */
948 
whlp_topicsect_write(WHLP h,struct file * f,void * data,int len,int can_break)949 static void whlp_topicsect_write(WHLP h, struct file *f, void *data, int len,
950 				 int can_break)
951 {
952     unsigned char *p = (unsigned char *)data;
953 
954     if (h->topicblock_remaining <= 0 ||
955 	h->topicblock_remaining < can_break) {
956 	/*
957 	 * Start a new block.
958 	 */
959 	if (h->topicblock_remaining > 0)
960 	    whlp_file_fill(f, h->topicblock_remaining);
961 	whlp_file_add_long(f, h->lasttopiclink);
962 	h->firsttopiclink_offset = whlp_file_offset(f);
963 	whlp_file_add_long(f, -1L);    /* this will be filled in later */
964 	whlp_file_add_long(f, h->lasttopicstart);
965 	h->topicblock_remaining = TOPIC_BLKSIZE - 12;
966     }
967     while (len > 0) {
968 	int thislen = (h->topicblock_remaining < len ?
969 		       h->topicblock_remaining : len);
970 	whlp_file_add(f, p, thislen);
971 	p += thislen;
972 	len -= thislen;
973 	h->topicblock_remaining -= thislen;
974 	if (len > 0 && h->topicblock_remaining <= 0) {
975 	    /*
976 	     * Start a new block.
977 	     */
978 	    whlp_file_add_long(f, h->lasttopiclink);
979 	    h->firsttopiclink_offset = whlp_file_offset(f);
980 	    whlp_file_add_long(f, -1L);    /* this will be filled in later */
981 	    whlp_file_add_long(f, h->lasttopicstart);
982 	    h->topicblock_remaining = TOPIC_BLKSIZE - 12;
983 	}
984     }
985 }
986 
whlp_topic_layout(WHLP h)987 static void whlp_topic_layout(WHLP h)
988 {
989     int block, offset, pos;
990     int i, nlinks, size;
991     int topicnum;
992     struct topiclink *link;
993     struct file *f;
994 
995     /*
996      * Create a final TOPICLINK containing no usable data.
997      */
998     link = snew(struct topiclink);
999     link->nexttopic = NULL;
1000     if (h->prevtopic)
1001 	h->prevtopic->nexttopic = link;
1002     h->prevtopic = link;
1003     link->data1 = snewn(0x1c, unsigned char);
1004     link->block_size = 0;
1005     link->data2 = NULL;
1006     link->len1 = 0x1c;
1007     link->len2 = 0;
1008     link->nexttopic = NULL;
1009     link->recordtype = 2;
1010     link->nonscroll = link->scroll = NULL;
1011     link->context = NULL;
1012     addpos234(h->text, link, count234(h->text));
1013 
1014     /*
1015      * Each TOPICBLOCK has space for TOPIC_BLKSIZE-12 bytes. The
1016      * size of each TOPICLINK is 21 bytes plus the combined lengths
1017      * of LinkData1 and LinkData2. So we can now go through and
1018      * break up the TOPICLINKs into TOPICBLOCKs, and also set up
1019      * the TOPICOFFSET and TOPICPOS of each one while we do so.
1020      */
1021 
1022     block = 0;
1023     offset = 0;
1024     pos = 12;
1025     nlinks = count234(h->text);
1026     for (i = 0; i < nlinks; i++) {
1027 	link = index234(h->text, i);
1028 	size = 21 + link->len1 + link->len2;
1029 	/*
1030 	 * We can't split within the topicblock header or within
1031 	 * linkdata1. So if the split would fall in that area,
1032 	 * start a new block _now_.
1033 	 */
1034 	if (TOPIC_BLKSIZE - pos < 21 + link->len1) {
1035 	    block++;
1036 	    offset = 0;
1037 	    pos = 12;
1038 	}
1039 	link->topicoffset = block * 0x8000 + offset;
1040 	link->topicpos = block * 0x4000 + pos;
1041 	pos += size;
1042 	if (link->recordtype != 2)     /* TOPICOFFSET doesn't count titles */
1043 	    offset += link->len2;
1044 	while (pos > TOPIC_BLKSIZE) {
1045 	    block++;
1046 	    offset = 0;
1047 	    pos -= TOPIC_BLKSIZE - 12;
1048 	}
1049     }
1050 
1051     /*
1052      * Now we have laid out the TOPICLINKs into blocks, and
1053      * determined the final TOPICOFFSET and TOPICPOS of each one.
1054      * So now we can go through and write the headers of the type-2
1055      * records.
1056      */
1057 
1058     topicnum = 0;
1059     for (i = 0; i < nlinks; i++) {
1060 	link = index234(h->text, i);
1061 	if (link->recordtype != 2)
1062 	    continue;
1063 
1064 	PUT_32BIT_LSB_FIRST(link->data1 + 0, link->block_size);
1065 	if (link->context && link->context->browse_prev)
1066 	    PUT_32BIT_LSB_FIRST(link->data1 + 4,
1067 				link->context->browse_prev->link->topicoffset);
1068 	else
1069 	    PUT_32BIT_LSB_FIRST(link->data1 + 4, 0xFFFFFFFFL);
1070 	if (link->context && link->context->browse_next)
1071 	    PUT_32BIT_LSB_FIRST(link->data1 + 8,
1072 				link->context->browse_next->link->topicoffset);
1073 	else
1074 	    PUT_32BIT_LSB_FIRST(link->data1 + 8, 0xFFFFFFFFL);
1075 	PUT_32BIT_LSB_FIRST(link->data1 + 12, topicnum);
1076 	topicnum++;
1077 	if (link->nonscroll)
1078 	    PUT_32BIT_LSB_FIRST(link->data1 + 16, link->nonscroll->topicpos);
1079 	else
1080 	    PUT_32BIT_LSB_FIRST(link->data1 + 16, 0xFFFFFFFFL);
1081 	if (link->scroll)
1082 	    PUT_32BIT_LSB_FIRST(link->data1 + 20, link->scroll->topicpos);
1083 	else
1084 	    PUT_32BIT_LSB_FIRST(link->data1 + 20, 0xFFFFFFFFL);
1085 	if (link->nexttopic)
1086 	    PUT_32BIT_LSB_FIRST(link->data1 + 24, link->nexttopic->topicpos);
1087 	else
1088 	    PUT_32BIT_LSB_FIRST(link->data1 + 24, 0xFFFFFFFFL);
1089     }
1090 
1091     /*
1092      * Having done all _that_, we're now finally ready to go
1093      * through and create the |TOPIC section in its final form.
1094      */
1095 
1096     h->lasttopiclink = -1L;
1097     h->lasttopicstart = 0L;
1098     f = whlp_new_file(h, "|TOPIC");
1099     h->topicblock_remaining = -1;
1100     whlp_topicsect_write(h, f, NULL, 0, 0);   /* start the first block */
1101     for (i = 0; i < nlinks; i++) {
1102 	unsigned char header[21];
1103 	struct topiclink *otherlink;
1104 
1105 	link = index234(h->text, i);
1106 
1107 	/*
1108 	 * Create and output the TOPICLINK header.
1109 	 */
1110 	PUT_32BIT_LSB_FIRST(header + 0, 21 + link->len1 + link->len2);
1111 	PUT_32BIT_LSB_FIRST(header + 4, link->len2);
1112 	if (i == 0) {
1113 	    PUT_32BIT_LSB_FIRST(header + 8, 0xFFFFFFFFL);
1114 	} else {
1115 	    otherlink = index234(h->text, i-1);
1116 	    PUT_32BIT_LSB_FIRST(header + 8, otherlink->topicpos);
1117 	}
1118 	if (i+1 >= nlinks) {
1119 	    PUT_32BIT_LSB_FIRST(header + 12, 0xFFFFFFFFL);
1120 	} else {
1121 	    otherlink = index234(h->text, i+1);
1122 	    PUT_32BIT_LSB_FIRST(header + 12, otherlink->topicpos);
1123 	}
1124 	PUT_32BIT_LSB_FIRST(header + 16, 21 + link->len1);
1125 	header[20] = link->recordtype;
1126 	whlp_topicsect_write(h, f, header, 21, 21 + link->len1);
1127 
1128 	/*
1129 	 * Fill in the `first topiclink' pointer in the block
1130 	 * header if appropriate. (We do this _after_ outputting
1131 	 * the header because then we can be sure we'll be in the
1132 	 * same block as we think we are.)
1133 	 */
1134 	if (h->firsttopiclink_offset > 0) {
1135 	    whlp_file_seek(f, h->firsttopiclink_offset, 0);
1136 	    whlp_file_add_long(f, link->topicpos);
1137 	    h->firsttopiclink_offset = 0;
1138 	    whlp_file_seek(f, 0, 2);
1139 	}
1140 
1141 	/*
1142 	 * Update the `last topiclink', and possibly `last
1143 	 * topicstart', pointers.
1144 	 */
1145 	h->lasttopiclink = link->topicpos;
1146 	if (link->recordtype == 2)
1147 	    h->lasttopicstart = link->topicpos;
1148 
1149 
1150 	/*
1151 	 * Output LinkData1 and LinkData2.
1152 	 */
1153 	whlp_topicsect_write(h, f, link->data1, link->len1, link->len1);
1154 	whlp_topicsect_write(h, f, link->data2, link->len2, 0);
1155 
1156 	/*
1157 	 * Output the block header.
1158 	 */
1159 
1160 	link = index234(h->text, i);
1161 
1162     }
1163 }
1164 
1165 /* ----------------------------------------------------------------------
1166  * Manage the index sections (|KWDATA, |KWMAP, |KWBTREE).
1167  */
1168 
whlp_index_term(WHLP h,char * index,WHLP_TOPIC topic)1169 void whlp_index_term(WHLP h, char *index, WHLP_TOPIC topic)
1170 {
1171     struct indexrec *idx = snew(struct indexrec);
1172 
1173     idx->term = dupstr(index);
1174     idx->topic = topic;
1175     /*
1176      * If this reference is already in the tree, just silently drop
1177      * the duplicate.
1178      */
1179     if (add234(h->index, idx) != idx) {
1180         sfree(idx->term);
1181         sfree(idx);
1182     }
1183 }
1184 
whlp_build_kwdata(WHLP h)1185 static void whlp_build_kwdata(WHLP h)
1186 {
1187     struct file *f;
1188     int i;
1189     struct indexrec *first, *next;
1190 
1191     f = whlp_new_file(h, "|KWDATA");
1192 
1193     /*
1194      * Go through the index B-tree, condensing all sequences of
1195      * records with the same term into a single one with a valid
1196      * (count,offset) pair, and building up the KWDATA section.
1197      */
1198     i = 0;
1199     while ( (first = index234(h->index, i)) != NULL) {
1200         first->count = 1;
1201         first->offset = whlp_file_offset(f);
1202         whlp_file_add_long(f, first->topic->link->topicoffset);
1203         i++;
1204         while ( (next = index234(h->index, i)) != NULL &&
1205                !strcmp(first->term, next->term)) {
1206             /*
1207              * The next index record has the same term. Fold it
1208              * into this one and remove from the tree.
1209              */
1210             whlp_file_add_long(f, next->topic->link->topicoffset);
1211             first->count++;
1212             delpos234(h->index, i);
1213             sfree(next->term);
1214             sfree(next);
1215         }
1216     }
1217 
1218     /*
1219      * Now we should have `index' in a form that's ready to
1220      * construct |KWBTREE. So we can return.
1221      */
1222 }
1223 
1224 /* ----------------------------------------------------------------------
1225  * Standard chunks of data for the |SYSTEM and |FONT sections.
1226  */
1227 
whlp_system_record(struct file * f,int id,const void * data,int length)1228 static void whlp_system_record(struct file *f, int id,
1229 			       const void *data, int length)
1230 {
1231     whlp_file_add_short(f, id);
1232     whlp_file_add_short(f, length);
1233     whlp_file_add(f, data, length);
1234 }
1235 
whlp_standard_systemsection(struct file * f)1236 static void whlp_standard_systemsection(struct file *f)
1237 {
1238     const char lcid[] = { 0, 0, 0, 0, 0, 0, 0, 0, 9, 4 };
1239     const char charset[] = { 0, 0, 0, 2, 0 };
1240 
1241     whlp_file_add_short(f, 0x36C);     /* magic number */
1242     whlp_file_add_short(f, 33);	       /* minor version: HCW 4.00 Win95+ */
1243     whlp_file_add_short(f, 1);	       /* major version */
1244     whlp_file_add_long(f, time(NULL)); /* generation date */
1245     whlp_file_add_short(f, 0);	       /* flags=0 means no compression */
1246 
1247     /*
1248      * Add some magic locale identifier information. (We ought to
1249      * find out something about what all this means; see the TODO
1250      * list at the top of the file.)
1251      */
1252     whlp_system_record(f, 9, lcid, sizeof(lcid));
1253     whlp_system_record(f, 11, charset, sizeof(charset));
1254 }
1255 
whlp_title(WHLP h,char * title)1256 void whlp_title(WHLP h, char *title)
1257 {
1258     whlp_system_record(h->systemfile, 1, title, 1+strlen(title));
1259 }
1260 
whlp_copyright(WHLP h,char * copyright)1261 void whlp_copyright(WHLP h, char *copyright)
1262 {
1263     whlp_system_record(h->systemfile, 2, copyright, 1+strlen(copyright));
1264 }
1265 
whlp_start_macro(WHLP h,char * macro)1266 void whlp_start_macro(WHLP h, char *macro)
1267 {
1268     whlp_system_record(h->systemfile, 4, macro, 1+strlen(macro));
1269 }
1270 
whlp_primary_topic(WHLP h,WHLP_TOPIC t)1271 void whlp_primary_topic(WHLP h, WHLP_TOPIC t)
1272 {
1273     h->ptopic = t;
1274 }
1275 
whlp_do_primary_topic(WHLP h)1276 static void whlp_do_primary_topic(WHLP h)
1277 {
1278     unsigned char firsttopic[4];
1279     PUT_32BIT_LSB_FIRST(firsttopic, h->ptopic->link->topicoffset);
1280     whlp_system_record(h->systemfile, 3, firsttopic, sizeof(firsttopic));
1281 }
1282 
whlp_create_font(WHLP h,char * font,int family,int halfpoints,int rendition,int r,int g,int b)1283 int whlp_create_font(WHLP h, char *font, int family, int halfpoints,
1284 		     int rendition, int r, int g, int b)
1285 {
1286     char *fontname = dupstr(font);
1287     struct fontdesc *fontdesc;
1288     int index;
1289 
1290     font = add234(h->fontnames, fontname);
1291     if (font != fontname) {
1292 	/* The font name was already present. Free the new copy. */
1293 	sfree(fontname);
1294     }
1295 
1296     fontdesc = snew(struct fontdesc);
1297     fontdesc->font = font;
1298     fontdesc->family = family;
1299     fontdesc->halfpoints = halfpoints;
1300     fontdesc->rendition = rendition;
1301     fontdesc->r = r;
1302     fontdesc->g = g;
1303     fontdesc->b = b;
1304 
1305     index = count234(h->fontdescs);
1306     addpos234(h->fontdescs, fontdesc, index);
1307     return index;
1308 }
1309 
whlp_make_fontsection(WHLP h,struct file * f)1310 static void whlp_make_fontsection(WHLP h, struct file *f)
1311 {
1312     int i;
1313     char *fontname;
1314     struct fontdesc *fontdesc;
1315 
1316     /*
1317      * Header block: number of font names, number of font
1318      * descriptors, offset to font names, and offset to font
1319      * descriptors.
1320      */
1321     whlp_file_add_short(f, count234(h->fontnames));
1322     whlp_file_add_short(f, count234(h->fontdescs));
1323     whlp_file_add_short(f, 8);
1324     whlp_file_add_short(f, 8 + 32 * count234(h->fontnames));
1325 
1326     /*
1327      * Font names.
1328      */
1329     for (i = 0; (fontname = index234(h->fontnames, i)) != NULL; i++) {
1330 	char data[32];
1331 	memset(data, i, sizeof(data));
1332 	strncpy(data, fontname, sizeof(data));
1333 	whlp_file_add(f, data, sizeof(data));
1334     }
1335 
1336     /*
1337      * Font descriptors.
1338      */
1339     for (i = 0; (fontdesc = index234(h->fontdescs, i)) != NULL; i++) {
1340 	int fontpos;
1341 	void *ret;
1342 
1343 	ret = findpos234(h->fontnames, fontdesc->font, NULL, &fontpos);
1344 	assert(ret != NULL);
1345 
1346 	whlp_file_add_char(f, fontdesc->rendition);
1347 	whlp_file_add_char(f, fontdesc->halfpoints);
1348 	whlp_file_add_char(f, fontdesc->family);
1349 	whlp_file_add_short(f, fontpos);
1350 	/* Foreground RGB */
1351 	whlp_file_add_char(f, fontdesc->r);
1352 	whlp_file_add_char(f, fontdesc->g);
1353 	whlp_file_add_char(f, fontdesc->b);
1354 	/* Background RGB is apparently unused and always set to zero */
1355 	whlp_file_add_char(f, 0);
1356 	whlp_file_add_char(f, 0);
1357 	whlp_file_add_char(f, 0);
1358     }
1359 
1360 }
1361 
1362 /* ----------------------------------------------------------------------
1363  * Routines to manage a B-tree type file.
1364  */
1365 
whlp_make_btree(struct file * f,int flags,int pagesize,char * dataformat,tree234 * tree,struct file * map,bt_index_fn indexfn,bt_leaf_fn leaffn)1366 static void whlp_make_btree(struct file *f, int flags, int pagesize,
1367 			    char *dataformat, tree234 *tree,
1368                             struct file *map,
1369 			    bt_index_fn indexfn, bt_leaf_fn leaffn)
1370 {
1371     void **page_elements = NULL;
1372     int npages = 0, pagessize = 0;
1373     int npages_this_level, nentries, nlevels;
1374     int total_leaf_entries;
1375     unsigned char btdata[MAX_PAGE_SIZE];
1376     int btlen;
1377     int page_start, fixups_offset, unused_bytes;
1378     void *element;
1379     int index;
1380 
1381     assert(pagesize <= MAX_PAGE_SIZE);
1382 
1383     /*
1384      * Start with the B-tree header. We'll have to come back and
1385      * fill in a few bits later.
1386      */
1387     whlp_file_add_short(f, 0x293B);    /* magic number */
1388     whlp_file_add_short(f, flags);
1389     whlp_file_add_short(f, pagesize);
1390     {
1391 	char data[16];
1392 	memset(data, 0, sizeof(data));
1393 	assert(strlen(dataformat) <= sizeof(data));
1394 	memcpy(data, dataformat, strlen(dataformat));
1395 	whlp_file_add(f, data, sizeof(data));
1396     }
1397     whlp_file_add_short(f, 0);	       /* must-be-zero */
1398     fixups_offset = whlp_file_offset(f);
1399     whlp_file_add_short(f, 0);	       /* page splits; fix up later */
1400     whlp_file_add_short(f, 0);	       /* root page index; fix up later */
1401     whlp_file_add_short(f, -1);	       /* must-be-minus-one */
1402     whlp_file_add_short(f, 0);	       /* total number of pages; fix later */
1403     whlp_file_add_short(f, 0);	       /* number of levels; fix later */
1404     whlp_file_add_long(f, count234(tree));/* total B-tree entries */
1405 
1406     /*
1407      * If we have a map section, leave space at the start for its
1408      * element count.
1409      */
1410     if (map) {
1411         whlp_file_add_short(map, 0);
1412     }
1413 
1414     /*
1415      * Now create the leaf pages.
1416      */
1417     index = 0;
1418 
1419     npages_this_level = 0;
1420     total_leaf_entries = 0;
1421 
1422     element = index234(tree, index);
1423     while (element) {
1424 	/*
1425 	 * Make a new leaf page.
1426 	 */
1427 	npages_this_level++;
1428 	if (npages >= pagessize) {
1429 	    pagessize = npages + 32;
1430 	    page_elements = sresize(page_elements, pagessize, void *);
1431 	}
1432 	page_elements[npages++] = element;
1433 
1434 	/*
1435 	 * Leave space in the leaf page for the header. We'll
1436 	 * come back and add it later.
1437 	 */
1438 	page_start = whlp_file_offset(f);
1439 	whlp_file_add(f, "12345678", 8);
1440 	unused_bytes = pagesize - 8;
1441 	nentries = 0;
1442 
1443 	/*
1444 	 * Now add leaf entries until we run out of room, or out of
1445 	 * elements.
1446 	 */
1447 	while (element) {
1448 	    btlen = leaffn(element, btdata);
1449 	    if (btlen > unused_bytes)
1450 		break;
1451 	    whlp_file_add(f, btdata, btlen);
1452 	    unused_bytes -= btlen;
1453 	    nentries++;
1454 	    index++;
1455 	    element = index234(tree, index);
1456 	}
1457 
1458 	/*
1459 	 * Now add the unused bytes, and then go back and put
1460 	 * in the header.
1461 	 */
1462 	whlp_file_fill(f, unused_bytes);
1463 	whlp_file_seek(f, page_start, 0);
1464 	whlp_file_add_short(f, unused_bytes);
1465 	whlp_file_add_short(f, nentries);
1466 	/* Previous-page indicator will automatically go to -1 when
1467 	 * absent. */
1468 	whlp_file_add_short(f, npages-2);
1469 	/* Next-page indicator must be -1 if we're at the end. */
1470 	if (!element)
1471 	    whlp_file_add_short(f, -1);
1472 	else
1473 	    whlp_file_add_short(f, npages);
1474 	whlp_file_seek(f, 0, 2);
1475 
1476         /*
1477          * If we have a map section, add a map entry.
1478          */
1479         if (map) {
1480             whlp_file_add_long(map, total_leaf_entries);
1481             whlp_file_add_short(map, npages_this_level-1);
1482         }
1483         total_leaf_entries += nentries;
1484     }
1485 
1486     /*
1487      * If we have a map section, write the total number of map
1488      * entries into it.
1489      */
1490     if (map) {
1491         whlp_file_seek(map, 0, 0);
1492         whlp_file_add_short(map, npages_this_level);
1493         whlp_file_seek(map, 0, 2);
1494     }
1495 
1496     /*
1497      * Now create further levels until we're down to one page.
1498      */
1499     nlevels = 1;
1500     while (npages_this_level > 1) {
1501 	int first = npages - npages_this_level;
1502 	int last = npages - 1;
1503 	int current;
1504 
1505 	nlevels++;
1506 	npages_this_level = 0;
1507 
1508 	current = first;
1509 	while (current <= last) {
1510 	    /*
1511 	     * Make a new index page.
1512 	     */
1513 	    npages_this_level++;
1514 	    if (npages >= pagessize) {
1515 		pagessize = npages + 32;
1516 		page_elements = sresize(page_elements, pagessize, void *);
1517 	    }
1518 	    page_elements[npages++] = page_elements[current];
1519 
1520 	    /*
1521 	     * Leave space for some of the header, but we can put
1522 	     * in the PreviousPage link already.
1523 	     */
1524 	    page_start = whlp_file_offset(f);
1525 	    whlp_file_add(f, "1234", 4);
1526 	    whlp_file_add_short(f, current);
1527 	    unused_bytes = pagesize - 6;
1528 
1529 	    /*
1530 	     * Now add index entries until we run out of either
1531 	     * space or pages.
1532 	     */
1533 	    current++;
1534 	    nentries = 0;
1535 	    while (current <= last) {
1536 		btlen = indexfn(page_elements[current], btdata);
1537 		if (btlen + 2 > unused_bytes)
1538 		    break;
1539 		whlp_file_add(f, btdata, btlen);
1540 		whlp_file_add_short(f, current);
1541 		unused_bytes -= btlen+2;
1542 		nentries++;
1543 		current++;
1544 	    }
1545 
1546 	    /*
1547 	     * Now add the unused bytes, and then go back and put
1548 	     * in the header.
1549 	     */
1550 	    whlp_file_fill(f, unused_bytes);
1551 	    whlp_file_seek(f, page_start, 0);
1552 	    whlp_file_add_short(f, unused_bytes);
1553 	    whlp_file_add_short(f, nentries);
1554 	    whlp_file_seek(f, 0, 2);
1555 	}
1556     }
1557 
1558     /*
1559      * Now we have all our pages ready, and we know where our root
1560      * page is. Fix up the main B-tree header.
1561      */
1562     whlp_file_seek(f, fixups_offset, 0);
1563     /* Creation of every page requires a split unless it's the first in
1564      * a new level. Hence, page splits equals pages minus levels. */
1565     whlp_file_add_short(f, npages - nlevels);
1566     whlp_file_add_short(f, npages-1);  /* root page index */
1567     whlp_file_add_short(f, -1);	       /* must-be-minus-one */
1568     whlp_file_add_short(f, npages);    /* total number of pages */
1569     whlp_file_add_short(f, nlevels);   /* number of levels */
1570 
1571     /* Just for tidiness, seek to the end of the file :-) */
1572     whlp_file_seek(f, 0, 2);
1573 
1574     /* Clean up. */
1575     sfree(page_elements);
1576 }
1577 
1578 
1579 /* ----------------------------------------------------------------------
1580  * Routines to manage the `internal file' structure.
1581  */
1582 
whlp_new_file(WHLP h,char * name)1583 static struct file *whlp_new_file(WHLP h, char *name)
1584 {
1585     struct file *f;
1586     f = snew(struct file);
1587     f->data = NULL;
1588     f->pos = f->len = f->size = 0;
1589     if (name) {
1590 	f->name = dupstr(name);
1591 	add234(h->files, f);
1592     } else {
1593 	f->name = NULL;
1594     }
1595     return f;
1596 }
1597 
whlp_free_file(struct file * f)1598 static void whlp_free_file(struct file *f)
1599 {
1600     sfree(f->data);
1601     sfree(f->name);		       /* may be NULL */
1602     sfree(f);
1603 }
1604 
whlp_file_add(struct file * f,const void * data,int len)1605 static void whlp_file_add(struct file *f, const void *data, int len)
1606 {
1607     if (f->pos + len > f->size) {
1608 	f->size = f->pos + len + 1024;
1609 	f->data = sresize(f->data, f->size, unsigned char);
1610     }
1611     memcpy(f->data + f->pos, data, len);
1612     f->pos += len;
1613     if (f->len < f->pos)
1614 	f->len = f->pos;
1615 }
1616 
whlp_file_add_char(struct file * f,int data)1617 static void whlp_file_add_char(struct file *f, int data)
1618 {
1619     unsigned char s;
1620     s = data & 0xFF;
1621     whlp_file_add(f, &s, 1);
1622 }
1623 
whlp_file_add_short(struct file * f,int data)1624 static void whlp_file_add_short(struct file *f, int data)
1625 {
1626     unsigned char s[2];
1627     PUT_16BIT_LSB_FIRST(s, data);
1628     whlp_file_add(f, s, 2);
1629 }
1630 
whlp_file_add_long(struct file * f,int data)1631 static void whlp_file_add_long(struct file *f, int data)
1632 {
1633     unsigned char s[4];
1634     PUT_32BIT_LSB_FIRST(s, data);
1635     whlp_file_add(f, s, 4);
1636 }
1637 
whlp_file_add_cushort(struct file * f,int data)1638 static void whlp_file_add_cushort(struct file *f, int data)
1639 {
1640     if (data <= 0x7F) {
1641 	whlp_file_add_char(f, data*2);
1642     } else {
1643 	whlp_file_add_char(f, 1 + (data%128 * 2));
1644 	whlp_file_add_char(f, data/128);
1645     }
1646 }
1647 
1648 #if 0				       /* currently unused */
1649 static void whlp_file_add_csshort(struct file *f, int data)
1650 {
1651     if (data >= -0x40 && data <= 0x3F)
1652 	whlp_file_add_cushort(f, data+64);
1653     else
1654 	whlp_file_add_cushort(f, data+16384);
1655 }
1656 #endif
1657 
whlp_file_add_culong(struct file * f,int data)1658 static void whlp_file_add_culong(struct file *f, int data)
1659 {
1660     if (data <= 0x7FFF) {
1661 	whlp_file_add_short(f, data*2);
1662     } else {
1663 	whlp_file_add_short(f, 1 + (data%32768 * 2));
1664 	whlp_file_add_short(f, data/32768);
1665     }
1666 }
1667 
1668 #if 0				       /* currently unused */
1669 static void whlp_file_add_cslong(struct file *f, int data)
1670 {
1671     if (data >= -0x4000 && data <= 0x3FFF)
1672 	whlp_file_add_culong(f, data+16384);
1673     else
1674 	whlp_file_add_culong(f, data+67108864);
1675 }
1676 #endif
1677 
whlp_file_fill(struct file * f,int len)1678 static void whlp_file_fill(struct file *f, int len)
1679 {
1680     if (f->pos + len > f->size) {
1681 	f->size = f->pos + len + 1024;
1682 	f->data = sresize(f->data, f->size, unsigned char);
1683     }
1684     memset(f->data + f->pos, 0, len);
1685     f->pos += len;
1686     if (f->len < f->pos)
1687 	f->len = f->pos;
1688 }
1689 
whlp_file_seek(struct file * f,int pos,int whence)1690 static void whlp_file_seek(struct file *f, int pos, int whence)
1691 {
1692     f->pos = (whence == 0 ? 0 : whence == 1 ? f->pos : f->len) + pos;
1693 }
1694 
whlp_file_offset(struct file * f)1695 static int whlp_file_offset(struct file *f)
1696 {
1697     return f->pos;
1698 }
1699 
1700 /* ----------------------------------------------------------------------
1701  * Open and close routines; final wrapper around everything.
1702  */
1703 
whlp_new(void)1704 WHLP whlp_new(void)
1705 {
1706     WHLP ret;
1707     struct file *f;
1708 
1709     ret = snew(struct WHLP_tag);
1710 
1711     /*
1712      * Internal B-trees.
1713      */
1714     ret->files = newtree234(filecmp);
1715     ret->pre_contexts = newtree234(NULL);
1716     ret->contexts = newtree234(ctxcmp);
1717     ret->titles = newtree234(ttlcmp);
1718     ret->text = newtree234(NULL);
1719     ret->index = newtree234(idxcmp);
1720     ret->tabstops = newtree234(tabcmp);
1721     ret->fontnames = newtree234(fontcmp);
1722     ret->fontdescs = newtree234(NULL);
1723 
1724     /*
1725      * Some standard files.
1726      */
1727     f = whlp_new_file(ret, "|CTXOMAP");
1728     whlp_file_add_short(f, 0);	       /* dummy section */
1729     f = whlp_new_file(ret, "|SYSTEM");
1730     whlp_standard_systemsection(f);
1731     ret->systemfile = f;
1732 
1733     /*
1734      * Other variables.
1735      */
1736     ret->prevtopic = NULL;
1737     ret->ncontexts = 0;
1738     ret->link = NULL;
1739     ret->picture_index = 0;
1740 
1741     return ret;
1742 }
1743 
whlp_close(WHLP h,char * filename)1744 void whlp_close(WHLP h, char *filename)
1745 {
1746     FILE *fp;
1747     int filecount, offset, index, filelen;
1748     struct file *file, *map, *md;
1749     context *ctx;
1750     int has_index;
1751 
1752     /*
1753      * Lay out the topic section.
1754      */
1755     whlp_topic_layout(h);
1756 
1757     /*
1758      * Finish off the system section.
1759      */
1760     whlp_do_primary_topic(h);
1761 
1762     /*
1763      * Assemble the font section.
1764      */
1765     file = whlp_new_file(h, "|FONT");
1766     whlp_make_fontsection(h, file);
1767 
1768     /*
1769      * Set up the index.
1770      */
1771     has_index = (count234(h->index) != 0);
1772     if (has_index)
1773         whlp_build_kwdata(h);
1774 
1775     /*
1776      * Set up the `titles' B-tree for the |TTLBTREE section.
1777      */
1778     for (index = 0; (ctx = index234(h->contexts, index)) != NULL; index++)
1779 	add234(h->titles, ctx);
1780 
1781     /*
1782      * Construct the various B-trees.
1783      */
1784     file = whlp_new_file(h, "|CONTEXT");
1785     whlp_make_btree(file, 0x0002, 0x0800, "L4",
1786                     h->contexts, NULL, ctxindex, ctxleaf);
1787 
1788     file = whlp_new_file(h, "|TTLBTREE");
1789     whlp_make_btree(file, 0x0002, 0x0800, "Lz",
1790 		    h->titles, NULL, ttlindex, ttlleaf);
1791 
1792     if (has_index) {
1793         file = whlp_new_file(h, "|KWBTREE");
1794         map = whlp_new_file(h, "|KWMAP");
1795         whlp_make_btree(file, 0x0002, 0x0800, "F24",
1796                         h->index, map, idxindex, idxleaf);
1797     }
1798 
1799     /*
1800      * Open the output file.
1801      */
1802     fp = fopen(filename, "wb");
1803     if (!fp) {
1804 	whlp_abandon(h);
1805 	return;
1806     }
1807 
1808     /*
1809      * Work out all the file offsets.
1810      */
1811     filecount = count234(h->files);
1812     offset = 16;		       /* just after header */
1813     for (index = 0; index < filecount; index++) {
1814 	file = index234(h->files, index);
1815 	file->fileoffset = offset;
1816 	offset += 9 + file->len;       /* 9 is size of file header */
1817     }
1818     /* Now `offset' holds what will be the offset of the master directory. */
1819 
1820     md = whlp_new_file(h, NULL);       /* master directory file */
1821     whlp_make_btree(md, 0x0402, 0x0400, "z4",
1822                     h->files, NULL, fileindex, fileleaf);
1823 
1824     filelen = offset + 9 + md->len;
1825 
1826     /*
1827      * Write out the file header.
1828      */
1829     {
1830 	unsigned char header[16];
1831 	PUT_32BIT_LSB_FIRST(header+0, 0x00035F3FL);  /* magic */
1832 	PUT_32BIT_LSB_FIRST(header+4, offset);       /* offset to directory */
1833 	PUT_32BIT_LSB_FIRST(header+8, 0xFFFFFFFFL);  /* first free block */
1834 	PUT_32BIT_LSB_FIRST(header+12, filelen);     /* total file length */
1835 	fwrite(header, 1, 16, fp);
1836     }
1837 
1838     /*
1839      * Now write out each file.
1840      */
1841     for (index = 0; index <= filecount; index++) {
1842 	int used, reserved;
1843 	unsigned char header[9];
1844 
1845 	if (index == filecount)
1846 	    file = md;		       /* master directory comes last */
1847 	else
1848 	    file = index234(h->files, index);
1849 
1850 	used = file->len;
1851 	reserved = used + 9;
1852 
1853 	/* File header. */
1854 	PUT_32BIT_LSB_FIRST(header+0, reserved);
1855 	PUT_32BIT_LSB_FIRST(header+4, used);
1856 	header[8] = 0;		       /* flags */
1857 	fwrite(header, 1, 9, fp);
1858 
1859 	/* File data. */
1860 	fwrite(file->data, 1, file->len, fp);
1861     }
1862 
1863     fclose(fp);
1864 
1865     whlp_free_file(md);
1866 
1867     whlp_abandon(h);		       /* now free everything */
1868 }
1869 
whlp_abandon(WHLP h)1870 void whlp_abandon(WHLP h)
1871 {
1872     struct file *f;
1873     struct indexrec *idx;
1874     struct topiclink *link;
1875     struct fontdesc *fontdesc;
1876     char *fontname;
1877     context *ctx;
1878 
1879     /* Get rid of any lingering tab stops. */
1880     whlp_para_reset(h);
1881 
1882     /* Delete the (now empty) tabstops tree. */
1883     freetree234(h->tabstops);
1884 
1885     /* Delete the index tree and all its entries. */
1886     while ( (idx = index234(h->index, 0)) != NULL) {
1887 	delpos234(h->index, 0);
1888 	sfree(idx->term);
1889 	sfree(idx);
1890     }
1891     freetree234(h->index);
1892 
1893     /* Delete the text tree and all its topiclinks. */
1894     while ( (link = index234(h->text, 0)) != NULL) {
1895 	delpos234(h->text, 0);
1896 	sfree(link->data1);	       /* may be NULL */
1897 	sfree(link->data2);	       /* may be NULL */
1898 	sfree(link);
1899     }
1900     freetree234(h->text);
1901 
1902     /* Delete the fontdescs tree and all its entries. */
1903     while ( (fontdesc = index234(h->fontdescs, 0)) != NULL) {
1904 	delpos234(h->fontdescs, 0);
1905 	sfree(fontdesc);
1906     }
1907     freetree234(h->fontdescs);
1908 
1909     /* Delete the fontnames tree and all its entries. */
1910     while ( (fontname = index234(h->fontnames, 0)) != NULL) {
1911 	delpos234(h->fontnames, 0);
1912 	sfree(fontname);
1913     }
1914     freetree234(h->fontnames);
1915 
1916     /* There might be an unclosed paragraph in h->link. */
1917     if (h->link)
1918 	sfree(h->link);		       /* if so it won't have data1 or data2 */
1919 
1920     /*
1921      * `titles' contains copies of the `contexts' entries, so we
1922      * don't need to free them here.
1923      */
1924     freetree234(h->titles);
1925 
1926     /*
1927      * `contexts' and `pre_contexts' _both_ contain contexts that
1928      * need freeing. (pre_contexts shouldn't contain any, unless
1929      * the help generation was abandoned half-way through.)
1930      */
1931     while ( (ctx = index234(h->pre_contexts, 0)) != NULL) {
1932 	delpos234(h->index, 0);
1933 	sfree(ctx->name);
1934 	sfree(ctx->title);
1935 	sfree(ctx);
1936     }
1937     freetree234(h->pre_contexts);
1938     while ( (ctx = index234(h->contexts, 0)) != NULL) {
1939 	delpos234(h->contexts, 0);
1940 	sfree(ctx->name);
1941 	sfree(ctx->title);
1942 	sfree(ctx);
1943     }
1944     freetree234(h->contexts);
1945 
1946     /*
1947      * Free all the internal files.
1948      */
1949     while ( (f = index234(h->files, 0)) != NULL ) {
1950 	delpos234(h->files, 0);
1951 	whlp_free_file(f);
1952     }
1953     freetree234(h->files);
1954 
1955     sfree(h);
1956 }
1957 
1958 #ifdef WINHELP_TESTMODE
1959 
1960 #ifdef PICTURE_FROM_CMDLINE
1961 #include "png.h"
1962 #include "colquant.h"
1963 #include "dither.h"
1964 #endif
1965 
main(int argc,char ** argv)1966 int main(int argc, char **argv)
1967 {
1968     WHLP h;
1969     WHLP_TOPIC t1, t2, t3;
1970     char *e;
1971     char mymacro[100];
1972 
1973     h = whlp_new();
1974 
1975     whlp_title(h, "Test Help File");
1976     whlp_copyright(h, "This manual is copyright \251 2001 Simon Tatham."
1977 		   " All rights reversed.");
1978     whlp_start_macro(h, "CB(\"btn_about\",\"&About\",\"About()\")");
1979     whlp_start_macro(h, "CB(\"btn_up\",\"&Up\",\"Contents()\")");
1980     whlp_start_macro(h, "BrowseButtons()");
1981 
1982     whlp_create_font(h, "Arial", WHLP_FONTFAM_SANS, 30,
1983 		     0, 0, 0, 0);
1984     whlp_create_font(h, "Times New Roman", WHLP_FONTFAM_SERIF, 24,
1985 		     WHLP_FONT_STRIKEOUT, 0, 0, 0);
1986     whlp_create_font(h, "Times New Roman", WHLP_FONTFAM_SERIF, 24,
1987 		     WHLP_FONT_ITALIC, 0, 0, 0);
1988     whlp_create_font(h, "Courier New", WHLP_FONTFAM_FIXED, 24,
1989 		     0, 0, 0, 0);
1990 
1991     t1 = whlp_register_topic(h, "foobar", &e);
1992     assert(t1 != NULL);
1993     t2 = whlp_register_topic(h, "M359HPEHGW", &e);
1994     assert(t2 != NULL);
1995     t3 = whlp_register_topic(h, "Y5VQEXZQVJ", &e);
1996     assert(t3 == NULL && !strcmp(e, "M359HPEHGW"));
1997     t3 = whlp_register_topic(h, NULL, NULL);
1998     assert(t3 != NULL);
1999 
2000     whlp_primary_topic(h, t2);
2001 
2002     whlp_prepare(h);
2003 
2004     whlp_begin_topic(h, t1, "First Topic", "DB(\"btn_up\")", NULL);
2005 
2006     whlp_begin_para(h, WHLP_PARA_NONSCROLL);
2007     whlp_set_font(h, 0);
2008     whlp_text(h, "Foobar");
2009     whlp_end_para(h);
2010 
2011     whlp_begin_para(h, WHLP_PARA_SCROLL);
2012     whlp_set_font(h, 1);
2013     whlp_text(h, "This is a silly paragraph with ");
2014     whlp_set_font(h, 3);
2015     whlp_text(h, "code");
2016     whlp_set_font(h, 1);
2017     whlp_text(h, " in it.");
2018     whlp_end_para(h);
2019 
2020     whlp_para_attr(h, WHLP_PARA_SPACEABOVE, 12);
2021     whlp_begin_para(h, WHLP_PARA_SCROLL);
2022     whlp_set_font(h, 1);
2023     whlp_text(h, "This second, equally silly, paragraph has ");
2024     whlp_set_font(h, 2);
2025     whlp_text(h, "emphasis");
2026     whlp_set_font(h, 1);
2027     whlp_text(h, " just to prove we can do it.");
2028     whlp_end_para(h);
2029 
2030     whlp_begin_para(h, WHLP_PARA_SCROLL);
2031     whlp_set_font(h, 1);
2032     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2033 	      " to make some wrapping happen, and also to make the topicblock"
2034 	      " go across its boundaries. This is going to take a fair amount"
2035 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2036     whlp_end_para(h);
2037 
2038     whlp_begin_para(h, WHLP_PARA_SCROLL);
2039     whlp_set_font(h, 1);
2040     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2041 	      " to make some wrapping happen, and also to make the topicblock"
2042 	      " go across its boundaries. This is going to take a fair amount"
2043 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2044     whlp_end_para(h);
2045 
2046     whlp_begin_para(h, WHLP_PARA_SCROLL);
2047     whlp_set_font(h, 1);
2048     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2049 	      " to make some wrapping happen, and also to make the topicblock"
2050 	      " go across its boundaries. This is going to take a fair amount"
2051 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2052     whlp_end_para(h);
2053 
2054     whlp_begin_para(h, WHLP_PARA_SCROLL);
2055     whlp_set_font(h, 1);
2056     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2057 	      " to make some wrapping happen, and also to make the topicblock"
2058 	      " go across its boundaries. This is going to take a fair amount"
2059 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2060     whlp_end_para(h);
2061 
2062     whlp_begin_para(h, WHLP_PARA_SCROLL);
2063     whlp_set_font(h, 1);
2064     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2065 	      " to make some wrapping happen, and also to make the topicblock"
2066 	      " go across its boundaries. This is going to take a fair amount"
2067 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2068     whlp_end_para(h);
2069 
2070     whlp_begin_para(h, WHLP_PARA_SCROLL);
2071     whlp_set_font(h, 1);
2072     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2073 	      " to make some wrapping happen, and also to make the topicblock"
2074 	      " go across its boundaries. This is going to take a fair amount"
2075 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2076     whlp_end_para(h);
2077 
2078     whlp_begin_para(h, WHLP_PARA_SCROLL);
2079     whlp_set_font(h, 1);
2080     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2081 	      " to make some wrapping happen, and also to make the topicblock"
2082 	      " go across its boundaries. This is going to take a fair amount"
2083 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2084     whlp_end_para(h);
2085 
2086     whlp_begin_para(h, WHLP_PARA_SCROLL);
2087     whlp_set_font(h, 1);
2088     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2089 	      " to make some wrapping happen, and also to make the topicblock"
2090 	      " go across its boundaries. This is going to take a fair amount"
2091 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2092     whlp_end_para(h);
2093 
2094     whlp_begin_para(h, WHLP_PARA_SCROLL);
2095     whlp_set_font(h, 1);
2096     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2097 	      " to make some wrapping happen, and also to make the topicblock"
2098 	      " go across its boundaries. This is going to take a fair amount"
2099 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2100     whlp_end_para(h);
2101 
2102     whlp_begin_para(h, WHLP_PARA_SCROLL);
2103     whlp_set_font(h, 1);
2104     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2105 	      " to make some wrapping happen, and also to make the topicblock"
2106 	      " go across its boundaries. This is going to take a fair amount"
2107 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2108     whlp_end_para(h);
2109 
2110     whlp_begin_para(h, WHLP_PARA_SCROLL);
2111     whlp_set_font(h, 1);
2112     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2113 	      " to make some wrapping happen, and also to make the topicblock"
2114 	      " go across its boundaries. This is going to take a fair amount"
2115 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2116     whlp_end_para(h);
2117 
2118     whlp_begin_para(h, WHLP_PARA_SCROLL);
2119     whlp_set_font(h, 1);
2120     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2121 	      " to make some wrapping happen, and also to make the topicblock"
2122 	      " go across its boundaries. This is going to take a fair amount"
2123 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2124     whlp_end_para(h);
2125 
2126     whlp_begin_para(h, WHLP_PARA_SCROLL);
2127     whlp_set_font(h, 1);
2128     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2129 	      " to make some wrapping happen, and also to make the topicblock"
2130 	      " go across its boundaries. This is going to take a fair amount"
2131 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2132     whlp_end_para(h);
2133 
2134     whlp_begin_para(h, WHLP_PARA_SCROLL);
2135     whlp_set_font(h, 1);
2136     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2137 	      " to make some wrapping happen, and also to make the topicblock"
2138 	      " go across its boundaries. This is going to take a fair amount"
2139 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2140     whlp_end_para(h);
2141 
2142     whlp_begin_para(h, WHLP_PARA_SCROLL);
2143     whlp_set_font(h, 1);
2144     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2145 	      " to make some wrapping happen, and also to make the topicblock"
2146 	      " go across its boundaries. This is going to take a fair amount"
2147 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2148     whlp_end_para(h);
2149 
2150     whlp_begin_para(h, WHLP_PARA_SCROLL);
2151     whlp_set_font(h, 1);
2152     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2153 	      " to make some wrapping happen, and also to make the topicblock"
2154 	      " go across its boundaries. This is going to take a fair amount"
2155 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2156     whlp_end_para(h);
2157 
2158     whlp_begin_para(h, WHLP_PARA_SCROLL);
2159     whlp_set_font(h, 1);
2160     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2161 	      " to make some wrapping happen, and also to make the topicblock"
2162 	      " go across its boundaries. This is going to take a fair amount"
2163 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2164     whlp_end_para(h);
2165 
2166     whlp_begin_para(h, WHLP_PARA_SCROLL);
2167     whlp_set_font(h, 1);
2168     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2169 	      " to make some wrapping happen, and also to make the topicblock"
2170 	      " go across its boundaries. This is going to take a fair amount"
2171 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2172     whlp_end_para(h);
2173 
2174     whlp_begin_para(h, WHLP_PARA_SCROLL);
2175     whlp_set_font(h, 1);
2176     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2177 	      " to make some wrapping happen, and also to make the topicblock"
2178 	      " go across its boundaries. This is going to take a fair amount"
2179 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2180     whlp_end_para(h);
2181 
2182     whlp_begin_para(h, WHLP_PARA_SCROLL);
2183     whlp_set_font(h, 1);
2184     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2185 	      " to make some wrapping happen, and also to make the topicblock"
2186 	      " go across its boundaries. This is going to take a fair amount"
2187 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2188     whlp_end_para(h);
2189 
2190     whlp_begin_para(h, WHLP_PARA_SCROLL);
2191     whlp_set_font(h, 1);
2192     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2193 	      " to make some wrapping happen, and also to make the topicblock"
2194 	      " go across its boundaries. This is going to take a fair amount"
2195 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2196     whlp_end_para(h);
2197 
2198     whlp_begin_para(h, WHLP_PARA_SCROLL);
2199     whlp_set_font(h, 1);
2200     whlp_text(h, "Now I'm going to waffle on indefinitely, in a vague attempt"
2201 	      " to make some wrapping happen, and also to make the topicblock"
2202 	      " go across its boundaries. This is going to take a fair amount"
2203 	      " of text, so I'll just have to cheat and c'n'p a lot of it.");
2204     whlp_end_para(h);
2205 
2206     whlp_begin_para(h, WHLP_PARA_SCROLL);
2207     whlp_set_font(h, 1);
2208     whlp_text(h, "Have a ");
2209     whlp_start_hyperlink(h, t2);
2210     whlp_text(h, "hyperlink");
2211     whlp_end_hyperlink(h);
2212     whlp_text(h, " to another topic.");
2213     whlp_end_para(h);
2214 
2215     sprintf(mymacro, "CBB(\"btn_up\",\"JI(`',`%s')\");EB(\"btn_up\")",
2216 	    whlp_topic_id(t3));
2217 
2218     whlp_begin_topic(h, t2, "Second Topic", mymacro, NULL);
2219 
2220     whlp_begin_para(h, WHLP_PARA_SCROLL);
2221     whlp_set_font(h, 1);
2222     whlp_text(h, "This topic contains no non-scrolling region. I would"
2223 	      " illustrate this with a ludicrously long paragraph, but that"
2224 	      " would get very tedious very quickly. Instead I'll just waffle"
2225 	      " on pointlessly for a little bit and then shut up.");
2226     whlp_end_para(h);
2227 
2228     whlp_set_tabstop(h, 36, WHLP_ALIGN_LEFT);
2229     whlp_para_attr(h, WHLP_PARA_LEFTINDENT, 36);
2230     whlp_para_attr(h, WHLP_PARA_FIRSTLINEINDENT, -36);
2231     whlp_para_attr(h, WHLP_PARA_SPACEABOVE, 12);
2232     whlp_begin_para(h, WHLP_PARA_SCROLL);
2233     whlp_set_font(h, 1);
2234     whlp_text(h, "\225");              /* bullet */
2235     whlp_tab(h);
2236     whlp_text(h, "This is a paragraph with a bullet. With any luck it should"
2237               " work exactly like it used to in the old NASM help file.");
2238     whlp_end_para(h);
2239 
2240     whlp_set_tabstop(h, 128, WHLP_ALIGN_RIGHT);
2241     whlp_set_tabstop(h, 256, WHLP_ALIGN_CENTRE);
2242     whlp_set_tabstop(h, 384, WHLP_ALIGN_LEFT);
2243     whlp_para_attr(h, WHLP_PARA_SPACEABOVE, 12);
2244     whlp_begin_para(h, WHLP_PARA_SCROLL);
2245     whlp_set_font(h, 1);
2246     whlp_text(h, "Ooh:"); whlp_tab(h);
2247     whlp_text(h, "Right?"); whlp_tab(h);
2248     whlp_text(h, "Centre?"); whlp_tab(h);
2249     whlp_text(h, "Left?");
2250     whlp_end_para(h);
2251 
2252     whlp_set_tabstop(h, 128, WHLP_ALIGN_RIGHT);
2253     whlp_set_tabstop(h, 256, WHLP_ALIGN_CENTRE);
2254     whlp_set_tabstop(h, 384, WHLP_ALIGN_LEFT);
2255     whlp_begin_para(h, WHLP_PARA_SCROLL);
2256     whlp_set_font(h, 1);
2257     whlp_text(h, "Aah:"); whlp_tab(h);
2258     whlp_text(h, "R?"); whlp_tab(h);
2259     whlp_text(h, "C?"); whlp_tab(h);
2260     whlp_text(h, "L?");
2261     whlp_end_para(h);
2262 
2263     sprintf(mymacro, "CBB(\"btn_up\",\"JI(`',`%s')\");EB(\"btn_up\")",
2264 	    whlp_topic_id(t1));
2265 
2266     whlp_begin_topic(h, t3, "Third Topic", mymacro, NULL);
2267 
2268     whlp_begin_para(h, WHLP_PARA_SCROLL);
2269     whlp_set_font(h, 1);
2270     whlp_text(h, "This third topic is not nearly as boring as the first, "
2271 	      "because it has a picture: ");
2272     {
2273 #ifndef PICTURE_FROM_CMDLINE
2274 	const unsigned long palette[] = {
2275 	    0xFF0000,
2276 	    0xFFFF00,
2277 	    0x00FF00,
2278 	    0x00FFFF,
2279 	    0x0000FF,
2280 	};
2281 	const unsigned char picture[] = {
2282 	    0, 0, 0, 0, 1, 2, 3, 4,
2283 	    0, 0, 0, 0, 1, 2, 3, 4,
2284 	    0, 0, 0, 0, 1, 2, 3, 4,
2285 	    0, 0, 0, 1, 2, 3, 4, 4,
2286 	    0, 0, 0, 1, 2, 3, 4, 4,
2287 	    0, 0, 0, 1, 2, 3, 4, 4,
2288 	    0, 0, 1, 2, 3, 4, 4, 4,
2289 	    0, 0, 1, 2, 3, 4, 4, 4,
2290 	    0, 0, 1, 2, 3, 4, 4, 4,
2291 	    0, 1, 2, 3, 4, 4, 4, 4,
2292 	    0, 1, 2, 3, 4, 4, 4, 4,
2293 	    0, 1, 2, 3, 4, 4, 4, 4,
2294 	};
2295 	int wid = 8, ht = 12;
2296 #else
2297 	png_pixel ppalette[256];
2298 	unsigned long palette[256];
2299 	unsigned char *picture;
2300 	png *png;
2301 	colquant *cq;
2302 	int plen, i, err, wid, ht;
2303 
2304 	if (argc < 2) {
2305 	    fprintf(stderr, "in this mode I need a .png file on the"
2306 		    " command line\n");
2307 	    return 1;
2308 	}
2309 	png = png_decode_file(argv[1], &err);
2310 	if (!png) {
2311 	    fprintf(stderr, "%s: PNG read error: %s\n", argv[1],
2312 		    png_error_msg[err]);
2313 	    return 1;
2314 	}
2315 
2316 	cq = colquant_new(256, 8);
2317 	colquant_data(cq, png->pixels, png->width * png->height);
2318 	plen = colquant_get_palette(cq, ppalette);
2319 	colquant_free(cq);
2320 	assert(plen <= 256);
2321 	for (i = 0; i < plen; i++) {
2322 	    palette[i] = ppalette[i].r >> 8;
2323 	    palette[i] <<= 8;
2324 	    palette[i] |= ppalette[i].g >> 8;
2325 	    palette[i] <<= 8;
2326 	    palette[i] |= ppalette[i].b >> 8;
2327 	}
2328 	picture = malloc(png->width * png->height);
2329 	dither_image(png->width, png->height, png->pixels,
2330 		     ppalette, plen, picture);
2331 	wid = png->width;
2332 	ht = png->height;
2333 	png_free(png);
2334 
2335 #endif
2336 	whlp_ref_picture(h, whlp_add_picture(h, wid, ht, picture, palette));
2337     }
2338     whlp_end_para(h);
2339 
2340     /*
2341      * Browse sequence.
2342      */
2343     whlp_browse_link(h, t1, t2);
2344     whlp_browse_link(h, t2, t3);
2345 
2346     /*
2347      * Index terms.
2348      */
2349     whlp_index_term(h, "foobarbaz", t1);
2350     whlp_index_term(h, "foobarbaz", t2);
2351     whlp_index_term(h, "foobarbaz", t3);
2352     whlp_index_term(h, "foobar", t1);
2353     whlp_index_term(h, "foobar", t2);
2354     whlp_index_term(h, "foobaz", t1);
2355     whlp_index_term(h, "foobaz", t3);
2356     whlp_index_term(h, "barbaz", t2);
2357     whlp_index_term(h, "barbaz", t3);
2358     whlp_index_term(h, "foo", t1);
2359     whlp_index_term(h, "bar", t2);
2360     whlp_index_term(h, "baz", t3);
2361 
2362     whlp_close(h, "test.hlp");
2363     return 0;
2364 }
2365 
2366 #endif
2367