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 snew(type) ( (type *) smalloc (sizeof (type)) )
87 #define snewn(number, type) ( (type *) smalloc ((number) * sizeof (type)) )
88 #define sresize(array, len, type) \
89 ( (type *) srealloc ((array), (len) * sizeof (type)) )
90 #define lenof(array) ( sizeof(array) / sizeof(*(array)) )
91 char *dupstr(char *s
) {
92 char *r
= snewn(1+strlen(s
), char); strcpy(r
,s
); return r
;
96 #define UNUSEDARG(x) ( (x) = (x) )
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))
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)
110 #define GET_16BIT_LSB_FIRST(cp) \
111 (((unsigned long)(unsigned char)(cp)[0]) | \
112 ((unsigned long)(unsigned char)(cp)[1] << 8))
114 #define PUT_16BIT_LSB_FIRST(cp, value) do { \
115 (cp)[0] = 0xFF & (value); \
116 (cp)[1] = 0xFF & ((value) >> 8); } while (0)
118 #define MAX_PAGE_SIZE 0x800 /* max page size in any B-tree */
119 #define TOPIC_BLKSIZE 4096 /* implied by version/flags combo */
121 typedef struct WHLP_TOPIC_tag context
;
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 */
133 char *term
; /* index term, will need freeing */
134 context
*topic
; /* topic it links to */
135 int count
, offset
; /* used when building |KWDATA */
139 int topicoffset
, topicpos
; /* for referencing from elsewhere */
142 unsigned char *data1
, *data2
;
144 struct topiclink
*nonscroll
, *scroll
, *nexttopic
;
145 int block_size
; /* for the topic header - *boggle* */
148 struct WHLP_TOPIC_tag
{
149 char *name
; /* needs freeing */
151 struct topiclink
*link
; /* this provides TOPICOFFSET */
152 context
*browse_next
, *browse_prev
;
153 char *title
; /* needs freeing */
154 int index
; /* arbitrary number */
159 int family
, rendition
, halfpoints
;
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 */
188 /* Functions to return the index and leaf data for B-tree contents. */
189 typedef int (*bt_index_fn
)(const void *item
, unsigned char *outbuf
);
190 typedef int (*bt_leaf_fn
)(const void *item
, unsigned char *outbuf
);
192 /* Forward references. */
193 static void whlp_para_reset(WHLP h
);
194 static struct file
*whlp_new_file(WHLP h
, char *name
);
195 static void whlp_file_add(struct file
*f
, const void *data
, int len
);
196 static void whlp_file_add_char(struct file
*f
, int data
);
197 static void whlp_file_add_short(struct file
*f
, int data
);
198 static void whlp_file_add_long(struct file
*f
, int data
);
199 static void whlp_file_fill(struct file
*f
, int len
);
200 static void whlp_file_seek(struct file
*f
, int pos
, int whence
);
201 static int whlp_file_offset(struct file
*f
);
203 /* ----------------------------------------------------------------------
204 * Fiddly little functions: B-tree compare, index and leaf functions.
207 /* The master index maps file names to help-file offsets. */
209 static int filecmp(void *av
, void *bv
)
211 const struct file
*a
= (const struct file
*)av
;
212 const struct file
*b
= (const struct file
*)bv
;
213 return strcmp(a
->name
, b
->name
);
216 static int fileindex(const void *av
, unsigned char *outbuf
)
218 const struct file
*a
= (const struct file
*)av
;
219 int len
= 1+strlen(a
->name
);
220 memcpy(outbuf
, a
->name
, len
);
224 static int fileleaf(const void *av
, unsigned char *outbuf
)
226 const struct file
*a
= (const struct file
*)av
;
227 int len
= 1+strlen(a
->name
);
228 memcpy(outbuf
, a
->name
, len
);
229 PUT_32BIT_LSB_FIRST(outbuf
+len
, a
->fileoffset
);
233 /* The |CONTEXT internal file maps help context hashes to TOPICOFFSETs. */
235 static int ctxcmp(void *av
, void *bv
)
237 const context
*a
= (const context
*)av
;
238 const context
*b
= (const context
*)bv
;
239 if ((signed long)a
->hash
< (signed long)b
->hash
)
241 if ((signed long)a
->hash
> (signed long)b
->hash
)
246 static int ctxindex(const void *av
, unsigned char *outbuf
)
248 const context
*a
= (const context
*)av
;
249 PUT_32BIT_LSB_FIRST(outbuf
, a
->hash
);
253 static int ctxleaf(const void *av
, unsigned char *outbuf
)
255 const context
*a
= (const context
*)av
;
256 PUT_32BIT_LSB_FIRST(outbuf
, a
->hash
);
257 PUT_32BIT_LSB_FIRST(outbuf
+4, a
->link
->topicoffset
);
261 /* The |TTLBTREE internal file maps TOPICOFFSETs to title strings. */
263 static int ttlcmp(void *av
, void *bv
)
265 const context
*a
= (const context
*)av
;
266 const context
*b
= (const context
*)bv
;
267 if (a
->link
->topicoffset
< b
->link
->topicoffset
)
269 if (a
->link
->topicoffset
> b
->link
->topicoffset
)
274 static int ttlindex(const void *av
, unsigned char *outbuf
)
276 const context
*a
= (const context
*)av
;
277 PUT_32BIT_LSB_FIRST(outbuf
, a
->link
->topicoffset
);
281 static int ttlleaf(const void *av
, unsigned char *outbuf
)
283 const context
*a
= (const context
*)av
;
285 PUT_32BIT_LSB_FIRST(outbuf
, a
->link
->topicoffset
);
286 slen
= 1+strlen(a
->title
);
287 memcpy(outbuf
+4, a
->title
, slen
);
291 /* The |KWBTREE internal file maps index strings to TOPICOFFSETs. */
293 static int idxcmp(void *av
, void *bv
)
295 const struct indexrec
*a
= (const struct indexrec
*)av
;
296 const struct indexrec
*b
= (const struct indexrec
*)bv
;
298 if ( (cmp
= strcmp(a
->term
, b
->term
)) != 0)
300 /* Now sort on the index field of the topics. */
301 if (a
->topic
->index
< b
->topic
->index
)
303 if (a
->topic
->index
> b
->topic
->index
)
308 static int idxindex(const void *av
, unsigned char *outbuf
)
310 const struct indexrec
*a
= (const struct indexrec
*)av
;
311 int len
= 1+strlen(a
->term
);
312 memcpy(outbuf
, a
->term
, len
);
316 static int idxleaf(const void *av
, unsigned char *outbuf
)
318 const struct indexrec
*a
= (const struct indexrec
*)av
;
319 int len
= 1+strlen(a
->term
);
320 memcpy(outbuf
, a
->term
, len
);
321 PUT_16BIT_LSB_FIRST(outbuf
+len
, a
->count
);
322 PUT_32BIT_LSB_FIRST(outbuf
+len
+2, a
->offset
);
327 * The internal `tabstops' B-tree stores pointers-to-int. Sorting
328 * is by the low 16 bits of the number (above that is flags).
331 static int tabcmp(void *av
, void *bv
)
333 const int *a
= (const int *)av
;
334 const int *b
= (const int *)bv
;
335 if ((*a
& 0xFFFF) < (*b
& 0xFFFF))
337 if ((*a
& 0xFFFF) > (*b
& 0xFFFF))
342 /* The internal `fontnames' B-tree stores strings. */
343 static int fontcmp(void *av
, void *bv
)
345 const char *a
= (const char *)av
;
346 const char *b
= (const char *)bv
;
350 /* ----------------------------------------------------------------------
351 * Manage help contexts and topics.
355 * This is the code to compute the hash of a context name. Copied
356 * straight from Winterhoff's documentation.
358 static unsigned long context_hash(char *context
)
360 signed char bytemapping
[256] =
361 "\x00\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF"
362 "\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF"
363 "\xF0\x0B\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\x0C\xFF"
364 "\x0A\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"
365 "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
366 "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2A\x0B\x0C\x0D\x0E\x0D"
367 "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
368 "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2A\x2B\x2C\x2D\x2E\x2F"
369 "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5A\x5B\x5C\x5D\x5E\x5F"
370 "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6A\x6B\x6C\x6D\x6E\x6F"
371 "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7A\x7B\x7C\x7D\x7E\x7F"
372 "\x80\x81\x82\x83\x0B\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F"
373 "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F"
374 "\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF"
375 "\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF"
376 "\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF";
379 /* Sanity check the size of unsigned long */
380 enum { assertion
= 1 /
381 (((unsigned long)0xFFFFFFFF) + 2 == (unsigned long)1) };
384 * The hash algorithm starts the hash at 0 and updates it with
385 * each character. Therefore, logically, the hash of an empty
386 * string should be 0 (it starts at 0 and is never updated);
387 * but Winterhoff says it is in fact 1. Shouldn't matter, since
388 * I never plan to use empty context names, but I'll stick the
389 * special case in here anyway.
395 * Now compute the hash in the normal way.
399 hash
= hash
* 43 + bytemapping
[(unsigned char)*context
];
405 WHLP_TOPIC
whlp_register_topic(WHLP h
, char *context_name
, char **clash
)
407 context
*ctx
= snew(context
);
411 * Index contexts in order of creation, just so there's some
412 * sort of non-arbitrary ordering in the index B-tree. Call me
413 * fussy, but I don't like indexing on pointer values because I
414 * prefer the code to be deterministic when run under different
417 ctx
->index
= h
->ncontexts
++;
418 ctx
->browse_prev
= ctx
->browse_next
= NULL
;
422 * We have a context name, which means we can put this
423 * context straight into the `contexts' tree.
425 ctx
->name
= dupstr(context_name
);
426 ctx
->hash
= context_hash(context_name
);
427 otherctx
= add234(h
->contexts
, ctx
);
428 if (otherctx
!= ctx
) {
430 * Hash clash. Destroy the new context and return NULL,
431 * providing the clashing string.
435 if (clash
) *clash
= otherctx
->name
;
440 * We have no context name yet. Enter this into the
441 * pre_contexts tree of anonymous topics, which we will go
442 * through later and allocate unique context names and hash
446 addpos234(h
->pre_contexts
, ctx
, count234(h
->pre_contexts
));
451 void whlp_prepare(WHLP h
)
454 * We must go through pre_contexts and allocate a context ID to
455 * each anonymous context, making sure it doesn't clash with
456 * the existing contexts.
458 * Our own context IDs will just be of the form `t00000001',
459 * and we'll increment the number each time and skip over any
460 * IDs that clash with existing context names.
463 context
*ctx
, *otherctx
;
465 while ( (ctx
= index234(h
->pre_contexts
, 0)) != NULL
) {
466 delpos234(h
->pre_contexts
, 0);
467 ctx
->name
= snewn(20, char);
469 sprintf(ctx
->name
, "t%08d", ctx_num
++);
470 ctx
->hash
= context_hash(ctx
->name
);
471 otherctx
= add234(h
->contexts
, ctx
);
472 } while (otherctx
!= ctx
);
476 * Ensure paragraph attributes are clear for the start of text
482 char *whlp_topic_id(WHLP_TOPIC topic
)
487 void whlp_begin_topic(WHLP h
, WHLP_TOPIC topic
, char *title
, ...)
489 struct topiclink
*link
= snew(struct topiclink
);
494 link
->nexttopic
= NULL
;
496 h
->prevtopic
->nexttopic
= link
;
499 link
->nonscroll
= link
->scroll
= NULL
;
500 link
->context
= topic
;
501 link
->block_size
= 0;
503 link
->recordtype
= 2; /* topic header */
504 link
->len1
= 4*7; /* standard linkdata1 size */
505 link
->data1
= snewn(link
->len1
, unsigned char);
507 slen
= strlen(title
);
508 assert(slen
+1 <= TOPIC_BLKSIZE
);
509 memcpy(h
->linkdata2
, title
, slen
+1);
513 while ( (macro
= va_arg(ap
, char *)) != NULL
) {
514 slen
= strlen(macro
);
515 assert(len
+slen
+1 <= TOPIC_BLKSIZE
);
516 memcpy(h
->linkdata2
+len
, macro
, slen
+1);
520 len
--; /* lose the last \0 on the last macro */
523 link
->data2
= snewn(link
->len2
, unsigned char);
524 memcpy(link
->data2
, h
->linkdata2
, link
->len2
);
526 topic
->title
= dupstr(title
);
529 addpos234(h
->text
, link
, count234(h
->text
));
532 void whlp_browse_link(WHLP h
, WHLP_TOPIC before
, WHLP_TOPIC after
)
537 * See if the `before' topic is already linked to another one,
538 * and break the link to that if so. Likewise the `after'
541 if (before
->browse_next
)
542 before
->browse_next
->browse_prev
= NULL
;
543 if (after
->browse_prev
)
544 after
->browse_prev
->browse_next
= NULL
;
545 before
->browse_next
= after
;
546 after
->browse_prev
= before
;
549 /* ----------------------------------------------------------------------
550 * Manage the actual generation of paragraph and text records.
553 static void whlp_linkdata(WHLP h
, int which
, int c
)
555 int *len
= (which
== 1 ?
&h
->link
->len1
: &h
->link
->len2
);
556 char *data
= (which
== 1 ? h
->linkdata1
: h
->linkdata2
);
557 assert(*len
< TOPIC_BLKSIZE
);
561 static void whlp_linkdata_short(WHLP h
, int which
, int data
)
563 whlp_linkdata(h
, which
, data
& 0xFF);
564 whlp_linkdata(h
, which
, (data
>> 8) & 0xFF);
567 static void whlp_linkdata_long(WHLP h
, int which
, int data
)
569 whlp_linkdata(h
, which
, data
& 0xFF);
570 whlp_linkdata(h
, which
, (data
>> 8) & 0xFF);
571 whlp_linkdata(h
, which
, (data
>> 16) & 0xFF);
572 whlp_linkdata(h
, which
, (data
>> 24) & 0xFF);
575 static void whlp_linkdata_cushort(WHLP h
, int which
, int data
)
578 whlp_linkdata(h
, which
, data
*2);
580 whlp_linkdata(h
, which
, 1 + (data
%128 * 2));
581 whlp_linkdata(h
, which
, data
/128);
585 static void whlp_linkdata_csshort(WHLP h
, int which
, int data
)
587 if (data
>= -0x40 && data
<= 0x3F)
588 whlp_linkdata_cushort(h
, which
, data
+64);
590 whlp_linkdata_cushort(h
, which
, data
+16384);
593 static void whlp_linkdata_culong(WHLP h
, int which
, int data
)
595 if (data
<= 0x7FFF) {
596 whlp_linkdata_short(h
, which
, data
*2);
598 whlp_linkdata_short(h
, which
, 1 + (data
%32768 * 2));
599 whlp_linkdata_short(h
, which
, data
/32768);
603 static void whlp_linkdata_cslong(WHLP h
, int which
, int data
)
605 if (data
>= -0x4000 && data
<= 0x3FFF)
606 whlp_linkdata_culong(h
, which
, data
+16384);
608 whlp_linkdata_culong(h
, which
, data
+67108864);
611 static void whlp_para_reset(WHLP h
)
617 while ( (p
= index234(h
->tabstops
, 0)) != NULL
) {
618 delpos234(h
->tabstops
, 0);
623 void whlp_para_attr(WHLP h
, int attr_id
, int attr_param
)
625 if (attr_id
>= WHLP_PARA_SPACEABOVE
&&
626 attr_id
<= WHLP_PARA_FIRSTLINEINDENT
) {
627 h
->para_flags
|= 1 << attr_id
;
628 h
->para_attrs
[attr_id
] = attr_param
;
629 } else if (attr_id
== WHLP_PARA_ALIGNMENT
) {
630 h
->para_flags
&= ~0xC00;
631 if (attr_param
== WHLP_ALIGN_RIGHT
)
632 h
->para_flags
|= 0x400;
633 else if (attr_param
== WHLP_ALIGN_CENTRE
)
634 h
->para_flags
|= 0x800;
638 void whlp_set_tabstop(WHLP h
, int tabstop
, int alignment
)
642 if (alignment
== WHLP_ALIGN_CENTRE
)
644 if (alignment
== WHLP_ALIGN_RIGHT
)
649 add234(h
->tabstops
, p
);
650 h
->para_flags
|= 0x0200;
653 void whlp_begin_para(WHLP h
, int para_type
)
655 struct topiclink
*link
= snew(struct topiclink
);
659 * Clear these to NULL out of paranoia, although in records
660 * that aren't type 2 they should never actually be needed.
662 link
->nexttopic
= NULL
;
663 link
->context
= NULL
;
664 link
->nonscroll
= link
->scroll
= NULL
;
666 link
->recordtype
= 32; /* text record */
669 link
->len1
= link
->len2
= 0;
670 link
->data1
= h
->linkdata1
;
671 link
->data2
= h
->linkdata2
;
673 if (para_type
== WHLP_PARA_NONSCROLL
&& h
->prevtopic
&&
674 !h
->prevtopic
->nonscroll
)
675 h
->prevtopic
->nonscroll
= link
;
676 if (para_type
== WHLP_PARA_SCROLL
&& h
->prevtopic
&&
677 !h
->prevtopic
->scroll
)
678 h
->prevtopic
->scroll
= link
;
681 * Now we're ready to start accumulating stuff in linkdata1 and
682 * linkdata2. Next we build up the paragraph info. Note that
683 * the TopicSize (cslong: size of LinkData1 minus the topicsize
684 * and topiclength fields) and TopicLength (cushort: size of
685 * LinkData2) fields are missing; we will put those on when we
688 whlp_linkdata(h
, 1, 0); /* must-be-0x00 */
689 whlp_linkdata(h
, 1, 0x80); /* must-be-0x80 */
690 whlp_linkdata_short(h
, 1, 0); /* Winterhoff says `id'; always 0 AFAICT */
691 whlp_linkdata_short(h
, 1, h
->para_flags
);
692 for (i
= WHLP_PARA_SPACEABOVE
; i
<= WHLP_PARA_FIRSTLINEINDENT
; i
++) {
693 if (h
->para_flags
& (1<<i
))
694 whlp_linkdata_csshort(h
, 1, h
->para_attrs
[i
]);
696 if (h
->para_flags
& 0x0200) {
699 * Write out tab stop data.
701 ntabs
= count234(h
->tabstops
);
702 whlp_linkdata_csshort(h
, 1, ntabs
);
703 for (i
= 0; i
< ntabs
; i
++) {
705 tabp
= index234(h
->tabstops
, i
);
709 whlp_linkdata_cushort(h
, 1, tab
& 0xFFFF);
711 whlp_linkdata_cushort(h
, 1, tab
>> 16);
716 * Fine. Now we're ready to start writing actual text and
717 * formatting commands.
721 void whlp_set_font(WHLP h
, int font_id
)
724 * Write a NUL into linkdata2 to cause the reader to flip over
725 * to linkdata1 to see the formatting command.
727 whlp_linkdata(h
, 2, 0);
729 * Now the formatting command is 0x80 followed by a short.
731 whlp_linkdata(h
, 1, 0x80);
732 whlp_linkdata_short(h
, 1, font_id
);
735 void whlp_start_hyperlink(WHLP h
, WHLP_TOPIC target
)
738 * Write a NUL into linkdata2.
740 whlp_linkdata(h
, 2, 0);
742 * Now the formatting command is 0xE3 followed by the context
745 whlp_linkdata(h
, 1, 0xE3);
746 whlp_linkdata_long(h
, 1, target
->hash
);
749 void whlp_end_hyperlink(WHLP h
)
752 * Write a NUL into linkdata2.
754 whlp_linkdata(h
, 2, 0);
756 * Now the formatting command is 0x89.
758 whlp_linkdata(h
, 1, 0x89);
761 void whlp_tab(WHLP h
)
764 * Write a NUL into linkdata2.
766 whlp_linkdata(h
, 2, 0);
768 * Now the formatting command is 0x83.
770 whlp_linkdata(h
, 1, 0x83);
773 void whlp_text(WHLP h
, char *text
)
776 whlp_linkdata(h
, 2, *text
++);
780 void whlp_end_para(WHLP h
)
785 * Round off the paragraph with 0x82 and 0xFF formatting
786 * commands. Each requires a NUL in linkdata2.
788 whlp_linkdata(h
, 2, 0);
789 whlp_linkdata(h
, 1, 0x82);
790 whlp_linkdata(h
, 2, 0);
791 whlp_linkdata(h
, 1, 0xFF);
794 * Now finish up: create the header of linkdata1 (TopicLength
795 * and TopicSize fields), allocate the real linkdata1 and
796 * linkdata2 fields, and copy them out of the buffers in h.
797 * Then insert the finished topiclink into the `text' tree, and
800 data1cut
= h
->link
->len1
;
801 whlp_linkdata_cslong(h
, 1, data1cut
);
802 whlp_linkdata_cushort(h
, 1, h
->link
->len2
);
804 h
->link
->data1
= snewn(h
->link
->len1
, unsigned char);
805 memcpy(h
->link
->data1
, h
->linkdata1
+ data1cut
, h
->link
->len1
- data1cut
);
806 memcpy(h
->link
->data1
+ h
->link
->len1
- data1cut
, h
->linkdata1
, data1cut
);
807 h
->link
->data2
= snewn(h
->link
->len2
, unsigned char);
808 memcpy(h
->link
->data2
, h
->linkdata2
, h
->link
->len2
);
810 addpos234(h
->text
, h
->link
, count234(h
->text
));
812 /* Hack: accumulate the `blocksize' parameter in the topic header. */
814 h
->prevtopic
->block_size
+= 21 + h
->link
->len1
+ h
->link
->len2
;
816 h
->link
= NULL
; /* this is now in the tree */
821 /* ----------------------------------------------------------------------
822 * Manage the layout and generation of the |TOPIC section.
825 static void whlp_topicsect_write(WHLP h
, struct file
*f
, void *data
, int len
,
828 unsigned char *p
= (unsigned char *)data
;
830 if (h
->topicblock_remaining
<= 0 ||
831 h
->topicblock_remaining
< can_break
) {
835 if (h
->topicblock_remaining
> 0)
836 whlp_file_fill(f
, h
->topicblock_remaining
);
837 whlp_file_add_long(f
, h
->lasttopiclink
);
838 h
->firsttopiclink_offset
= whlp_file_offset(f
);
839 whlp_file_add_long(f
, -1L); /* this will be filled in later */
840 whlp_file_add_long(f
, h
->lasttopicstart
);
841 h
->topicblock_remaining
= TOPIC_BLKSIZE
- 12;
844 int thislen
= (h
->topicblock_remaining
< len ?
845 h
->topicblock_remaining
: len
);
846 whlp_file_add(f
, p
, thislen
);
849 h
->topicblock_remaining
-= thislen
;
850 if (len
> 0 && h
->topicblock_remaining
<= 0) {
854 whlp_file_add_long(f
, h
->lasttopiclink
);
855 h
->firsttopiclink_offset
= whlp_file_offset(f
);
856 whlp_file_add_long(f
, -1L); /* this will be filled in later */
857 whlp_file_add_long(f
, h
->lasttopicstart
);
858 h
->topicblock_remaining
= TOPIC_BLKSIZE
- 12;
863 static void whlp_topic_layout(WHLP h
)
865 int block
, offset
, pos
;
868 struct topiclink
*link
;
872 * Create a final TOPICLINK containing no usable data.
874 link
= snew(struct topiclink
);
875 link
->nexttopic
= NULL
;
877 h
->prevtopic
->nexttopic
= link
;
879 link
->data1
= snewn(0x1c, unsigned char);
880 link
->block_size
= 0;
884 link
->nexttopic
= NULL
;
885 link
->recordtype
= 2;
886 link
->nonscroll
= link
->scroll
= NULL
;
887 link
->context
= NULL
;
888 addpos234(h
->text
, link
, count234(h
->text
));
891 * Each TOPICBLOCK has space for TOPIC_BLKSIZE-12 bytes. The
892 * size of each TOPICLINK is 21 bytes plus the combined lengths
893 * of LinkData1 and LinkData2. So we can now go through and
894 * break up the TOPICLINKs into TOPICBLOCKs, and also set up
895 * the TOPICOFFSET and TOPICPOS of each one while we do so.
901 nlinks
= count234(h
->text
);
902 for (i
= 0; i
< nlinks
; i
++) {
903 link
= index234(h
->text
, i
);
904 size
= 21 + link
->len1
+ link
->len2
;
906 * We can't split within the topicblock header or within
907 * linkdata1. So if the split would fall in that area,
908 * start a new block _now_.
910 if (TOPIC_BLKSIZE
- pos
< 21 + link
->len1
) {
915 link
->topicoffset
= block
* 0x8000 + offset
;
916 link
->topicpos
= block
* 0x4000 + pos
;
918 if (link
->recordtype
!= 2) /* TOPICOFFSET doesn't count titles */
919 offset
+= link
->len2
;
920 while (pos
> TOPIC_BLKSIZE
) {
923 pos
-= TOPIC_BLKSIZE
- 12;
928 * Now we have laid out the TOPICLINKs into blocks, and
929 * determined the final TOPICOFFSET and TOPICPOS of each one.
930 * So now we can go through and write the headers of the type-2
935 for (i
= 0; i
< nlinks
; i
++) {
936 link
= index234(h
->text
, i
);
937 if (link
->recordtype
!= 2)
940 PUT_32BIT_LSB_FIRST(link
->data1
+ 0, link
->block_size
);
941 if (link
->context
&& link
->context
->browse_prev
)
942 PUT_32BIT_LSB_FIRST(link
->data1
+ 4,
943 link
->context
->browse_prev
->link
->topicoffset
);
945 PUT_32BIT_LSB_FIRST(link
->data1
+ 4, 0xFFFFFFFFL
);
946 if (link
->context
&& link
->context
->browse_next
)
947 PUT_32BIT_LSB_FIRST(link
->data1
+ 8,
948 link
->context
->browse_next
->link
->topicoffset
);
950 PUT_32BIT_LSB_FIRST(link
->data1
+ 8, 0xFFFFFFFFL
);
951 PUT_32BIT_LSB_FIRST(link
->data1
+ 12, topicnum
);
954 PUT_32BIT_LSB_FIRST(link
->data1
+ 16, link
->nonscroll
->topicpos
);
956 PUT_32BIT_LSB_FIRST(link
->data1
+ 16, 0xFFFFFFFFL
);
958 PUT_32BIT_LSB_FIRST(link
->data1
+ 20, link
->scroll
->topicpos
);
960 PUT_32BIT_LSB_FIRST(link
->data1
+ 20, 0xFFFFFFFFL
);
962 PUT_32BIT_LSB_FIRST(link
->data1
+ 24, link
->nexttopic
->topicpos
);
964 PUT_32BIT_LSB_FIRST(link
->data1
+ 24, 0xFFFFFFFFL
);
968 * Having done all _that_, we're now finally ready to go
969 * through and create the |TOPIC section in its final form.
972 h
->lasttopiclink
= -1L;
973 h
->lasttopicstart
= 0L;
974 f
= whlp_new_file(h
, "|TOPIC");
975 h
->topicblock_remaining
= -1;
976 whlp_topicsect_write(h
, f
, NULL
, 0, 0); /* start the first block */
977 for (i
= 0; i
< nlinks
; i
++) {
978 unsigned char header
[21];
979 struct topiclink
*otherlink
;
981 link
= index234(h
->text
, i
);
984 * Create and output the TOPICLINK header.
986 PUT_32BIT_LSB_FIRST(header
+ 0, 21 + link
->len1
+ link
->len2
);
987 PUT_32BIT_LSB_FIRST(header
+ 4, link
->len2
);
989 PUT_32BIT_LSB_FIRST(header
+ 8, 0xFFFFFFFFL
);
991 otherlink
= index234(h
->text
, i
-1);
992 PUT_32BIT_LSB_FIRST(header
+ 8, otherlink
->topicpos
);
995 PUT_32BIT_LSB_FIRST(header
+ 12, 0xFFFFFFFFL
);
997 otherlink
= index234(h
->text
, i
+1);
998 PUT_32BIT_LSB_FIRST(header
+ 12, otherlink
->topicpos
);
1000 PUT_32BIT_LSB_FIRST(header
+ 16, 21 + link
->len1
);
1001 header
[20] = link
->recordtype
;
1002 whlp_topicsect_write(h
, f
, header
, 21, 21 + link
->len1
);
1005 * Fill in the `first topiclink' pointer in the block
1006 * header if appropriate. (We do this _after_ outputting
1007 * the header because then we can be sure we'll be in the
1008 * same block as we think we are.)
1010 if (h
->firsttopiclink_offset
> 0) {
1011 whlp_file_seek(f
, h
->firsttopiclink_offset
, 0);
1012 whlp_file_add_long(f
, link
->topicpos
);
1013 h
->firsttopiclink_offset
= 0;
1014 whlp_file_seek(f
, 0, 2);
1018 * Update the `last topiclink', and possibly `last
1019 * topicstart', pointers.
1021 h
->lasttopiclink
= link
->topicpos
;
1022 if (link
->recordtype
== 2)
1023 h
->lasttopicstart
= link
->topicpos
;
1027 * Output LinkData1 and LinkData2.
1029 whlp_topicsect_write(h
, f
, link
->data1
, link
->len1
, link
->len1
);
1030 whlp_topicsect_write(h
, f
, link
->data2
, link
->len2
, 0);
1033 * Output the block header.
1036 link
= index234(h
->text
, i
);
1041 /* ----------------------------------------------------------------------
1042 * Manage the index sections (|KWDATA, |KWMAP, |KWBTREE).
1045 void whlp_index_term(WHLP h
, char *index
, WHLP_TOPIC topic
)
1047 struct indexrec
*idx
= snew(struct indexrec
);
1049 idx
->term
= dupstr(index
);
1052 * If this reference is already in the tree, just silently drop
1055 if (add234(h
->index
, idx
) != idx
) {
1061 static void whlp_build_kwdata(WHLP h
)
1065 struct indexrec
*first
, *next
;
1067 f
= whlp_new_file(h
, "|KWDATA");
1070 * Go through the index B-tree, condensing all sequences of
1071 * records with the same term into a single one with a valid
1072 * (count,offset) pair, and building up the KWDATA section.
1075 while ( (first
= index234(h
->index
, i
)) != NULL
) {
1077 first
->offset
= whlp_file_offset(f
);
1078 whlp_file_add_long(f
, first
->topic
->link
->topicoffset
);
1080 while ( (next
= index234(h
->index
, i
)) != NULL
&&
1081 !strcmp(first
->term
, next
->term
)) {
1083 * The next index record has the same term. Fold it
1084 * into this one and remove from the tree.
1086 whlp_file_add_long(f
, next
->topic
->link
->topicoffset
);
1088 delpos234(h
->index
, i
);
1095 * Now we should have `index' in a form that's ready to
1096 * construct |KWBTREE. So we can return.
1100 /* ----------------------------------------------------------------------
1101 * Standard chunks of data for the |SYSTEM and |FONT sections.
1104 static void whlp_system_record(struct file
*f
, int id
,
1105 const void *data
, int length
)
1107 whlp_file_add_short(f
, id
);
1108 whlp_file_add_short(f
, length
);
1109 whlp_file_add(f
, data
, length
);
1112 static void whlp_standard_systemsection(struct file
*f
)
1114 const char lcid
[] = { 0, 0, 0, 0, 0, 0, 0, 0, 9, 4 };
1115 const char charset
[] = { 0, 0, 0, 2, 0 };
1117 whlp_file_add_short(f
, 0x36C); /* magic number */
1118 whlp_file_add_short(f
, 33); /* minor version: HCW 4.00 Win95+ */
1119 whlp_file_add_short(f
, 1); /* major version */
1120 whlp_file_add_long(f
, time(NULL
)); /* generation date */
1121 whlp_file_add_short(f
, 0); /* flags=0 means no compression */
1124 * Add some magic locale identifier information. (We ought to
1125 * find out something about what all this means; see the TODO
1126 * list at the top of the file.)
1128 whlp_system_record(f
, 9, lcid
, sizeof(lcid
));
1129 whlp_system_record(f
, 11, charset
, sizeof(charset
));
1132 void whlp_title(WHLP h
, char *title
)
1134 whlp_system_record(h
->systemfile
, 1, title
, 1+strlen(title
));
1137 void whlp_copyright(WHLP h
, char *copyright
)
1139 whlp_system_record(h
->systemfile
, 2, copyright
, 1+strlen(copyright
));
1142 void whlp_start_macro(WHLP h
, char *macro
)
1144 whlp_system_record(h
->systemfile
, 4, macro
, 1+strlen(macro
));
1147 void whlp_primary_topic(WHLP h
, WHLP_TOPIC t
)
1152 static void whlp_do_primary_topic(WHLP h
)
1154 unsigned char firsttopic
[4];
1155 PUT_32BIT_LSB_FIRST(firsttopic
, h
->ptopic
->link
->topicoffset
);
1156 whlp_system_record(h
->systemfile
, 3, firsttopic
, sizeof(firsttopic
));
1159 int whlp_create_font(WHLP h
, char *font
, int family
, int halfpoints
,
1160 int rendition
, int r
, int g
, int b
)
1162 char *fontname
= dupstr(font
);
1163 struct fontdesc
*fontdesc
;
1166 font
= add234(h
->fontnames
, fontname
);
1167 if (font
!= fontname
) {
1168 /* The font name was already present. Free the new copy. */
1172 fontdesc
= snew(struct fontdesc
);
1173 fontdesc
->font
= font
;
1174 fontdesc
->family
= family
;
1175 fontdesc
->halfpoints
= halfpoints
;
1176 fontdesc
->rendition
= rendition
;
1181 index
= count234(h
->fontdescs
);
1182 addpos234(h
->fontdescs
, fontdesc
, index
);
1186 static void whlp_make_fontsection(WHLP h
, struct file
*f
)
1190 struct fontdesc
*fontdesc
;
1193 * Header block: number of font names, number of font
1194 * descriptors, offset to font names, and offset to font
1197 whlp_file_add_short(f
, count234(h
->fontnames
));
1198 whlp_file_add_short(f
, count234(h
->fontdescs
));
1199 whlp_file_add_short(f
, 8);
1200 whlp_file_add_short(f
, 8 + 32 * count234(h
->fontnames
));
1205 for (i
= 0; (fontname
= index234(h
->fontnames
, i
)) != NULL
; i
++) {
1207 memset(data
, i
, sizeof(data
));
1208 strncpy(data
, fontname
, sizeof(data
));
1209 whlp_file_add(f
, data
, sizeof(data
));
1215 for (i
= 0; (fontdesc
= index234(h
->fontdescs
, i
)) != NULL
; i
++) {
1219 ret
= findpos234(h
->fontnames
, fontdesc
->font
, NULL
, &fontpos
);
1220 assert(ret
!= NULL
);
1222 whlp_file_add_char(f
, fontdesc
->rendition
);
1223 whlp_file_add_char(f
, fontdesc
->halfpoints
);
1224 whlp_file_add_char(f
, fontdesc
->family
);
1225 whlp_file_add_short(f
, fontpos
);
1226 /* Foreground RGB */
1227 whlp_file_add_char(f
, fontdesc
->r
);
1228 whlp_file_add_char(f
, fontdesc
->g
);
1229 whlp_file_add_char(f
, fontdesc
->b
);
1230 /* Background RGB is apparently unused and always set to zero */
1231 whlp_file_add_char(f
, 0);
1232 whlp_file_add_char(f
, 0);
1233 whlp_file_add_char(f
, 0);
1238 /* ----------------------------------------------------------------------
1239 * Routines to manage a B-tree type file.
1242 static void whlp_make_btree(struct file
*f
, int flags
, int pagesize
,
1243 char *dataformat
, tree234
*tree
,
1245 bt_index_fn indexfn
, bt_leaf_fn leaffn
)
1247 void **page_elements
= NULL
;
1248 int npages
= 0, pagessize
= 0;
1249 int npages_this_level
, nentries
, nlevels
;
1250 int total_leaf_entries
;
1251 char btdata
[MAX_PAGE_SIZE
];
1253 int page_start
, fixups_offset
, unused_bytes
;
1257 assert(pagesize
<= MAX_PAGE_SIZE
);
1260 * Start with the B-tree header. We'll have to come back and
1261 * fill in a few bits later.
1263 whlp_file_add_short(f
, 0x293B); /* magic number */
1264 whlp_file_add_short(f
, flags
);
1265 whlp_file_add_short(f
, pagesize
);
1268 memset(data
, 0, sizeof(data
));
1269 assert(strlen(dataformat
) <= sizeof(data
));
1270 memcpy(data
, dataformat
, strlen(dataformat
));
1271 whlp_file_add(f
, data
, sizeof(data
));
1273 whlp_file_add_short(f
, 0); /* must-be-zero */
1274 fixups_offset
= whlp_file_offset(f
);
1275 whlp_file_add_short(f
, 0); /* page splits; fix up later */
1276 whlp_file_add_short(f
, 0); /* root page index; fix up later */
1277 whlp_file_add_short(f
, -1); /* must-be-minus-one */
1278 whlp_file_add_short(f
, 0); /* total number of pages; fix later */
1279 whlp_file_add_short(f
, 0); /* number of levels; fix later */
1280 whlp_file_add_long(f
, count234(tree
));/* total B-tree entries */
1283 * If we have a map section, leave space at the start for its
1287 whlp_file_add_short(map
, 0);
1291 * Now create the leaf pages.
1295 npages_this_level
= 0;
1296 total_leaf_entries
= 0;
1298 element
= index234(tree
, index
);
1301 * Make a new leaf page.
1303 npages_this_level
++;
1304 if (npages
>= pagessize
) {
1305 pagessize
= npages
+ 32;
1306 page_elements
= sresize(page_elements
, pagessize
, void *);
1308 page_elements
[npages
++] = element
;
1311 * Leave space in the leaf page for the header. We'll
1312 * come back and add it later.
1314 page_start
= whlp_file_offset(f
);
1315 whlp_file_add(f
, "12345678", 8);
1316 unused_bytes
= pagesize
- 8;
1320 * Now add leaf entries until we run out of room, or out of
1324 btlen
= leaffn(element
, btdata
);
1325 if (btlen
> unused_bytes
)
1327 whlp_file_add(f
, btdata
, btlen
);
1328 unused_bytes
-= btlen
;
1331 element
= index234(tree
, index
);
1335 * Now add the unused bytes, and then go back and put
1338 whlp_file_fill(f
, unused_bytes
);
1339 whlp_file_seek(f
, page_start
, 0);
1340 whlp_file_add_short(f
, unused_bytes
);
1341 whlp_file_add_short(f
, nentries
);
1342 /* Previous-page indicator will automatically go to -1 when
1344 whlp_file_add_short(f
, npages
-2);
1345 /* Next-page indicator must be -1 if we're at the end. */
1347 whlp_file_add_short(f
, -1);
1349 whlp_file_add_short(f
, npages
);
1350 whlp_file_seek(f
, 0, 2);
1353 * If we have a map section, add a map entry.
1356 whlp_file_add_long(map
, total_leaf_entries
);
1357 whlp_file_add_short(map
, npages_this_level
-1);
1359 total_leaf_entries
+= nentries
;
1363 * If we have a map section, write the total number of map
1367 whlp_file_seek(map
, 0, 0);
1368 whlp_file_add_short(map
, npages_this_level
);
1369 whlp_file_seek(map
, 0, 2);
1373 * Now create further levels until we're down to one page.
1376 while (npages_this_level
> 1) {
1377 int first
= npages
- npages_this_level
;
1378 int last
= npages
- 1;
1382 npages_this_level
= 0;
1385 while (current
<= last
) {
1387 * Make a new index page.
1389 npages_this_level
++;
1390 if (npages
>= pagessize
) {
1391 pagessize
= npages
+ 32;
1392 page_elements
= sresize(page_elements
, pagessize
, void *);
1394 page_elements
[npages
++] = page_elements
[current
];
1397 * Leave space for some of the header, but we can put
1398 * in the PreviousPage link already.
1400 page_start
= whlp_file_offset(f
);
1401 whlp_file_add(f
, "1234", 4);
1402 whlp_file_add_short(f
, current
);
1403 unused_bytes
= pagesize
- 6;
1406 * Now add index entries until we run out of either
1411 while (current
<= last
) {
1412 btlen
= indexfn(page_elements
[current
], btdata
);
1413 if (btlen
+ 2 > unused_bytes
)
1415 whlp_file_add(f
, btdata
, btlen
);
1416 whlp_file_add_short(f
, current
);
1417 unused_bytes
-= btlen
+2;
1423 * Now add the unused bytes, and then go back and put
1426 whlp_file_fill(f
, unused_bytes
);
1427 whlp_file_seek(f
, page_start
, 0);
1428 whlp_file_add_short(f
, unused_bytes
);
1429 whlp_file_add_short(f
, nentries
);
1430 whlp_file_seek(f
, 0, 2);
1435 * Now we have all our pages ready, and we know where our root
1436 * page is. Fix up the main B-tree header.
1438 whlp_file_seek(f
, fixups_offset
, 0);
1439 /* Creation of every page requires a split unless it's the first in
1440 * a new level. Hence, page splits equals pages minus levels. */
1441 whlp_file_add_short(f
, npages
- nlevels
);
1442 whlp_file_add_short(f
, npages
-1); /* root page index */
1443 whlp_file_add_short(f
, -1); /* must-be-minus-one */
1444 whlp_file_add_short(f
, npages
); /* total number of pages */
1445 whlp_file_add_short(f
, nlevels
); /* number of levels */
1447 /* Just for tidiness, seek to the end of the file :-) */
1448 whlp_file_seek(f
, 0, 2);
1451 sfree(page_elements
);
1455 /* ----------------------------------------------------------------------
1456 * Routines to manage the `internal file' structure.
1459 static struct file
*whlp_new_file(WHLP h
, char *name
)
1462 f
= snew(struct file
);
1464 f
->pos
= f
->len
= f
->size
= 0;
1466 f
->name
= dupstr(name
);
1467 add234(h
->files
, f
);
1474 static void whlp_free_file(struct file
*f
)
1477 sfree(f
->name
); /* may be NULL */
1481 static void whlp_file_add(struct file
*f
, const void *data
, int len
)
1483 if (f
->pos
+ len
> f
->size
) {
1484 f
->size
= f
->pos
+ len
+ 1024;
1485 f
->data
= sresize(f
->data
, f
->size
, unsigned char);
1487 memcpy(f
->data
+ f
->pos
, data
, len
);
1489 if (f
->len
< f
->pos
)
1493 static void whlp_file_add_char(struct file
*f
, int data
)
1497 whlp_file_add(f
, &s
, 1);
1500 static void whlp_file_add_short(struct file
*f
, int data
)
1503 PUT_16BIT_LSB_FIRST(s
, data
);
1504 whlp_file_add(f
, s
, 2);
1507 static void whlp_file_add_long(struct file
*f
, int data
)
1510 PUT_32BIT_LSB_FIRST(s
, data
);
1511 whlp_file_add(f
, s
, 4);
1514 static void whlp_file_fill(struct file
*f
, int len
)
1516 if (f
->pos
+ len
> f
->size
) {
1517 f
->size
= f
->pos
+ len
+ 1024;
1518 f
->data
= sresize(f
->data
, f
->size
, unsigned char);
1520 memset(f
->data
+ f
->pos
, 0, len
);
1522 if (f
->len
< f
->pos
)
1526 static void whlp_file_seek(struct file
*f
, int pos
, int whence
)
1528 f
->pos
= (whence
== 0 ?
0 : whence
== 1 ? f
->pos
: f
->len
) + pos
;
1531 static int whlp_file_offset(struct file
*f
)
1536 /* ----------------------------------------------------------------------
1537 * Open and close routines; final wrapper around everything.
1545 ret
= snew(struct WHLP_tag
);
1550 ret
->files
= newtree234(filecmp
);
1551 ret
->pre_contexts
= newtree234(NULL
);
1552 ret
->contexts
= newtree234(ctxcmp
);
1553 ret
->titles
= newtree234(ttlcmp
);
1554 ret
->text
= newtree234(NULL
);
1555 ret
->index
= newtree234(idxcmp
);
1556 ret
->tabstops
= newtree234(tabcmp
);
1557 ret
->fontnames
= newtree234(fontcmp
);
1558 ret
->fontdescs
= newtree234(NULL
);
1561 * Some standard files.
1563 f
= whlp_new_file(ret
, "|CTXOMAP");
1564 whlp_file_add_short(f
, 0); /* dummy section */
1565 f
= whlp_new_file(ret
, "|SYSTEM");
1566 whlp_standard_systemsection(f
);
1567 ret
->systemfile
= f
;
1572 ret
->prevtopic
= NULL
;
1579 void whlp_close(WHLP h
, char *filename
)
1582 int filecount
, offset
, index
, filelen
;
1583 struct file
*file
, *map
, *md
;
1588 * Lay out the topic section.
1590 whlp_topic_layout(h
);
1593 * Finish off the system section.
1595 whlp_do_primary_topic(h
);
1598 * Assemble the font section.
1600 file
= whlp_new_file(h
, "|FONT");
1601 whlp_make_fontsection(h
, file
);
1606 has_index
= (count234(h
->index
) != 0);
1608 whlp_build_kwdata(h
);
1611 * Set up the `titles' B-tree for the |TTLBTREE section.
1613 for (index
= 0; (ctx
= index234(h
->contexts
, index
)) != NULL
; index
++)
1614 add234(h
->titles
, ctx
);
1617 * Construct the various B-trees.
1619 file
= whlp_new_file(h
, "|CONTEXT");
1620 whlp_make_btree(file
, 0x0002, 0x0800, "L4",
1621 h
->contexts
, NULL
, ctxindex
, ctxleaf
);
1623 file
= whlp_new_file(h
, "|TTLBTREE");
1624 whlp_make_btree(file
, 0x0002, 0x0800, "Lz",
1625 h
->titles
, NULL
, ttlindex
, ttlleaf
);
1628 file
= whlp_new_file(h
, "|KWBTREE");
1629 map
= whlp_new_file(h
, "|KWMAP");
1630 whlp_make_btree(file
, 0x0002, 0x0800, "F24",
1631 h
->index
, map
, idxindex
, idxleaf
);
1635 * Open the output file.
1637 fp
= fopen(filename
, "wb");
1644 * Work out all the file offsets.
1646 filecount
= count234(h
->files
);
1647 offset
= 16; /* just after header */
1648 for (index
= 0; index
< filecount
; index
++) {
1649 file
= index234(h
->files
, index
);
1650 file
->fileoffset
= offset
;
1651 offset
+= 9 + file
->len
; /* 9 is size of file header */
1653 /* Now `offset' holds what will be the offset of the master directory. */
1655 md
= whlp_new_file(h
, NULL
); /* master directory file */
1656 whlp_make_btree(md
, 0x0402, 0x0400, "z4",
1657 h
->files
, NULL
, fileindex
, fileleaf
);
1659 filelen
= offset
+ 9 + md
->len
;
1662 * Write out the file header.
1665 unsigned char header
[16];
1666 PUT_32BIT_LSB_FIRST(header
+0, 0x00035F3FL
); /* magic */
1667 PUT_32BIT_LSB_FIRST(header
+4, offset
); /* offset to directory */
1668 PUT_32BIT_LSB_FIRST(header
+8, 0xFFFFFFFFL
); /* first free block */
1669 PUT_32BIT_LSB_FIRST(header
+12, filelen
); /* total file length */
1670 fwrite(header
, 1, 16, fp
);
1674 * Now write out each file.
1676 for (index
= 0; index
<= filecount
; index
++) {
1678 unsigned char header
[9];
1680 if (index
== filecount
)
1681 file
= md
; /* master directory comes last */
1683 file
= index234(h
->files
, index
);
1686 reserved
= used
+ 9;
1689 PUT_32BIT_LSB_FIRST(header
+0, reserved
);
1690 PUT_32BIT_LSB_FIRST(header
+4, used
);
1691 header
[8] = 0; /* flags */
1692 fwrite(header
, 1, 9, fp
);
1695 fwrite(file
->data
, 1, file
->len
, fp
);
1702 whlp_abandon(h
); /* now free everything */
1705 void whlp_abandon(WHLP h
)
1708 struct indexrec
*idx
;
1709 struct topiclink
*link
;
1710 struct fontdesc
*fontdesc
;
1714 /* Get rid of any lingering tab stops. */
1717 /* Delete the (now empty) tabstops tree. */
1718 freetree234(h
->tabstops
);
1720 /* Delete the index tree and all its entries. */
1721 while ( (idx
= index234(h
->index
, 0)) != NULL
) {
1722 delpos234(h
->index
, 0);
1726 freetree234(h
->index
);
1728 /* Delete the text tree and all its topiclinks. */
1729 while ( (link
= index234(h
->text
, 0)) != NULL
) {
1730 delpos234(h
->text
, 0);
1731 sfree(link
->data1
); /* may be NULL */
1732 sfree(link
->data2
); /* may be NULL */
1735 freetree234(h
->text
);
1737 /* Delete the fontdescs tree and all its entries. */
1738 while ( (fontdesc
= index234(h
->fontdescs
, 0)) != NULL
) {
1739 delpos234(h
->fontdescs
, 0);
1742 freetree234(h
->fontdescs
);
1744 /* Delete the fontnames tree and all its entries. */
1745 while ( (fontname
= index234(h
->fontnames
, 0)) != NULL
) {
1746 delpos234(h
->fontnames
, 0);
1749 freetree234(h
->fontnames
);
1751 /* There might be an unclosed paragraph in h->link. */
1753 sfree(h
->link
); /* if so it won't have data1 or data2 */
1756 * `titles' contains copies of the `contexts' entries, so we
1757 * don't need to free them here.
1759 freetree234(h
->titles
);
1762 * `contexts' and `pre_contexts' _both_ contain contexts that
1763 * need freeing. (pre_contexts shouldn't contain any, unless
1764 * the help generation was abandoned half-way through.)
1766 while ( (ctx
= index234(h
->pre_contexts
, 0)) != NULL
) {
1767 delpos234(h
->index
, 0);
1772 freetree234(h
->pre_contexts
);
1773 while ( (ctx
= index234(h
->contexts
, 0)) != NULL
) {
1774 delpos234(h
->contexts
, 0);
1779 freetree234(h
->contexts
);
1782 * Free all the internal files.
1784 while ( (f
= index234(h
->files
, 0)) != NULL
) {
1785 delpos234(h
->files
, 0);
1788 freetree234(h
->files
);
1798 WHLP_TOPIC t1
, t2
, t3
;
1804 whlp_title(h
, "Test Help File");
1805 whlp_copyright(h
, "This manual is copyright \251 2001 Simon Tatham."
1806 " All rights reversed.");
1807 whlp_start_macro(h
, "CB(\"btn_about\",\"&About\",\"About()\")");
1808 whlp_start_macro(h
, "CB(\"btn_up\",\"&Up\",\"Contents()\")");
1809 whlp_start_macro(h
, "BrowseButtons()");
1811 whlp_create_font(h
, "Arial", WHLP_FONTFAM_SANS
, 30,
1813 whlp_create_font(h
, "Times New Roman", WHLP_FONTFAM_SERIF
, 24,
1814 WHLP_FONT_STRIKEOUT
, 0, 0, 0);
1815 whlp_create_font(h
, "Times New Roman", WHLP_FONTFAM_SERIF
, 24,
1816 WHLP_FONT_ITALIC
, 0, 0, 0);
1817 whlp_create_font(h
, "Courier New", WHLP_FONTFAM_FIXED
, 24,
1820 t1
= whlp_register_topic(h
, "foobar", &e
);
1822 t2
= whlp_register_topic(h
, "M359HPEHGW", &e
);
1824 t3
= whlp_register_topic(h
, "Y5VQEXZQVJ", &e
);
1825 assert(t3
== NULL
&& !strcmp(e
, "M359HPEHGW"));
1826 t3
= whlp_register_topic(h
, NULL
, NULL
);
1829 whlp_primary_topic(h
, t2
);
1833 whlp_begin_topic(h
, t1
, "First Topic", "DB(\"btn_up\")", NULL
);
1835 whlp_begin_para(h
, WHLP_PARA_NONSCROLL
);
1836 whlp_set_font(h
, 0);
1837 whlp_text(h
, "Foobar");
1840 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1841 whlp_set_font(h
, 1);
1842 whlp_text(h
, "This is a silly paragraph with ");
1843 whlp_set_font(h
, 3);
1844 whlp_text(h
, "code");
1845 whlp_set_font(h
, 1);
1846 whlp_text(h
, " in it.");
1849 whlp_para_attr(h
, WHLP_PARA_SPACEABOVE
, 12);
1850 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1851 whlp_set_font(h
, 1);
1852 whlp_text(h
, "This second, equally silly, paragraph has ");
1853 whlp_set_font(h
, 2);
1854 whlp_text(h
, "emphasis");
1855 whlp_set_font(h
, 1);
1856 whlp_text(h
, " just to prove we can do it.");
1859 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1860 whlp_set_font(h
, 1);
1861 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1862 " to make some wrapping happen, and also to make the topicblock"
1863 " go across its boundaries. This is going to take a fair amount"
1864 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1867 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1868 whlp_set_font(h
, 1);
1869 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1870 " to make some wrapping happen, and also to make the topicblock"
1871 " go across its boundaries. This is going to take a fair amount"
1872 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1875 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1876 whlp_set_font(h
, 1);
1877 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1878 " to make some wrapping happen, and also to make the topicblock"
1879 " go across its boundaries. This is going to take a fair amount"
1880 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1883 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1884 whlp_set_font(h
, 1);
1885 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1886 " to make some wrapping happen, and also to make the topicblock"
1887 " go across its boundaries. This is going to take a fair amount"
1888 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1891 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1892 whlp_set_font(h
, 1);
1893 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1894 " to make some wrapping happen, and also to make the topicblock"
1895 " go across its boundaries. This is going to take a fair amount"
1896 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1899 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1900 whlp_set_font(h
, 1);
1901 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1902 " to make some wrapping happen, and also to make the topicblock"
1903 " go across its boundaries. This is going to take a fair amount"
1904 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1907 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1908 whlp_set_font(h
, 1);
1909 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1910 " to make some wrapping happen, and also to make the topicblock"
1911 " go across its boundaries. This is going to take a fair amount"
1912 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1915 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1916 whlp_set_font(h
, 1);
1917 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1918 " to make some wrapping happen, and also to make the topicblock"
1919 " go across its boundaries. This is going to take a fair amount"
1920 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1923 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1924 whlp_set_font(h
, 1);
1925 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1926 " to make some wrapping happen, and also to make the topicblock"
1927 " go across its boundaries. This is going to take a fair amount"
1928 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1931 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1932 whlp_set_font(h
, 1);
1933 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1934 " to make some wrapping happen, and also to make the topicblock"
1935 " go across its boundaries. This is going to take a fair amount"
1936 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1939 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1940 whlp_set_font(h
, 1);
1941 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1942 " to make some wrapping happen, and also to make the topicblock"
1943 " go across its boundaries. This is going to take a fair amount"
1944 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1947 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1948 whlp_set_font(h
, 1);
1949 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1950 " to make some wrapping happen, and also to make the topicblock"
1951 " go across its boundaries. This is going to take a fair amount"
1952 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1955 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1956 whlp_set_font(h
, 1);
1957 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1958 " to make some wrapping happen, and also to make the topicblock"
1959 " go across its boundaries. This is going to take a fair amount"
1960 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1963 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1964 whlp_set_font(h
, 1);
1965 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1966 " to make some wrapping happen, and also to make the topicblock"
1967 " go across its boundaries. This is going to take a fair amount"
1968 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1971 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1972 whlp_set_font(h
, 1);
1973 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1974 " to make some wrapping happen, and also to make the topicblock"
1975 " go across its boundaries. This is going to take a fair amount"
1976 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1979 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1980 whlp_set_font(h
, 1);
1981 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1982 " to make some wrapping happen, and also to make the topicblock"
1983 " go across its boundaries. This is going to take a fair amount"
1984 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1987 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1988 whlp_set_font(h
, 1);
1989 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1990 " to make some wrapping happen, and also to make the topicblock"
1991 " go across its boundaries. This is going to take a fair amount"
1992 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1995 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1996 whlp_set_font(h
, 1);
1997 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1998 " to make some wrapping happen, and also to make the topicblock"
1999 " go across its boundaries. This is going to take a fair amount"
2000 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2003 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2004 whlp_set_font(h
, 1);
2005 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2006 " to make some wrapping happen, and also to make the topicblock"
2007 " go across its boundaries. This is going to take a fair amount"
2008 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2011 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2012 whlp_set_font(h
, 1);
2013 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2014 " to make some wrapping happen, and also to make the topicblock"
2015 " go across its boundaries. This is going to take a fair amount"
2016 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2019 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2020 whlp_set_font(h
, 1);
2021 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2022 " to make some wrapping happen, and also to make the topicblock"
2023 " go across its boundaries. This is going to take a fair amount"
2024 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2027 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2028 whlp_set_font(h
, 1);
2029 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2030 " to make some wrapping happen, and also to make the topicblock"
2031 " go across its boundaries. This is going to take a fair amount"
2032 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2035 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2036 whlp_set_font(h
, 1);
2037 whlp_text(h
, "Have a ");
2038 whlp_start_hyperlink(h
, t2
);
2039 whlp_text(h
, "hyperlink");
2040 whlp_end_hyperlink(h
);
2041 whlp_text(h
, " to another topic.");
2044 sprintf(mymacro
, "CBB(\"btn_up\",\"JI(`',`%s')\");EB(\"btn_up\")",
2047 whlp_begin_topic(h
, t2
, "Second Topic", mymacro
, NULL
);
2049 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2050 whlp_set_font(h
, 1);
2051 whlp_text(h
, "This topic contains no non-scrolling region. I would"
2052 " illustrate this with a ludicrously long paragraph, but that"
2053 " would get very tedious very quickly. Instead I'll just waffle"
2054 " on pointlessly for a little bit and then shut up.");
2057 whlp_set_tabstop(h
, 36, WHLP_ALIGN_LEFT
);
2058 whlp_para_attr(h
, WHLP_PARA_LEFTINDENT
, 36);
2059 whlp_para_attr(h
, WHLP_PARA_FIRSTLINEINDENT
, -36);
2060 whlp_para_attr(h
, WHLP_PARA_SPACEABOVE
, 12);
2061 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2062 whlp_set_font(h
, 1);
2063 whlp_text(h
, "\225"); /* bullet */
2065 whlp_text(h
, "This is a paragraph with a bullet. With any luck it should"
2066 " work exactly like it used to in the old NASM help file.");
2069 whlp_set_tabstop(h
, 128, WHLP_ALIGN_RIGHT
);
2070 whlp_set_tabstop(h
, 256, WHLP_ALIGN_CENTRE
);
2071 whlp_set_tabstop(h
, 384, WHLP_ALIGN_LEFT
);
2072 whlp_para_attr(h
, WHLP_PARA_SPACEABOVE
, 12);
2073 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2074 whlp_set_font(h
, 1);
2075 whlp_text(h
, "Ooh:"); whlp_tab(h
);
2076 whlp_text(h
, "Right?"); whlp_tab(h
);
2077 whlp_text(h
, "Centre?"); whlp_tab(h
);
2078 whlp_text(h
, "Left?");
2081 whlp_set_tabstop(h
, 128, WHLP_ALIGN_RIGHT
);
2082 whlp_set_tabstop(h
, 256, WHLP_ALIGN_CENTRE
);
2083 whlp_set_tabstop(h
, 384, WHLP_ALIGN_LEFT
);
2084 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2085 whlp_set_font(h
, 1);
2086 whlp_text(h
, "Aah:"); whlp_tab(h
);
2087 whlp_text(h
, "R?"); whlp_tab(h
);
2088 whlp_text(h
, "C?"); whlp_tab(h
);
2092 sprintf(mymacro
, "CBB(\"btn_up\",\"JI(`',`%s')\");EB(\"btn_up\")",
2095 whlp_begin_topic(h
, t3
, "Third Topic", mymacro
, NULL
);
2097 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2098 whlp_set_font(h
, 1);
2099 whlp_text(h
, "This third topic is almost as boring as the first. Woo!");
2105 whlp_browse_link(h
, t1
, t2
);
2106 whlp_browse_link(h
, t2
, t3
);
2111 whlp_index_term(h
, "foobarbaz", t1
);
2112 whlp_index_term(h
, "foobarbaz", t2
);
2113 whlp_index_term(h
, "foobarbaz", t3
);
2114 whlp_index_term(h
, "foobar", t1
);
2115 whlp_index_term(h
, "foobar", t2
);
2116 whlp_index_term(h
, "foobaz", t1
);
2117 whlp_index_term(h
, "foobaz", t3
);
2118 whlp_index_term(h
, "barbaz", t2
);
2119 whlp_index_term(h
, "barbaz", t3
);
2120 whlp_index_term(h
, "foo", t1
);
2121 whlp_index_term(h
, "bar", t2
);
2122 whlp_index_term(h
, "baz", t3
);
2124 whlp_close(h
, "test.hlp");