2 * winhelp.c a module to generate Windows .HLP files
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.
11 * Potential future features:
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.
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
30 * - tables might be nice.
32 * Unlikely future features:
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.
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.
55 * - find out what should happen if a single topiclink crosses
56 * _two_ topicblock boundaries.
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.
80 * This lot is useful for testing. Something like it will also be
81 * needed to use this module standalone.
83 #define smalloc malloc
84 #define srealloc realloc
86 #define mknew(type) ( (type *) smalloc (sizeof (type)) )
87 #define mknewa(type, number) ( (type *) smalloc ((number) * sizeof (type)) )
88 #define resize(array, len) ( srealloc ((array), (len) * sizeof (*(array))) )
89 #define lenof(array) ( sizeof(array) / sizeof(*(array)) )
90 char *dupstr(char *s
) {
91 char *r
= mknewa(char, 1+strlen(s
)); strcpy(r
,s
); return r
;
95 #define UNUSEDARG(x) ( (x) = (x) )
97 #define GET_32BIT_LSB_FIRST(cp) \
98 (((unsigned long)(unsigned char)(cp)[0]) | \
99 ((unsigned long)(unsigned char)(cp)[1] << 8) | \
100 ((unsigned long)(unsigned char)(cp)[2] << 16) | \
101 ((unsigned long)(unsigned char)(cp)[3] << 24))
103 #define PUT_32BIT_LSB_FIRST(cp, value) do { \
104 (cp)[0] = 0xFF & (value); \
105 (cp)[1] = 0xFF & ((value) >> 8); \
106 (cp)[2] = 0xFF & ((value) >> 16); \
107 (cp)[3] = 0xFF & ((value) >> 24); } while (0)
109 #define GET_16BIT_LSB_FIRST(cp) \
110 (((unsigned long)(unsigned char)(cp)[0]) | \
111 ((unsigned long)(unsigned char)(cp)[1] << 8))
113 #define PUT_16BIT_LSB_FIRST(cp, value) do { \
114 (cp)[0] = 0xFF & (value); \
115 (cp)[1] = 0xFF & ((value) >> 8); } while (0)
117 #define MAX_PAGE_SIZE 0x800 /* max page size in any B-tree */
118 #define TOPIC_BLKSIZE 4096 /* implied by version/flags combo */
120 typedef struct WHLP_TOPIC_tag context
;
123 char *name
; /* file name, will need freeing */
124 unsigned char *data
; /* file data, will need freeing */
125 int pos
; /* position for adding data */
126 int len
; /* # of meaningful bytes in data */
127 int size
; /* # of allocated bytes in data */
128 int fileoffset
; /* offset in the real .HLP file */
132 char *term
; /* index term, will need freeing */
133 context
*topic
; /* topic it links to */
134 int count
, offset
; /* used when building |KWDATA */
138 int topicoffset
, topicpos
; /* for referencing from elsewhere */
141 unsigned char *data1
, *data2
;
143 struct topiclink
*nonscroll
, *scroll
, *nexttopic
;
144 int block_size
; /* for the topic header - *boggle* */
147 struct WHLP_TOPIC_tag
{
148 char *name
; /* needs freeing */
150 struct topiclink
*link
; /* this provides TOPICOFFSET */
151 context
*browse_next
, *browse_prev
;
152 char *title
; /* needs freeing */
153 int index
; /* arbitrary number */
158 int family
, rendition
, halfpoints
;
163 tree234
*files
; /* stores `struct file' */
164 tree234
*pre_contexts
; /* stores `context' */
165 tree234
*contexts
; /* also stores `context' */
166 tree234
*titles
; /* _also_ stores `context' */
167 tree234
*text
; /* stores `struct topiclink' */
168 tree234
*index
; /* stores `struct indexrec' */
169 tree234
*tabstops
; /* stores `int' */
170 tree234
*fontnames
; /* stores `char *' */
171 tree234
*fontdescs
; /* stores `struct fontdesc' */
172 struct file
*systemfile
; /* the |SYSTEM internal file */
173 context
*ptopic
; /* primary topic */
174 struct topiclink
*prevtopic
; /* to link type-2 records together */
175 struct topiclink
*link
; /* while building a topiclink */
176 unsigned char linkdata1
[TOPIC_BLKSIZE
]; /* while building a topiclink */
177 unsigned char linkdata2
[TOPIC_BLKSIZE
]; /* while building a topiclink */
178 int topicblock_remaining
; /* while building |TOPIC section */
179 int lasttopiclink
; /* while building |TOPIC section */
180 int firsttopiclink_offset
; /* while building |TOPIC section */
181 int lasttopicstart
; /* while building |TOPIC section */
187 /* Functions to return the index and leaf data for B-tree contents. */
188 typedef int (*bt_index_fn
)(const void *item
, unsigned char *outbuf
);
189 typedef int (*bt_leaf_fn
)(const void *item
, unsigned char *outbuf
);
191 /* Forward references. */
192 static void whlp_para_reset(WHLP h
);
193 static struct file
*whlp_new_file(WHLP h
, char *name
);
194 static void whlp_file_add(struct file
*f
, const void *data
, int len
);
195 static void whlp_file_add_char(struct file
*f
, int data
);
196 static void whlp_file_add_short(struct file
*f
, int data
);
197 static void whlp_file_add_long(struct file
*f
, int data
);
198 static void whlp_file_fill(struct file
*f
, int len
);
199 static void whlp_file_seek(struct file
*f
, int pos
, int whence
);
200 static int whlp_file_offset(struct file
*f
);
202 /* ----------------------------------------------------------------------
203 * Fiddly little functions: B-tree compare, index and leaf functions.
206 /* The master index maps file names to help-file offsets. */
208 static int filecmp(void *av
, void *bv
)
210 const struct file
*a
= (const struct file
*)av
;
211 const struct file
*b
= (const struct file
*)bv
;
212 return strcmp(a
->name
, b
->name
);
215 static int fileindex(const void *av
, unsigned char *outbuf
)
217 const struct file
*a
= (const struct file
*)av
;
218 int len
= 1+strlen(a
->name
);
219 memcpy(outbuf
, a
->name
, len
);
223 static int fileleaf(const void *av
, unsigned char *outbuf
)
225 const struct file
*a
= (const struct file
*)av
;
226 int len
= 1+strlen(a
->name
);
227 memcpy(outbuf
, a
->name
, len
);
228 PUT_32BIT_LSB_FIRST(outbuf
+len
, a
->fileoffset
);
232 /* The |CONTEXT internal file maps help context hashes to TOPICOFFSETs. */
234 static int ctxcmp(void *av
, void *bv
)
236 const context
*a
= (const context
*)av
;
237 const context
*b
= (const context
*)bv
;
238 if ((signed long)a
->hash
< (signed long)b
->hash
)
240 if ((signed long)a
->hash
> (signed long)b
->hash
)
245 static int ctxindex(const void *av
, unsigned char *outbuf
)
247 const context
*a
= (const context
*)av
;
248 PUT_32BIT_LSB_FIRST(outbuf
, a
->hash
);
252 static int ctxleaf(const void *av
, unsigned char *outbuf
)
254 const context
*a
= (const context
*)av
;
255 PUT_32BIT_LSB_FIRST(outbuf
, a
->hash
);
256 PUT_32BIT_LSB_FIRST(outbuf
+4, a
->link
->topicoffset
);
260 /* The |TTLBTREE internal file maps TOPICOFFSETs to title strings. */
262 static int ttlcmp(void *av
, void *bv
)
264 const context
*a
= (const context
*)av
;
265 const context
*b
= (const context
*)bv
;
266 if (a
->link
->topicoffset
< b
->link
->topicoffset
)
268 if (a
->link
->topicoffset
> b
->link
->topicoffset
)
273 static int ttlindex(const void *av
, unsigned char *outbuf
)
275 const context
*a
= (const context
*)av
;
276 PUT_32BIT_LSB_FIRST(outbuf
, a
->link
->topicoffset
);
280 static int ttlleaf(const void *av
, unsigned char *outbuf
)
282 const context
*a
= (const context
*)av
;
284 PUT_32BIT_LSB_FIRST(outbuf
, a
->link
->topicoffset
);
285 slen
= 1+strlen(a
->title
);
286 memcpy(outbuf
+4, a
->title
, slen
);
290 /* The |KWBTREE internal file maps index strings to TOPICOFFSETs. */
292 static int idxcmp(void *av
, void *bv
)
294 const struct indexrec
*a
= (const struct indexrec
*)av
;
295 const struct indexrec
*b
= (const struct indexrec
*)bv
;
297 if ( (cmp
= strcmp(a
->term
, b
->term
)) != 0)
299 /* Now sort on the index field of the topics. */
300 if (a
->topic
->index
< b
->topic
->index
)
302 if (a
->topic
->index
> b
->topic
->index
)
307 static int idxindex(const void *av
, unsigned char *outbuf
)
309 const struct indexrec
*a
= (const struct indexrec
*)av
;
310 int len
= 1+strlen(a
->term
);
311 memcpy(outbuf
, a
->term
, len
);
315 static int idxleaf(const void *av
, unsigned char *outbuf
)
317 const struct indexrec
*a
= (const struct indexrec
*)av
;
318 int len
= 1+strlen(a
->term
);
319 memcpy(outbuf
, a
->term
, len
);
320 PUT_16BIT_LSB_FIRST(outbuf
+len
, a
->count
);
321 PUT_32BIT_LSB_FIRST(outbuf
+len
+2, a
->offset
);
326 * The internal `tabstops' B-tree stores pointers-to-int. Sorting
327 * is by the low 16 bits of the number (above that is flags).
330 static int tabcmp(void *av
, void *bv
)
332 const int *a
= (const int *)av
;
333 const int *b
= (const int *)bv
;
334 if ((*a
& 0xFFFF) < (*b
& 0xFFFF))
336 if ((*a
& 0xFFFF) > (*b
& 0xFFFF))
341 /* The internal `fontnames' B-tree stores strings. */
342 static int fontcmp(void *av
, void *bv
)
344 const char *a
= (const char *)av
;
345 const char *b
= (const char *)bv
;
349 /* ----------------------------------------------------------------------
350 * Manage help contexts and topics.
354 * This is the code to compute the hash of a context name. Copied
355 * straight from Winterhoff's documentation.
357 static unsigned long context_hash(char *context
)
359 signed char bytemapping
[256] =
360 "\x00\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF"
361 "\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF"
362 "\xF0\x0B\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\x0C\xFF"
363 "\x0A\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"
364 "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
365 "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2A\x0B\x0C\x0D\x0E\x0D"
366 "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
367 "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2A\x2B\x2C\x2D\x2E\x2F"
368 "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5A\x5B\x5C\x5D\x5E\x5F"
369 "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6A\x6B\x6C\x6D\x6E\x6F"
370 "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7A\x7B\x7C\x7D\x7E\x7F"
371 "\x80\x81\x82\x83\x0B\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F"
372 "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F"
373 "\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF"
374 "\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF"
375 "\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF";
378 /* Sanity check the size of unsigned long */
379 enum { assertion
= 1 /
380 (((unsigned long)0xFFFFFFFF) + 2 == (unsigned long)1) };
383 * The hash algorithm starts the hash at 0 and updates it with
384 * each character. Therefore, logically, the hash of an empty
385 * string should be 0 (it starts at 0 and is never updated);
386 * but Winterhoff says it is in fact 1. Shouldn't matter, since
387 * I never plan to use empty context names, but I'll stick the
388 * special case in here anyway.
394 * Now compute the hash in the normal way.
398 hash
= hash
* 43 + bytemapping
[(unsigned char)*context
];
404 WHLP_TOPIC
whlp_register_topic(WHLP h
, char *context_name
, char **clash
)
406 context
*ctx
= mknew(context
);
410 * Index contexts in order of creation, just so there's some
411 * sort of non-arbitrary ordering in the index B-tree. Call me
412 * fussy, but I don't like indexing on pointer values because I
413 * prefer the code to be deterministic when run under different
416 ctx
->index
= h
->ncontexts
++;
417 ctx
->browse_prev
= ctx
->browse_next
= NULL
;
421 * We have a context name, which means we can put this
422 * context straight into the `contexts' tree.
424 ctx
->name
= dupstr(context_name
);
425 ctx
->hash
= context_hash(context_name
);
426 otherctx
= add234(h
->contexts
, ctx
);
427 if (otherctx
!= ctx
) {
429 * Hash clash. Destroy the new context and return NULL,
430 * providing the clashing string.
434 if (clash
) *clash
= otherctx
->name
;
439 * We have no context name yet. Enter this into the
440 * pre_contexts tree of anonymous topics, which we will go
441 * through later and allocate unique context names and hash
445 addpos234(h
->pre_contexts
, ctx
, count234(h
->pre_contexts
));
450 void whlp_prepare(WHLP h
)
453 * We must go through pre_contexts and allocate a context ID to
454 * each anonymous context, making sure it doesn't clash with
455 * the existing contexts.
457 * Our own context IDs will just be of the form `t00000001',
458 * and we'll increment the number each time and skip over any
459 * IDs that clash with existing context names.
462 context
*ctx
, *otherctx
;
464 while ( (ctx
= index234(h
->pre_contexts
, 0)) != NULL
) {
465 delpos234(h
->pre_contexts
, 0);
466 ctx
->name
= mknewa(char, 20);
468 sprintf(ctx
->name
, "t%08d", ctx_num
++);
469 ctx
->hash
= context_hash(ctx
->name
);
470 otherctx
= add234(h
->contexts
, ctx
);
471 } while (otherctx
!= ctx
);
475 * Ensure paragraph attributes are clear for the start of text
481 char *whlp_topic_id(WHLP_TOPIC topic
)
486 void whlp_begin_topic(WHLP h
, WHLP_TOPIC topic
, char *title
, ...)
488 struct topiclink
*link
= mknew(struct topiclink
);
493 link
->nexttopic
= NULL
;
495 h
->prevtopic
->nexttopic
= link
;
498 link
->nonscroll
= link
->scroll
= NULL
;
499 link
->context
= topic
;
500 link
->block_size
= 0;
502 link
->recordtype
= 2; /* topic header */
503 link
->len1
= 4*7; /* standard linkdata1 size */
504 link
->data1
= mknewa(unsigned char, link
->len1
);
506 slen
= strlen(title
);
507 assert(slen
+1 <= TOPIC_BLKSIZE
);
508 memcpy(h
->linkdata2
, title
, slen
+1);
512 while ( (macro
= va_arg(ap
, char *)) != NULL
) {
513 slen
= strlen(macro
);
514 assert(len
+slen
+1 <= TOPIC_BLKSIZE
);
515 memcpy(h
->linkdata2
+len
, macro
, slen
+1);
519 len
--; /* lose the last \0 on the last macro */
522 link
->data2
= mknewa(unsigned char, link
->len2
);
523 memcpy(link
->data2
, h
->linkdata2
, link
->len2
);
525 topic
->title
= dupstr(title
);
528 addpos234(h
->text
, link
, count234(h
->text
));
531 void whlp_browse_link(WHLP h
, WHLP_TOPIC before
, WHLP_TOPIC after
)
536 * See if the `before' topic is already linked to another one,
537 * and break the link to that if so. Likewise the `after'
540 if (before
->browse_next
)
541 before
->browse_next
->browse_prev
= NULL
;
542 if (after
->browse_prev
)
543 after
->browse_prev
->browse_next
= NULL
;
544 before
->browse_next
= after
;
545 after
->browse_prev
= before
;
548 /* ----------------------------------------------------------------------
549 * Manage the actual generation of paragraph and text records.
552 static void whlp_linkdata(WHLP h
, int which
, int c
)
554 int *len
= (which
== 1 ?
&h
->link
->len1
: &h
->link
->len2
);
555 char *data
= (which
== 1 ? h
->linkdata1
: h
->linkdata2
);
556 assert(*len
< TOPIC_BLKSIZE
);
560 static void whlp_linkdata_short(WHLP h
, int which
, int data
)
562 whlp_linkdata(h
, which
, data
& 0xFF);
563 whlp_linkdata(h
, which
, (data
>> 8) & 0xFF);
566 static void whlp_linkdata_long(WHLP h
, int which
, int data
)
568 whlp_linkdata(h
, which
, data
& 0xFF);
569 whlp_linkdata(h
, which
, (data
>> 8) & 0xFF);
570 whlp_linkdata(h
, which
, (data
>> 16) & 0xFF);
571 whlp_linkdata(h
, which
, (data
>> 24) & 0xFF);
574 static void whlp_linkdata_cushort(WHLP h
, int which
, int data
)
577 whlp_linkdata(h
, which
, data
*2);
579 whlp_linkdata(h
, which
, 1 + (data
%128 * 2));
580 whlp_linkdata(h
, which
, data
/128);
584 static void whlp_linkdata_csshort(WHLP h
, int which
, int data
)
586 if (data
>= -0x40 && data
<= 0x3F)
587 whlp_linkdata_cushort(h
, which
, data
+64);
589 whlp_linkdata_cushort(h
, which
, data
+16384);
592 static void whlp_linkdata_culong(WHLP h
, int which
, int data
)
594 if (data
<= 0x7FFF) {
595 whlp_linkdata_short(h
, which
, data
*2);
597 whlp_linkdata_short(h
, which
, 1 + (data
%32768 * 2));
598 whlp_linkdata_short(h
, which
, data
/32768);
602 static void whlp_linkdata_cslong(WHLP h
, int which
, int data
)
604 if (data
>= -0x4000 && data
<= 0x3FFF)
605 whlp_linkdata_culong(h
, which
, data
+16384);
607 whlp_linkdata_culong(h
, which
, data
+67108864);
610 static void whlp_para_reset(WHLP h
)
616 while ( (p
= index234(h
->tabstops
, 0)) != NULL
) {
617 delpos234(h
->tabstops
, 0);
622 void whlp_para_attr(WHLP h
, int attr_id
, int attr_param
)
624 if (attr_id
>= WHLP_PARA_SPACEABOVE
&&
625 attr_id
<= WHLP_PARA_FIRSTLINEINDENT
) {
626 h
->para_flags
|= 1 << attr_id
;
627 h
->para_attrs
[attr_id
] = attr_param
;
628 } else if (attr_id
== WHLP_PARA_ALIGNMENT
) {
629 h
->para_flags
&= ~0xC00;
630 if (attr_param
== WHLP_ALIGN_RIGHT
)
631 h
->para_flags
|= 0x400;
632 else if (attr_param
== WHLP_ALIGN_CENTRE
)
633 h
->para_flags
|= 0x800;
637 void whlp_set_tabstop(WHLP h
, int tabstop
, int alignment
)
641 if (alignment
== WHLP_ALIGN_CENTRE
)
643 if (alignment
== WHLP_ALIGN_RIGHT
)
648 add234(h
->tabstops
, p
);
649 h
->para_flags
|= 0x0200;
652 void whlp_begin_para(WHLP h
, int para_type
)
654 struct topiclink
*link
= mknew(struct topiclink
);
658 * Clear these to NULL out of paranoia, although in records
659 * that aren't type 2 they should never actually be needed.
661 link
->nexttopic
= NULL
;
662 link
->context
= NULL
;
663 link
->nonscroll
= link
->scroll
= NULL
;
665 link
->recordtype
= 32; /* text record */
668 link
->len1
= link
->len2
= 0;
669 link
->data1
= h
->linkdata1
;
670 link
->data2
= h
->linkdata2
;
672 if (para_type
== WHLP_PARA_NONSCROLL
&& h
->prevtopic
&&
673 !h
->prevtopic
->nonscroll
)
674 h
->prevtopic
->nonscroll
= link
;
675 if (para_type
== WHLP_PARA_SCROLL
&& h
->prevtopic
&&
676 !h
->prevtopic
->scroll
)
677 h
->prevtopic
->scroll
= link
;
680 * Now we're ready to start accumulating stuff in linkdata1 and
681 * linkdata2. Next we build up the paragraph info. Note that
682 * the TopicSize (cslong: size of LinkData1 minus the topicsize
683 * and topiclength fields) and TopicLength (cushort: size of
684 * LinkData2) fields are missing; we will put those on when we
687 whlp_linkdata(h
, 1, 0); /* must-be-0x00 */
688 whlp_linkdata(h
, 1, 0x80); /* must-be-0x80 */
689 whlp_linkdata_short(h
, 1, 0); /* Winterhoff says `id'; always 0 AFAICT */
690 whlp_linkdata_short(h
, 1, h
->para_flags
);
691 for (i
= WHLP_PARA_SPACEABOVE
; i
<= WHLP_PARA_FIRSTLINEINDENT
; i
++) {
692 if (h
->para_flags
& (1<<i
))
693 whlp_linkdata_csshort(h
, 1, h
->para_attrs
[i
]);
695 if (h
->para_flags
& 0x0200) {
698 * Write out tab stop data.
700 ntabs
= count234(h
->tabstops
);
701 whlp_linkdata_csshort(h
, 1, ntabs
);
702 for (i
= 0; i
< ntabs
; i
++) {
704 tabp
= index234(h
->tabstops
, i
);
708 whlp_linkdata_cushort(h
, 1, tab
& 0xFFFF);
710 whlp_linkdata_cushort(h
, 1, tab
>> 16);
715 * Fine. Now we're ready to start writing actual text and
716 * formatting commands.
720 void whlp_set_font(WHLP h
, int font_id
)
723 * Write a NUL into linkdata2 to cause the reader to flip over
724 * to linkdata1 to see the formatting command.
726 whlp_linkdata(h
, 2, 0);
728 * Now the formatting command is 0x80 followed by a short.
730 whlp_linkdata(h
, 1, 0x80);
731 whlp_linkdata_short(h
, 1, font_id
);
734 void whlp_start_hyperlink(WHLP h
, WHLP_TOPIC target
)
737 * Write a NUL into linkdata2.
739 whlp_linkdata(h
, 2, 0);
741 * Now the formatting command is 0xE3 followed by the context
744 whlp_linkdata(h
, 1, 0xE3);
745 whlp_linkdata_long(h
, 1, target
->hash
);
748 void whlp_end_hyperlink(WHLP h
)
751 * Write a NUL into linkdata2.
753 whlp_linkdata(h
, 2, 0);
755 * Now the formatting command is 0x89.
757 whlp_linkdata(h
, 1, 0x89);
760 void whlp_tab(WHLP h
)
763 * Write a NUL into linkdata2.
765 whlp_linkdata(h
, 2, 0);
767 * Now the formatting command is 0x83.
769 whlp_linkdata(h
, 1, 0x83);
772 void whlp_text(WHLP h
, char *text
)
775 whlp_linkdata(h
, 2, *text
++);
779 void whlp_end_para(WHLP h
)
784 * Round off the paragraph with 0x82 and 0xFF formatting
785 * commands. Each requires a NUL in linkdata2.
787 whlp_linkdata(h
, 2, 0);
788 whlp_linkdata(h
, 1, 0x82);
789 whlp_linkdata(h
, 2, 0);
790 whlp_linkdata(h
, 1, 0xFF);
793 * Now finish up: create the header of linkdata1 (TopicLength
794 * and TopicSize fields), allocate the real linkdata1 and
795 * linkdata2 fields, and copy them out of the buffers in h.
796 * Then insert the finished topiclink into the `text' tree, and
799 data1cut
= h
->link
->len1
;
800 whlp_linkdata_cslong(h
, 1, data1cut
);
801 whlp_linkdata_cushort(h
, 1, h
->link
->len2
);
803 h
->link
->data1
= mknewa(unsigned char, h
->link
->len1
);
804 memcpy(h
->link
->data1
, h
->linkdata1
+ data1cut
, h
->link
->len1
- data1cut
);
805 memcpy(h
->link
->data1
+ h
->link
->len1
- data1cut
, h
->linkdata1
, data1cut
);
806 h
->link
->data2
= mknewa(unsigned char, h
->link
->len2
);
807 memcpy(h
->link
->data2
, h
->linkdata2
, h
->link
->len2
);
809 addpos234(h
->text
, h
->link
, count234(h
->text
));
811 /* Hack: accumulate the `blocksize' parameter in the topic header. */
813 h
->prevtopic
->block_size
+= 21 + h
->link
->len1
+ h
->link
->len2
;
815 h
->link
= NULL
; /* this is now in the tree */
820 /* ----------------------------------------------------------------------
821 * Manage the layout and generation of the |TOPIC section.
824 static void whlp_topicsect_write(WHLP h
, struct file
*f
, void *data
, int len
,
827 unsigned char *p
= (unsigned char *)data
;
829 if (h
->topicblock_remaining
<= 0 ||
830 h
->topicblock_remaining
< can_break
) {
834 if (h
->topicblock_remaining
> 0)
835 whlp_file_fill(f
, h
->topicblock_remaining
);
836 whlp_file_add_long(f
, h
->lasttopiclink
);
837 h
->firsttopiclink_offset
= whlp_file_offset(f
);
838 whlp_file_add_long(f
, -1L); /* this will be filled in later */
839 whlp_file_add_long(f
, h
->lasttopicstart
);
840 h
->topicblock_remaining
= TOPIC_BLKSIZE
- 12;
843 int thislen
= (h
->topicblock_remaining
< len ?
844 h
->topicblock_remaining
: len
);
845 whlp_file_add(f
, p
, thislen
);
848 h
->topicblock_remaining
-= thislen
;
849 if (len
> 0 && h
->topicblock_remaining
<= 0) {
853 whlp_file_add_long(f
, h
->lasttopiclink
);
854 h
->firsttopiclink_offset
= whlp_file_offset(f
);
855 whlp_file_add_long(f
, -1L); /* this will be filled in later */
856 whlp_file_add_long(f
, h
->lasttopicstart
);
857 h
->topicblock_remaining
= TOPIC_BLKSIZE
- 12;
862 static void whlp_topic_layout(WHLP h
)
864 int block
, offset
, pos
;
867 struct topiclink
*link
;
871 * Create a final TOPICLINK containing no usable data.
873 link
= mknew(struct topiclink
);
874 link
->nexttopic
= NULL
;
876 h
->prevtopic
->nexttopic
= link
;
878 link
->data1
= mknewa(unsigned char, 0x1c);
879 link
->block_size
= 0;
883 link
->nexttopic
= NULL
;
884 link
->recordtype
= 2;
885 link
->nonscroll
= link
->scroll
= NULL
;
886 link
->context
= NULL
;
887 addpos234(h
->text
, link
, count234(h
->text
));
890 * Each TOPICBLOCK has space for TOPIC_BLKSIZE-12 bytes. The
891 * size of each TOPICLINK is 21 bytes plus the combined lengths
892 * of LinkData1 and LinkData2. So we can now go through and
893 * break up the TOPICLINKs into TOPICBLOCKs, and also set up
894 * the TOPICOFFSET and TOPICPOS of each one while we do so.
900 nlinks
= count234(h
->text
);
901 for (i
= 0; i
< nlinks
; i
++) {
902 link
= index234(h
->text
, i
);
903 size
= 21 + link
->len1
+ link
->len2
;
905 * We can't split within the topicblock header or within
906 * linkdata1. So if the split would fall in that area,
907 * start a new block _now_.
909 if (TOPIC_BLKSIZE
- pos
< 21 + link
->len1
) {
914 link
->topicoffset
= block
* 0x8000 + offset
;
915 link
->topicpos
= block
* 0x4000 + pos
;
917 if (link
->recordtype
!= 2) /* TOPICOFFSET doesn't count titles */
918 offset
+= link
->len2
;
919 while (pos
> TOPIC_BLKSIZE
) {
922 pos
-= TOPIC_BLKSIZE
- 12;
927 * Now we have laid out the TOPICLINKs into blocks, and
928 * determined the final TOPICOFFSET and TOPICPOS of each one.
929 * So now we can go through and write the headers of the type-2
934 for (i
= 0; i
< nlinks
; i
++) {
935 link
= index234(h
->text
, i
);
936 if (link
->recordtype
!= 2)
939 PUT_32BIT_LSB_FIRST(link
->data1
+ 0, link
->block_size
);
940 if (link
->context
&& link
->context
->browse_prev
)
941 PUT_32BIT_LSB_FIRST(link
->data1
+ 4,
942 link
->context
->browse_prev
->link
->topicoffset
);
944 PUT_32BIT_LSB_FIRST(link
->data1
+ 4, 0xFFFFFFFFL
);
945 if (link
->context
&& link
->context
->browse_next
)
946 PUT_32BIT_LSB_FIRST(link
->data1
+ 8,
947 link
->context
->browse_next
->link
->topicoffset
);
949 PUT_32BIT_LSB_FIRST(link
->data1
+ 8, 0xFFFFFFFFL
);
950 PUT_32BIT_LSB_FIRST(link
->data1
+ 12, topicnum
);
953 PUT_32BIT_LSB_FIRST(link
->data1
+ 16, link
->nonscroll
->topicpos
);
955 PUT_32BIT_LSB_FIRST(link
->data1
+ 16, 0xFFFFFFFFL
);
957 PUT_32BIT_LSB_FIRST(link
->data1
+ 20, link
->scroll
->topicpos
);
959 PUT_32BIT_LSB_FIRST(link
->data1
+ 20, 0xFFFFFFFFL
);
961 PUT_32BIT_LSB_FIRST(link
->data1
+ 24, link
->nexttopic
->topicpos
);
963 PUT_32BIT_LSB_FIRST(link
->data1
+ 24, 0xFFFFFFFFL
);
967 * Having done all _that_, we're now finally ready to go
968 * through and create the |TOPIC section in its final form.
971 h
->lasttopiclink
= -1L;
972 h
->lasttopicstart
= 0L;
973 f
= whlp_new_file(h
, "|TOPIC");
974 h
->topicblock_remaining
= -1;
975 whlp_topicsect_write(h
, f
, NULL
, 0, 0); /* start the first block */
976 for (i
= 0; i
< nlinks
; i
++) {
977 unsigned char header
[21];
978 struct topiclink
*otherlink
;
980 link
= index234(h
->text
, i
);
983 * Create and output the TOPICLINK header.
985 PUT_32BIT_LSB_FIRST(header
+ 0, 21 + link
->len1
+ link
->len2
);
986 PUT_32BIT_LSB_FIRST(header
+ 4, link
->len2
);
988 PUT_32BIT_LSB_FIRST(header
+ 8, 0xFFFFFFFFL
);
990 otherlink
= index234(h
->text
, i
-1);
991 PUT_32BIT_LSB_FIRST(header
+ 8, otherlink
->topicpos
);
994 PUT_32BIT_LSB_FIRST(header
+ 12, 0xFFFFFFFFL
);
996 otherlink
= index234(h
->text
, i
+1);
997 PUT_32BIT_LSB_FIRST(header
+ 12, otherlink
->topicpos
);
999 PUT_32BIT_LSB_FIRST(header
+ 16, 21 + link
->len1
);
1000 header
[20] = link
->recordtype
;
1001 whlp_topicsect_write(h
, f
, header
, 21, 21 + link
->len1
);
1004 * Fill in the `first topiclink' pointer in the block
1005 * header if appropriate. (We do this _after_ outputting
1006 * the header because then we can be sure we'll be in the
1007 * same block as we think we are.)
1009 if (h
->firsttopiclink_offset
> 0) {
1010 whlp_file_seek(f
, h
->firsttopiclink_offset
, 0);
1011 whlp_file_add_long(f
, link
->topicpos
);
1012 h
->firsttopiclink_offset
= 0;
1013 whlp_file_seek(f
, 0, 2);
1017 * Update the `last topiclink', and possibly `last
1018 * topicstart', pointers.
1020 h
->lasttopiclink
= link
->topicpos
;
1021 if (link
->recordtype
== 2)
1022 h
->lasttopicstart
= link
->topicpos
;
1026 * Output LinkData1 and LinkData2.
1028 whlp_topicsect_write(h
, f
, link
->data1
, link
->len1
, link
->len1
);
1029 whlp_topicsect_write(h
, f
, link
->data2
, link
->len2
, 0);
1032 * Output the block header.
1035 link
= index234(h
->text
, i
);
1040 /* ----------------------------------------------------------------------
1041 * Manage the index sections (|KWDATA, |KWMAP, |KWBTREE).
1044 void whlp_index_term(WHLP h
, char *index
, WHLP_TOPIC topic
)
1046 struct indexrec
*idx
= mknew(struct indexrec
);
1048 idx
->term
= dupstr(index
);
1051 * If this reference is already in the tree, just silently drop
1054 if (add234(h
->index
, idx
) != idx
) {
1060 static void whlp_build_kwdata(WHLP h
)
1064 struct indexrec
*first
, *next
;
1066 f
= whlp_new_file(h
, "|KWDATA");
1069 * Go through the index B-tree, condensing all sequences of
1070 * records with the same term into a single one with a valid
1071 * (count,offset) pair, and building up the KWDATA section.
1074 while ( (first
= index234(h
->index
, i
)) != NULL
) {
1076 first
->offset
= whlp_file_offset(f
);
1077 whlp_file_add_long(f
, first
->topic
->link
->topicoffset
);
1079 while ( (next
= index234(h
->index
, i
)) != NULL
&&
1080 !strcmp(first
->term
, next
->term
)) {
1082 * The next index record has the same term. Fold it
1083 * into this one and remove from the tree.
1085 whlp_file_add_long(f
, next
->topic
->link
->topicoffset
);
1087 delpos234(h
->index
, i
);
1094 * Now we should have `index' in a form that's ready to
1095 * construct |KWBTREE. So we can return.
1099 /* ----------------------------------------------------------------------
1100 * Standard chunks of data for the |SYSTEM and |FONT sections.
1103 static void whlp_system_record(struct file
*f
, int id
,
1104 const void *data
, int length
)
1106 whlp_file_add_short(f
, id
);
1107 whlp_file_add_short(f
, length
);
1108 whlp_file_add(f
, data
, length
);
1111 static void whlp_standard_systemsection(struct file
*f
)
1113 const char lcid
[] = { 0, 0, 0, 0, 0, 0, 0, 0, 9, 4 };
1114 const char charset
[] = { 0, 0, 0, 2, 0 };
1116 whlp_file_add_short(f
, 0x36C); /* magic number */
1117 whlp_file_add_short(f
, 33); /* minor version: HCW 4.00 Win95+ */
1118 whlp_file_add_short(f
, 1); /* major version */
1119 whlp_file_add_long(f
, time(NULL
)); /* generation date */
1120 whlp_file_add_short(f
, 0); /* flags=0 means no compression */
1123 * Add some magic locale identifier information. (We ought to
1124 * find out something about what all this means; see the TODO
1125 * list at the top of the file.)
1127 whlp_system_record(f
, 9, lcid
, sizeof(lcid
));
1128 whlp_system_record(f
, 11, charset
, sizeof(charset
));
1131 void whlp_title(WHLP h
, char *title
)
1133 whlp_system_record(h
->systemfile
, 1, title
, 1+strlen(title
));
1136 void whlp_copyright(WHLP h
, char *copyright
)
1138 whlp_system_record(h
->systemfile
, 2, copyright
, 1+strlen(copyright
));
1141 void whlp_start_macro(WHLP h
, char *macro
)
1143 whlp_system_record(h
->systemfile
, 4, macro
, 1+strlen(macro
));
1146 void whlp_primary_topic(WHLP h
, WHLP_TOPIC t
)
1151 static void whlp_do_primary_topic(WHLP h
)
1153 unsigned char firsttopic
[4];
1154 PUT_32BIT_LSB_FIRST(firsttopic
, h
->ptopic
->link
->topicoffset
);
1155 whlp_system_record(h
->systemfile
, 3, firsttopic
, sizeof(firsttopic
));
1158 int whlp_create_font(WHLP h
, char *font
, int family
, int halfpoints
,
1159 int rendition
, int r
, int g
, int b
)
1161 char *fontname
= dupstr(font
);
1162 struct fontdesc
*fontdesc
;
1165 font
= add234(h
->fontnames
, fontname
);
1166 if (font
!= fontname
) {
1167 /* The font name was already present. Free the new copy. */
1171 fontdesc
= mknew(struct fontdesc
);
1172 fontdesc
->font
= font
;
1173 fontdesc
->family
= family
;
1174 fontdesc
->halfpoints
= halfpoints
;
1175 fontdesc
->rendition
= rendition
;
1180 index
= count234(h
->fontdescs
);
1181 addpos234(h
->fontdescs
, fontdesc
, index
);
1185 static void whlp_make_fontsection(WHLP h
, struct file
*f
)
1189 struct fontdesc
*fontdesc
;
1192 * Header block: number of font names, number of font
1193 * descriptors, offset to font names, and offset to font
1196 whlp_file_add_short(f
, count234(h
->fontnames
));
1197 whlp_file_add_short(f
, count234(h
->fontdescs
));
1198 whlp_file_add_short(f
, 8);
1199 whlp_file_add_short(f
, 8 + 32 * count234(h
->fontnames
));
1204 for (i
= 0; (fontname
= index234(h
->fontnames
, i
)) != NULL
; i
++) {
1206 memset(data
, i
, sizeof(data
));
1207 strncpy(data
, fontname
, sizeof(data
));
1208 whlp_file_add(f
, data
, sizeof(data
));
1214 for (i
= 0; (fontdesc
= index234(h
->fontdescs
, i
)) != NULL
; i
++) {
1218 ret
= findpos234(h
->fontnames
, fontdesc
->font
, NULL
, &fontpos
);
1219 assert(ret
!= NULL
);
1221 whlp_file_add_char(f
, fontdesc
->rendition
);
1222 whlp_file_add_char(f
, fontdesc
->halfpoints
);
1223 whlp_file_add_char(f
, fontdesc
->family
);
1224 whlp_file_add_short(f
, fontpos
);
1225 /* Foreground RGB */
1226 whlp_file_add_char(f
, fontdesc
->r
);
1227 whlp_file_add_char(f
, fontdesc
->g
);
1228 whlp_file_add_char(f
, fontdesc
->b
);
1229 /* Background RGB is apparently unused and always set to zero */
1230 whlp_file_add_char(f
, 0);
1231 whlp_file_add_char(f
, 0);
1232 whlp_file_add_char(f
, 0);
1237 /* ----------------------------------------------------------------------
1238 * Routines to manage a B-tree type file.
1241 static void whlp_make_btree(struct file
*f
, int flags
, int pagesize
,
1242 char *dataformat
, tree234
*tree
,
1244 bt_index_fn indexfn
, bt_leaf_fn leaffn
)
1246 void **page_elements
= NULL
;
1247 int npages
= 0, pagessize
= 0;
1248 int npages_this_level
, nentries
, nlevels
;
1249 int total_leaf_entries
;
1250 char btdata
[MAX_PAGE_SIZE
];
1252 int page_start
, fixups_offset
, unused_bytes
;
1256 assert(pagesize
<= MAX_PAGE_SIZE
);
1259 * Start with the B-tree header. We'll have to come back and
1260 * fill in a few bits later.
1262 whlp_file_add_short(f
, 0x293B); /* magic number */
1263 whlp_file_add_short(f
, flags
);
1264 whlp_file_add_short(f
, pagesize
);
1267 memset(data
, 0, sizeof(data
));
1268 assert(strlen(dataformat
) <= sizeof(data
));
1269 memcpy(data
, dataformat
, strlen(dataformat
));
1270 whlp_file_add(f
, data
, sizeof(data
));
1272 whlp_file_add_short(f
, 0); /* must-be-zero */
1273 fixups_offset
= whlp_file_offset(f
);
1274 whlp_file_add_short(f
, 0); /* page splits; fix up later */
1275 whlp_file_add_short(f
, 0); /* root page index; fix up later */
1276 whlp_file_add_short(f
, -1); /* must-be-minus-one */
1277 whlp_file_add_short(f
, 0); /* total number of pages; fix later */
1278 whlp_file_add_short(f
, 0); /* number of levels; fix later */
1279 whlp_file_add_long(f
, count234(tree
));/* total B-tree entries */
1282 * If we have a map section, leave space at the start for its
1286 whlp_file_add_short(map
, 0);
1290 * Now create the leaf pages.
1294 npages_this_level
= 0;
1295 total_leaf_entries
= 0;
1297 element
= index234(tree
, index
);
1300 * Make a new leaf page.
1302 npages_this_level
++;
1303 if (npages
>= pagessize
) {
1304 pagessize
= npages
+ 32;
1305 page_elements
= resize(page_elements
, pagessize
);
1307 page_elements
[npages
++] = element
;
1310 * Leave space in the leaf page for the header. We'll
1311 * come back and add it later.
1313 page_start
= whlp_file_offset(f
);
1314 whlp_file_add(f
, "12345678", 8);
1315 unused_bytes
= pagesize
- 8;
1319 * Now add leaf entries until we run out of room, or out of
1323 btlen
= leaffn(element
, btdata
);
1324 if (btlen
> unused_bytes
)
1326 whlp_file_add(f
, btdata
, btlen
);
1327 unused_bytes
-= btlen
;
1330 element
= index234(tree
, index
);
1334 * Now add the unused bytes, and then go back and put
1337 whlp_file_fill(f
, unused_bytes
);
1338 whlp_file_seek(f
, page_start
, 0);
1339 whlp_file_add_short(f
, unused_bytes
);
1340 whlp_file_add_short(f
, nentries
);
1341 /* Previous-page indicator will automatically go to -1 when
1343 whlp_file_add_short(f
, npages
-2);
1344 /* Next-page indicator must be -1 if we're at the end. */
1346 whlp_file_add_short(f
, -1);
1348 whlp_file_add_short(f
, npages
);
1349 whlp_file_seek(f
, 0, 2);
1352 * If we have a map section, add a map entry.
1355 whlp_file_add_long(map
, total_leaf_entries
);
1356 whlp_file_add_short(map
, npages_this_level
-1);
1358 total_leaf_entries
+= nentries
;
1362 * If we have a map section, write the total number of map
1366 whlp_file_seek(map
, 0, 0);
1367 whlp_file_add_short(map
, npages_this_level
);
1368 whlp_file_seek(map
, 0, 2);
1372 * Now create further levels until we're down to one page.
1375 while (npages_this_level
> 1) {
1376 int first
= npages
- npages_this_level
;
1377 int last
= npages
- 1;
1381 npages_this_level
= 0;
1384 while (current
<= last
) {
1386 * Make a new index page.
1388 npages_this_level
++;
1389 if (npages
>= pagessize
) {
1390 pagessize
= npages
+ 32;
1391 page_elements
= resize(page_elements
, pagessize
);
1393 page_elements
[npages
++] = page_elements
[current
];
1396 * Leave space for some of the header, but we can put
1397 * in the PreviousPage link already.
1399 page_start
= whlp_file_offset(f
);
1400 whlp_file_add(f
, "1234", 4);
1401 whlp_file_add_short(f
, current
);
1402 unused_bytes
= pagesize
- 6;
1405 * Now add index entries until we run out of either
1410 while (current
<= last
) {
1411 btlen
= indexfn(page_elements
[current
], btdata
);
1412 if (btlen
+ 2 > unused_bytes
)
1414 whlp_file_add(f
, btdata
, btlen
);
1415 whlp_file_add_short(f
, current
);
1416 unused_bytes
-= btlen
+2;
1422 * Now add the unused bytes, and then go back and put
1425 whlp_file_fill(f
, unused_bytes
);
1426 whlp_file_seek(f
, page_start
, 0);
1427 whlp_file_add_short(f
, unused_bytes
);
1428 whlp_file_add_short(f
, nentries
);
1429 whlp_file_seek(f
, 0, 2);
1434 * Now we have all our pages ready, and we know where our root
1435 * page is. Fix up the main B-tree header.
1437 whlp_file_seek(f
, fixups_offset
, 0);
1438 /* Creation of every page requires a split unless it's the first in
1439 * a new level. Hence, page splits equals pages minus levels. */
1440 whlp_file_add_short(f
, npages
- nlevels
);
1441 whlp_file_add_short(f
, npages
-1); /* root page index */
1442 whlp_file_add_short(f
, -1); /* must-be-minus-one */
1443 whlp_file_add_short(f
, npages
); /* total number of pages */
1444 whlp_file_add_short(f
, nlevels
); /* number of levels */
1446 /* Just for tidiness, seek to the end of the file :-) */
1447 whlp_file_seek(f
, 0, 2);
1450 sfree(page_elements
);
1454 /* ----------------------------------------------------------------------
1455 * Routines to manage the `internal file' structure.
1458 static struct file
*whlp_new_file(WHLP h
, char *name
)
1461 f
= mknew(struct file
);
1463 f
->pos
= f
->len
= f
->size
= 0;
1465 f
->name
= dupstr(name
);
1466 add234(h
->files
, f
);
1473 static void whlp_free_file(struct file
*f
)
1476 sfree(f
->name
); /* may be NULL */
1480 static void whlp_file_add(struct file
*f
, const void *data
, int len
)
1482 if (f
->pos
+ len
> f
->size
) {
1483 f
->size
= f
->pos
+ len
+ 1024;
1484 f
->data
= resize(f
->data
, f
->size
);
1486 memcpy(f
->data
+ f
->pos
, data
, len
);
1488 if (f
->len
< f
->pos
)
1492 static void whlp_file_add_char(struct file
*f
, int data
)
1496 whlp_file_add(f
, &s
, 1);
1499 static void whlp_file_add_short(struct file
*f
, int data
)
1502 PUT_16BIT_LSB_FIRST(s
, data
);
1503 whlp_file_add(f
, s
, 2);
1506 static void whlp_file_add_long(struct file
*f
, int data
)
1509 PUT_32BIT_LSB_FIRST(s
, data
);
1510 whlp_file_add(f
, s
, 4);
1513 static void whlp_file_fill(struct file
*f
, int len
)
1515 if (f
->pos
+ len
> f
->size
) {
1516 f
->size
= f
->pos
+ len
+ 1024;
1517 f
->data
= resize(f
->data
, f
->size
);
1519 memset(f
->data
+ f
->pos
, 0, len
);
1521 if (f
->len
< f
->pos
)
1525 static void whlp_file_seek(struct file
*f
, int pos
, int whence
)
1527 f
->pos
= (whence
== 0 ?
0 : whence
== 1 ? f
->pos
: f
->len
) + pos
;
1530 static int whlp_file_offset(struct file
*f
)
1535 /* ----------------------------------------------------------------------
1536 * Open and close routines; final wrapper around everything.
1544 ret
= mknew(struct WHLP_tag
);
1549 ret
->files
= newtree234(filecmp
);
1550 ret
->pre_contexts
= newtree234(NULL
);
1551 ret
->contexts
= newtree234(ctxcmp
);
1552 ret
->titles
= newtree234(ttlcmp
);
1553 ret
->text
= newtree234(NULL
);
1554 ret
->index
= newtree234(idxcmp
);
1555 ret
->tabstops
= newtree234(tabcmp
);
1556 ret
->fontnames
= newtree234(fontcmp
);
1557 ret
->fontdescs
= newtree234(NULL
);
1560 * Some standard files.
1562 f
= whlp_new_file(ret
, "|CTXOMAP");
1563 whlp_file_add_short(f
, 0); /* dummy section */
1564 f
= whlp_new_file(ret
, "|SYSTEM");
1565 whlp_standard_systemsection(f
);
1566 ret
->systemfile
= f
;
1571 ret
->prevtopic
= NULL
;
1578 void whlp_close(WHLP h
, char *filename
)
1581 int filecount
, offset
, index
, filelen
;
1582 struct file
*file
, *map
, *md
;
1587 * Lay out the topic section.
1589 whlp_topic_layout(h
);
1592 * Finish off the system section.
1594 whlp_do_primary_topic(h
);
1597 * Assemble the font section.
1599 file
= whlp_new_file(h
, "|FONT");
1600 whlp_make_fontsection(h
, file
);
1605 has_index
= (count234(h
->index
) != 0);
1607 whlp_build_kwdata(h
);
1610 * Set up the `titles' B-tree for the |TTLBTREE section.
1612 for (index
= 0; (ctx
= index234(h
->contexts
, index
)) != NULL
; index
++)
1613 add234(h
->titles
, ctx
);
1616 * Construct the various B-trees.
1618 file
= whlp_new_file(h
, "|CONTEXT");
1619 whlp_make_btree(file
, 0x0002, 0x0800, "L4",
1620 h
->contexts
, NULL
, ctxindex
, ctxleaf
);
1622 file
= whlp_new_file(h
, "|TTLBTREE");
1623 whlp_make_btree(file
, 0x0002, 0x0800, "Lz",
1624 h
->titles
, NULL
, ttlindex
, ttlleaf
);
1627 file
= whlp_new_file(h
, "|KWBTREE");
1628 map
= whlp_new_file(h
, "|KWMAP");
1629 whlp_make_btree(file
, 0x0002, 0x0800, "F24",
1630 h
->index
, map
, idxindex
, idxleaf
);
1634 * Open the output file.
1636 fp
= fopen(filename
, "wb");
1643 * Work out all the file offsets.
1645 filecount
= count234(h
->files
);
1646 offset
= 16; /* just after header */
1647 for (index
= 0; index
< filecount
; index
++) {
1648 file
= index234(h
->files
, index
);
1649 file
->fileoffset
= offset
;
1650 offset
+= 9 + file
->len
; /* 9 is size of file header */
1652 /* Now `offset' holds what will be the offset of the master directory. */
1654 md
= whlp_new_file(h
, NULL
); /* master directory file */
1655 whlp_make_btree(md
, 0x0402, 0x0400, "z4",
1656 h
->files
, NULL
, fileindex
, fileleaf
);
1658 filelen
= offset
+ 9 + md
->len
;
1661 * Write out the file header.
1664 unsigned char header
[16];
1665 PUT_32BIT_LSB_FIRST(header
+0, 0x00035F3FL
); /* magic */
1666 PUT_32BIT_LSB_FIRST(header
+4, offset
); /* offset to directory */
1667 PUT_32BIT_LSB_FIRST(header
+8, 0xFFFFFFFFL
); /* first free block */
1668 PUT_32BIT_LSB_FIRST(header
+12, filelen
); /* total file length */
1669 fwrite(header
, 1, 16, fp
);
1673 * Now write out each file.
1675 for (index
= 0; index
<= filecount
; index
++) {
1677 unsigned char header
[9];
1679 if (index
== filecount
)
1680 file
= md
; /* master directory comes last */
1682 file
= index234(h
->files
, index
);
1685 reserved
= used
+ 9;
1688 PUT_32BIT_LSB_FIRST(header
+0, reserved
);
1689 PUT_32BIT_LSB_FIRST(header
+4, used
);
1690 header
[8] = 0; /* flags */
1691 fwrite(header
, 1, 9, fp
);
1694 fwrite(file
->data
, 1, file
->len
, fp
);
1701 whlp_abandon(h
); /* now free everything */
1704 void whlp_abandon(WHLP h
)
1707 struct indexrec
*idx
;
1708 struct topiclink
*link
;
1709 struct fontdesc
*fontdesc
;
1713 /* Get rid of any lingering tab stops. */
1716 /* Delete the (now empty) tabstops tree. */
1717 freetree234(h
->tabstops
);
1719 /* Delete the index tree and all its entries. */
1720 while ( (idx
= index234(h
->index
, 0)) != NULL
) {
1721 delpos234(h
->index
, 0);
1725 freetree234(h
->index
);
1727 /* Delete the text tree and all its topiclinks. */
1728 while ( (link
= index234(h
->text
, 0)) != NULL
) {
1729 delpos234(h
->text
, 0);
1730 sfree(link
->data1
); /* may be NULL */
1731 sfree(link
->data2
); /* may be NULL */
1734 freetree234(h
->text
);
1736 /* Delete the fontdescs tree and all its entries. */
1737 while ( (fontdesc
= index234(h
->fontdescs
, 0)) != NULL
) {
1738 delpos234(h
->fontdescs
, 0);
1741 freetree234(h
->fontdescs
);
1743 /* Delete the fontnames tree and all its entries. */
1744 while ( (fontname
= index234(h
->fontnames
, 0)) != NULL
) {
1745 delpos234(h
->fontnames
, 0);
1748 freetree234(h
->fontnames
);
1750 /* There might be an unclosed paragraph in h->link. */
1752 sfree(h
->link
); /* if so it won't have data1 or data2 */
1755 * `titles' contains copies of the `contexts' entries, so we
1756 * don't need to free them here.
1758 freetree234(h
->titles
);
1761 * `contexts' and `pre_contexts' _both_ contain contexts that
1762 * need freeing. (pre_contexts shouldn't contain any, unless
1763 * the help generation was abandoned half-way through.)
1765 while ( (ctx
= index234(h
->pre_contexts
, 0)) != NULL
) {
1766 delpos234(h
->index
, 0);
1771 freetree234(h
->pre_contexts
);
1772 while ( (ctx
= index234(h
->contexts
, 0)) != NULL
) {
1773 delpos234(h
->contexts
, 0);
1778 freetree234(h
->contexts
);
1781 * Free all the internal files.
1783 while ( (f
= index234(h
->files
, 0)) != NULL
) {
1784 delpos234(h
->files
, 0);
1787 freetree234(h
->files
);
1797 WHLP_TOPIC t1
, t2
, t3
;
1803 whlp_title(h
, "Test Help File");
1804 whlp_copyright(h
, "This manual is copyright \251 2001 Simon Tatham."
1805 " All rights reversed.");
1806 whlp_start_macro(h
, "CB(\"btn_about\",\"&About\",\"About()\")");
1807 whlp_start_macro(h
, "CB(\"btn_up\",\"&Up\",\"Contents()\")");
1808 whlp_start_macro(h
, "BrowseButtons()");
1810 whlp_create_font(h
, "Arial", WHLP_FONTFAM_SANS
, 30,
1812 whlp_create_font(h
, "Times New Roman", WHLP_FONTFAM_SERIF
, 24,
1813 WHLP_FONT_STRIKEOUT
, 0, 0, 0);
1814 whlp_create_font(h
, "Times New Roman", WHLP_FONTFAM_SERIF
, 24,
1815 WHLP_FONT_ITALIC
, 0, 0, 0);
1816 whlp_create_font(h
, "Courier New", WHLP_FONTFAM_FIXED
, 24,
1819 t1
= whlp_register_topic(h
, "foobar", &e
);
1821 t2
= whlp_register_topic(h
, "M359HPEHGW", &e
);
1823 t3
= whlp_register_topic(h
, "Y5VQEXZQVJ", &e
);
1824 assert(t3
== NULL
&& !strcmp(e
, "M359HPEHGW"));
1825 t3
= whlp_register_topic(h
, NULL
, NULL
);
1828 whlp_primary_topic(h
, t2
);
1832 whlp_begin_topic(h
, t1
, "First Topic", "DB(\"btn_up\")", NULL
);
1834 whlp_begin_para(h
, WHLP_PARA_NONSCROLL
);
1835 whlp_set_font(h
, 0);
1836 whlp_text(h
, "Foobar");
1839 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1840 whlp_set_font(h
, 1);
1841 whlp_text(h
, "This is a silly paragraph with ");
1842 whlp_set_font(h
, 3);
1843 whlp_text(h
, "code");
1844 whlp_set_font(h
, 1);
1845 whlp_text(h
, " in it.");
1848 whlp_para_attr(h
, WHLP_PARA_SPACEABOVE
, 12);
1849 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1850 whlp_set_font(h
, 1);
1851 whlp_text(h
, "This second, equally silly, paragraph has ");
1852 whlp_set_font(h
, 2);
1853 whlp_text(h
, "emphasis");
1854 whlp_set_font(h
, 1);
1855 whlp_text(h
, " just to prove we can do it.");
1858 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1859 whlp_set_font(h
, 1);
1860 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1861 " to make some wrapping happen, and also to make the topicblock"
1862 " go across its boundaries. This is going to take a fair amount"
1863 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1866 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1867 whlp_set_font(h
, 1);
1868 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1869 " to make some wrapping happen, and also to make the topicblock"
1870 " go across its boundaries. This is going to take a fair amount"
1871 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1874 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1875 whlp_set_font(h
, 1);
1876 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1877 " to make some wrapping happen, and also to make the topicblock"
1878 " go across its boundaries. This is going to take a fair amount"
1879 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1882 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1883 whlp_set_font(h
, 1);
1884 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1885 " to make some wrapping happen, and also to make the topicblock"
1886 " go across its boundaries. This is going to take a fair amount"
1887 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1890 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1891 whlp_set_font(h
, 1);
1892 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1893 " to make some wrapping happen, and also to make the topicblock"
1894 " go across its boundaries. This is going to take a fair amount"
1895 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1898 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1899 whlp_set_font(h
, 1);
1900 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1901 " to make some wrapping happen, and also to make the topicblock"
1902 " go across its boundaries. This is going to take a fair amount"
1903 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1906 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1907 whlp_set_font(h
, 1);
1908 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1909 " to make some wrapping happen, and also to make the topicblock"
1910 " go across its boundaries. This is going to take a fair amount"
1911 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1914 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1915 whlp_set_font(h
, 1);
1916 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1917 " to make some wrapping happen, and also to make the topicblock"
1918 " go across its boundaries. This is going to take a fair amount"
1919 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1922 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1923 whlp_set_font(h
, 1);
1924 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1925 " to make some wrapping happen, and also to make the topicblock"
1926 " go across its boundaries. This is going to take a fair amount"
1927 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1930 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1931 whlp_set_font(h
, 1);
1932 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1933 " to make some wrapping happen, and also to make the topicblock"
1934 " go across its boundaries. This is going to take a fair amount"
1935 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1938 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1939 whlp_set_font(h
, 1);
1940 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1941 " to make some wrapping happen, and also to make the topicblock"
1942 " go across its boundaries. This is going to take a fair amount"
1943 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1946 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1947 whlp_set_font(h
, 1);
1948 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1949 " to make some wrapping happen, and also to make the topicblock"
1950 " go across its boundaries. This is going to take a fair amount"
1951 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1954 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1955 whlp_set_font(h
, 1);
1956 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1957 " to make some wrapping happen, and also to make the topicblock"
1958 " go across its boundaries. This is going to take a fair amount"
1959 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1962 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1963 whlp_set_font(h
, 1);
1964 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1965 " to make some wrapping happen, and also to make the topicblock"
1966 " go across its boundaries. This is going to take a fair amount"
1967 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1970 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1971 whlp_set_font(h
, 1);
1972 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1973 " to make some wrapping happen, and also to make the topicblock"
1974 " go across its boundaries. This is going to take a fair amount"
1975 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1978 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1979 whlp_set_font(h
, 1);
1980 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1981 " to make some wrapping happen, and also to make the topicblock"
1982 " go across its boundaries. This is going to take a fair amount"
1983 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1986 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1987 whlp_set_font(h
, 1);
1988 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1989 " to make some wrapping happen, and also to make the topicblock"
1990 " go across its boundaries. This is going to take a fair amount"
1991 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1994 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1995 whlp_set_font(h
, 1);
1996 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1997 " to make some wrapping happen, and also to make the topicblock"
1998 " go across its boundaries. This is going to take a fair amount"
1999 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2002 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2003 whlp_set_font(h
, 1);
2004 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2005 " to make some wrapping happen, and also to make the topicblock"
2006 " go across its boundaries. This is going to take a fair amount"
2007 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2010 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2011 whlp_set_font(h
, 1);
2012 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2013 " to make some wrapping happen, and also to make the topicblock"
2014 " go across its boundaries. This is going to take a fair amount"
2015 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2018 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2019 whlp_set_font(h
, 1);
2020 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2021 " to make some wrapping happen, and also to make the topicblock"
2022 " go across its boundaries. This is going to take a fair amount"
2023 " of text, so I'll just have to cheat and c'n'p a lot of it.");
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.");
2034 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2035 whlp_set_font(h
, 1);
2036 whlp_text(h
, "Have a ");
2037 whlp_start_hyperlink(h
, t2
);
2038 whlp_text(h
, "hyperlink");
2039 whlp_end_hyperlink(h
);
2040 whlp_text(h
, " to another topic.");
2043 sprintf(mymacro
, "CBB(\"btn_up\",\"JI(`',`%s')\");EB(\"btn_up\")",
2046 whlp_begin_topic(h
, t2
, "Second Topic", mymacro
, NULL
);
2048 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2049 whlp_set_font(h
, 1);
2050 whlp_text(h
, "This topic contains no non-scrolling region. I would"
2051 " illustrate this with a ludicrously long paragraph, but that"
2052 " would get very tedious very quickly. Instead I'll just waffle"
2053 " on pointlessly for a little bit and then shut up.");
2056 whlp_set_tabstop(h
, 36, WHLP_ALIGN_LEFT
);
2057 whlp_para_attr(h
, WHLP_PARA_LEFTINDENT
, 36);
2058 whlp_para_attr(h
, WHLP_PARA_FIRSTLINEINDENT
, -36);
2059 whlp_para_attr(h
, WHLP_PARA_SPACEABOVE
, 12);
2060 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2061 whlp_set_font(h
, 1);
2062 whlp_text(h
, "\225"); /* bullet */
2064 whlp_text(h
, "This is a paragraph with a bullet. With any luck it should"
2065 " work exactly like it used to in the old NASM help file.");
2068 whlp_set_tabstop(h
, 128, WHLP_ALIGN_RIGHT
);
2069 whlp_set_tabstop(h
, 256, WHLP_ALIGN_CENTRE
);
2070 whlp_set_tabstop(h
, 384, WHLP_ALIGN_LEFT
);
2071 whlp_para_attr(h
, WHLP_PARA_SPACEABOVE
, 12);
2072 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2073 whlp_set_font(h
, 1);
2074 whlp_text(h
, "Ooh:"); whlp_tab(h
);
2075 whlp_text(h
, "Right?"); whlp_tab(h
);
2076 whlp_text(h
, "Centre?"); whlp_tab(h
);
2077 whlp_text(h
, "Left?");
2080 whlp_set_tabstop(h
, 128, WHLP_ALIGN_RIGHT
);
2081 whlp_set_tabstop(h
, 256, WHLP_ALIGN_CENTRE
);
2082 whlp_set_tabstop(h
, 384, WHLP_ALIGN_LEFT
);
2083 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2084 whlp_set_font(h
, 1);
2085 whlp_text(h
, "Aah:"); whlp_tab(h
);
2086 whlp_text(h
, "R?"); whlp_tab(h
);
2087 whlp_text(h
, "C?"); whlp_tab(h
);
2091 sprintf(mymacro
, "CBB(\"btn_up\",\"JI(`',`%s')\");EB(\"btn_up\")",
2094 whlp_begin_topic(h
, t3
, "Third Topic", mymacro
, NULL
);
2096 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2097 whlp_set_font(h
, 1);
2098 whlp_text(h
, "This third topic is almost as boring as the first. Woo!");
2104 whlp_browse_link(h
, t1
, t2
);
2105 whlp_browse_link(h
, t2
, t3
);
2110 whlp_index_term(h
, "foobarbaz", t1
);
2111 whlp_index_term(h
, "foobarbaz", t2
);
2112 whlp_index_term(h
, "foobarbaz", t3
);
2113 whlp_index_term(h
, "foobar", t1
);
2114 whlp_index_term(h
, "foobar", t2
);
2115 whlp_index_term(h
, "foobaz", t1
);
2116 whlp_index_term(h
, "foobaz", t3
);
2117 whlp_index_term(h
, "barbaz", t2
);
2118 whlp_index_term(h
, "barbaz", t3
);
2119 whlp_index_term(h
, "foo", t1
);
2120 whlp_index_term(h
, "bar", t2
);
2121 whlp_index_term(h
, "baz", t3
);
2123 whlp_close(h
, "test.hlp");