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