Add an error check for correct formatting in Deflate uncompressed
[sgt/halibut] / winhelp.c
CommitLineData
d7482997 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
63d7b60d 78#ifdef WINHELP_TESTMODE
d7482997 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
f1530049 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)) )
d7482997 90#define lenof(array) ( sizeof(array) / sizeof(*(array)) )
91char *dupstr(char *s) {
f1530049 92 char *r = snewn(1+strlen(s), char); strcpy(r,s); return r;
d7482997 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
121typedef struct WHLP_TOPIC_tag context;
122
123struct 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
132struct 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
138struct 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
148struct 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
157struct fontdesc {
158 char *font;
159 int family, rendition, halfpoints;
160 int r, g, b;
161};
162
163struct 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;
83ac0deb 186 int picture_index;
d7482997 187};
188
189/* Functions to return the index and leaf data for B-tree contents. */
190typedef int (*bt_index_fn)(const void *item, unsigned char *outbuf);
191typedef int (*bt_leaf_fn)(const void *item, unsigned char *outbuf);
192
193/* Forward references. */
194static void whlp_para_reset(WHLP h);
195static struct file *whlp_new_file(WHLP h, char *name);
196static void whlp_file_add(struct file *f, const void *data, int len);
197static void whlp_file_add_char(struct file *f, int data);
198static void whlp_file_add_short(struct file *f, int data);
199static void whlp_file_add_long(struct file *f, int data);
83ac0deb 200static void whlp_file_add_cushort(struct file *f, int data);
595b8c0c 201#if 0 /* currently unused */
83ac0deb 202static void whlp_file_add_csshort(struct file *f, int data);
595b8c0c 203#endif
83ac0deb 204static void whlp_file_add_culong(struct file *f, int data);
595b8c0c 205#if 0 /* currently unused */
83ac0deb 206static void whlp_file_add_cslong(struct file *f, int data);
595b8c0c 207#endif
d7482997 208static void whlp_file_fill(struct file *f, int len);
209static void whlp_file_seek(struct file *f, int pos, int whence);
210static 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
218static 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
225static 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
233static 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
244static 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
255static 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
262static 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
272static 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
283static 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
290static 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
302static 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
317static 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
325static 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
340static 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. */
352static 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 */
367static 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
d7482997 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) {
9ae3b6f0 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;
f89b2ee6 430 } else if (val < 0 && hash < (unsigned long)-val) {
9ae3b6f0 431 hash += (0xFFFFFFFFUL + val) + 1;
432 } else
433 hash += val;
434 }
435
d7482997 436 context++;
437 }
438 return hash;
439}
440
441WHLP_TOPIC whlp_register_topic(WHLP h, char *context_name, char **clash)
442{
f1530049 443 context *ctx = snew(context);
d7482997 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
487void 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);
f1530049 503 ctx->name = snewn(20, char);
d7482997 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
518char *whlp_topic_id(WHLP_TOPIC topic)
519{
520 return topic->name;
521}
522
523void whlp_begin_topic(WHLP h, WHLP_TOPIC topic, char *title, ...)
524{
f1530049 525 struct topiclink *link = snew(struct topiclink);
d7482997 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 */
f1530049 541 link->data1 = snewn(link->len1, unsigned char);
d7482997 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;
f1530049 559 link->data2 = snewn(link->len2, unsigned char);
d7482997 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
568void 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
589static void whlp_linkdata(WHLP h, int which, int c)
590{
591 int *len = (which == 1 ? &h->link->len1 : &h->link->len2);
ee90d1f0 592 unsigned char *data = (which == 1 ? h->linkdata1 : h->linkdata2);
d7482997 593 assert(*len < TOPIC_BLKSIZE);
594 data[(*len)++] = c;
595}
596
597static 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
603static 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
611static 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
621static 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
629static 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
639static 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
647static 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
659void 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
674void 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
f1530049 683 p = snew(int);
d7482997 684 *p = tabstop;
685 add234(h->tabstops, p);
686 h->para_flags |= 0x0200;
687}
688
689void whlp_begin_para(WHLP h, int para_type)
690{
f1530049 691 struct topiclink *link = snew(struct topiclink);
d7482997 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
757void 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
771void 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
785void 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
797void 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
83ac0deb 809int 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
880void 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
d7482997 897void whlp_text(WHLP h, char *text)
898{
899 while (*text) {
900 whlp_linkdata(h, 2, *text++);
901 }
902}
903
904void 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
f1530049 928 h->link->data1 = snewn(h->link->len1, unsigned char);
d7482997 929 memcpy(h->link->data1, h->linkdata1 + data1cut, h->link->len1 - data1cut);
930 memcpy(h->link->data1 + h->link->len1 - data1cut, h->linkdata1, data1cut);
f1530049 931 h->link->data2 = snewn(h->link->len2, unsigned char);
d7482997 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
949static 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
987static 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 */
f1530049 998 link = snew(struct topiclink);
d7482997 999 link->nexttopic = NULL;
1000 if (h->prevtopic)
1001 h->prevtopic->nexttopic = link;
1002 h->prevtopic = link;
f1530049 1003 link->data1 = snewn(0x1c, unsigned char);
d7482997 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
1169void whlp_index_term(WHLP h, char *index, WHLP_TOPIC topic)
1170{
f1530049 1171 struct indexrec *idx = snew(struct indexrec);
d7482997 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
1185static 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
1228static 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
1236static 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
1256void whlp_title(WHLP h, char *title)
1257{
1258 whlp_system_record(h->systemfile, 1, title, 1+strlen(title));
1259}
1260
1261void whlp_copyright(WHLP h, char *copyright)
1262{
1263 whlp_system_record(h->systemfile, 2, copyright, 1+strlen(copyright));
1264}
1265
1266void whlp_start_macro(WHLP h, char *macro)
1267{
1268 whlp_system_record(h->systemfile, 4, macro, 1+strlen(macro));
1269}
1270
1271void whlp_primary_topic(WHLP h, WHLP_TOPIC t)
1272{
1273 h->ptopic = t;
1274}
1275
1276static 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
1283int 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
f1530049 1296 fontdesc = snew(struct fontdesc);
d7482997 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
1310static 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
1366static 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;
ee90d1f0 1375 unsigned char btdata[MAX_PAGE_SIZE];
d7482997 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;
f1530049 1430 page_elements = sresize(page_elements, pagessize, void *);
d7482997 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;
f1530049 1516 page_elements = sresize(page_elements, pagessize, void *);
d7482997 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
1583static struct file *whlp_new_file(WHLP h, char *name)
1584{
1585 struct file *f;
f1530049 1586 f = snew(struct file);
d7482997 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
1598static void whlp_free_file(struct file *f)
1599{
1600 sfree(f->data);
1601 sfree(f->name); /* may be NULL */
1602 sfree(f);
1603}
1604
1605static 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;
f1530049 1609 f->data = sresize(f->data, f->size, unsigned char);
d7482997 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
1617static 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
1624static 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
1631static 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
83ac0deb 1638static 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 */
1649static 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
1658static 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 */
1669static 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
d7482997 1678static void whlp_file_fill(struct file *f, int len)
1679{
1680 if (f->pos + len > f->size) {
1681 f->size = f->pos + len + 1024;
f1530049 1682 f->data = sresize(f->data, f->size, unsigned char);
d7482997 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
1690static 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
1695static 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
1704WHLP whlp_new(void)
1705{
1706 WHLP ret;
1707 struct file *f;
1708
f1530049 1709 ret = snew(struct WHLP_tag);
d7482997 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;
83ac0deb 1739 ret->picture_index = 0;
d7482997 1740
1741 return ret;
1742}
1743
1744void 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
1870void 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
63d7b60d 1958#ifdef WINHELP_TESTMODE
d7482997 1959
63d7b60d 1960#ifdef PICTURE_FROM_CMDLINE
1961#include "png.h"
1962#include "colquant.h"
1963#include "dither.h"
1964#endif
1965
1966int main(int argc, char **argv)
d7482997 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);
83ac0deb 2270 whlp_text(h, "This third topic is not nearly as boring as the first, "
2271 "because it has a picture: ");
2272 {
63d7b60d 2273#ifndef PICTURE_FROM_CMDLINE
83ac0deb 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 };
63d7b60d 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));
83ac0deb 2337 }
d7482997 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