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";
380 * The hash algorithm starts the hash at 0 and updates it with
381 * each character. Therefore, logically, the hash of an empty
382 * string should be 0 (it starts at 0 and is never updated);
383 * but Winterhoff says it is in fact 1. Shouldn't matter, since
384 * I never plan to use empty context names, but I'll stick the
385 * special case in here anyway.
391 * Now compute the hash in the normal way.
396 * Be careful of overflowing `unsigned long', for maximum
401 * Multiply `hash' by 43.
404 unsigned long bottom
, top
;
405 bottom
= (hash
& 0xFFFFUL
) * 43;
406 top
= ((hash
>> 16) & 0xFFFFUL
) * 43;
407 top
+= (bottom
>> 16);
410 hash
= (top
<< 16) | bottom
;
414 * Add the mapping value for this byte to `hash'.
417 int val
= bytemapping
[(unsigned char)*context
];
419 if (val
> 0 && hash
> (0xFFFFFFFFUL
- val
)) {
420 hash
-= (0xFFFFFFFFUL
- val
) + 1;
421 } else if (val
< 0 && hash
< (unsigned long)-val
) {
422 hash
+= (0xFFFFFFFFUL
+ val
) + 1;
432 WHLP_TOPIC
whlp_register_topic(WHLP h
, char *context_name
, char **clash
)
434 context
*ctx
= snew(context
);
438 * Index contexts in order of creation, just so there's some
439 * sort of non-arbitrary ordering in the index B-tree. Call me
440 * fussy, but I don't like indexing on pointer values because I
441 * prefer the code to be deterministic when run under different
444 ctx
->index
= h
->ncontexts
++;
445 ctx
->browse_prev
= ctx
->browse_next
= NULL
;
449 * We have a context name, which means we can put this
450 * context straight into the `contexts' tree.
452 ctx
->name
= dupstr(context_name
);
453 ctx
->hash
= context_hash(context_name
);
454 otherctx
= add234(h
->contexts
, ctx
);
455 if (otherctx
!= ctx
) {
457 * Hash clash. Destroy the new context and return NULL,
458 * providing the clashing string.
462 if (clash
) *clash
= otherctx
->name
;
467 * We have no context name yet. Enter this into the
468 * pre_contexts tree of anonymous topics, which we will go
469 * through later and allocate unique context names and hash
473 addpos234(h
->pre_contexts
, ctx
, count234(h
->pre_contexts
));
478 void whlp_prepare(WHLP h
)
481 * We must go through pre_contexts and allocate a context ID to
482 * each anonymous context, making sure it doesn't clash with
483 * the existing contexts.
485 * Our own context IDs will just be of the form `t00000001',
486 * and we'll increment the number each time and skip over any
487 * IDs that clash with existing context names.
490 context
*ctx
, *otherctx
;
492 while ( (ctx
= index234(h
->pre_contexts
, 0)) != NULL
) {
493 delpos234(h
->pre_contexts
, 0);
494 ctx
->name
= snewn(20, char);
496 sprintf(ctx
->name
, "t%08d", ctx_num
++);
497 ctx
->hash
= context_hash(ctx
->name
);
498 otherctx
= add234(h
->contexts
, ctx
);
499 } while (otherctx
!= ctx
);
503 * Ensure paragraph attributes are clear for the start of text
509 char *whlp_topic_id(WHLP_TOPIC topic
)
514 void whlp_begin_topic(WHLP h
, WHLP_TOPIC topic
, char *title
, ...)
516 struct topiclink
*link
= snew(struct topiclink
);
521 link
->nexttopic
= NULL
;
523 h
->prevtopic
->nexttopic
= link
;
526 link
->nonscroll
= link
->scroll
= NULL
;
527 link
->context
= topic
;
528 link
->block_size
= 0;
530 link
->recordtype
= 2; /* topic header */
531 link
->len1
= 4*7; /* standard linkdata1 size */
532 link
->data1
= snewn(link
->len1
, unsigned char);
534 slen
= strlen(title
);
535 assert(slen
+1 <= TOPIC_BLKSIZE
);
536 memcpy(h
->linkdata2
, title
, slen
+1);
540 while ( (macro
= va_arg(ap
, char *)) != NULL
) {
541 slen
= strlen(macro
);
542 assert(len
+slen
+1 <= TOPIC_BLKSIZE
);
543 memcpy(h
->linkdata2
+len
, macro
, slen
+1);
547 len
--; /* lose the last \0 on the last macro */
550 link
->data2
= snewn(link
->len2
, unsigned char);
551 memcpy(link
->data2
, h
->linkdata2
, link
->len2
);
553 topic
->title
= dupstr(title
);
556 addpos234(h
->text
, link
, count234(h
->text
));
559 void whlp_browse_link(WHLP h
, WHLP_TOPIC before
, WHLP_TOPIC after
)
564 * See if the `before' topic is already linked to another one,
565 * and break the link to that if so. Likewise the `after'
568 if (before
->browse_next
)
569 before
->browse_next
->browse_prev
= NULL
;
570 if (after
->browse_prev
)
571 after
->browse_prev
->browse_next
= NULL
;
572 before
->browse_next
= after
;
573 after
->browse_prev
= before
;
576 /* ----------------------------------------------------------------------
577 * Manage the actual generation of paragraph and text records.
580 static void whlp_linkdata(WHLP h
, int which
, int c
)
582 int *len
= (which
== 1 ?
&h
->link
->len1
: &h
->link
->len2
);
583 char *data
= (which
== 1 ? h
->linkdata1
: h
->linkdata2
);
584 assert(*len
< TOPIC_BLKSIZE
);
588 static void whlp_linkdata_short(WHLP h
, int which
, int data
)
590 whlp_linkdata(h
, which
, data
& 0xFF);
591 whlp_linkdata(h
, which
, (data
>> 8) & 0xFF);
594 static void whlp_linkdata_long(WHLP h
, int which
, int data
)
596 whlp_linkdata(h
, which
, data
& 0xFF);
597 whlp_linkdata(h
, which
, (data
>> 8) & 0xFF);
598 whlp_linkdata(h
, which
, (data
>> 16) & 0xFF);
599 whlp_linkdata(h
, which
, (data
>> 24) & 0xFF);
602 static void whlp_linkdata_cushort(WHLP h
, int which
, int data
)
605 whlp_linkdata(h
, which
, data
*2);
607 whlp_linkdata(h
, which
, 1 + (data
%128 * 2));
608 whlp_linkdata(h
, which
, data
/128);
612 static void whlp_linkdata_csshort(WHLP h
, int which
, int data
)
614 if (data
>= -0x40 && data
<= 0x3F)
615 whlp_linkdata_cushort(h
, which
, data
+64);
617 whlp_linkdata_cushort(h
, which
, data
+16384);
620 static void whlp_linkdata_culong(WHLP h
, int which
, int data
)
622 if (data
<= 0x7FFF) {
623 whlp_linkdata_short(h
, which
, data
*2);
625 whlp_linkdata_short(h
, which
, 1 + (data
%32768 * 2));
626 whlp_linkdata_short(h
, which
, data
/32768);
630 static void whlp_linkdata_cslong(WHLP h
, int which
, int data
)
632 if (data
>= -0x4000 && data
<= 0x3FFF)
633 whlp_linkdata_culong(h
, which
, data
+16384);
635 whlp_linkdata_culong(h
, which
, data
+67108864);
638 static void whlp_para_reset(WHLP h
)
644 while ( (p
= index234(h
->tabstops
, 0)) != NULL
) {
645 delpos234(h
->tabstops
, 0);
650 void whlp_para_attr(WHLP h
, int attr_id
, int attr_param
)
652 if (attr_id
>= WHLP_PARA_SPACEABOVE
&&
653 attr_id
<= WHLP_PARA_FIRSTLINEINDENT
) {
654 h
->para_flags
|= 1 << attr_id
;
655 h
->para_attrs
[attr_id
] = attr_param
;
656 } else if (attr_id
== WHLP_PARA_ALIGNMENT
) {
657 h
->para_flags
&= ~0xC00;
658 if (attr_param
== WHLP_ALIGN_RIGHT
)
659 h
->para_flags
|= 0x400;
660 else if (attr_param
== WHLP_ALIGN_CENTRE
)
661 h
->para_flags
|= 0x800;
665 void whlp_set_tabstop(WHLP h
, int tabstop
, int alignment
)
669 if (alignment
== WHLP_ALIGN_CENTRE
)
671 if (alignment
== WHLP_ALIGN_RIGHT
)
676 add234(h
->tabstops
, p
);
677 h
->para_flags
|= 0x0200;
680 void whlp_begin_para(WHLP h
, int para_type
)
682 struct topiclink
*link
= snew(struct topiclink
);
686 * Clear these to NULL out of paranoia, although in records
687 * that aren't type 2 they should never actually be needed.
689 link
->nexttopic
= NULL
;
690 link
->context
= NULL
;
691 link
->nonscroll
= link
->scroll
= NULL
;
693 link
->recordtype
= 32; /* text record */
696 link
->len1
= link
->len2
= 0;
697 link
->data1
= h
->linkdata1
;
698 link
->data2
= h
->linkdata2
;
700 if (para_type
== WHLP_PARA_NONSCROLL
&& h
->prevtopic
&&
701 !h
->prevtopic
->nonscroll
)
702 h
->prevtopic
->nonscroll
= link
;
703 if (para_type
== WHLP_PARA_SCROLL
&& h
->prevtopic
&&
704 !h
->prevtopic
->scroll
)
705 h
->prevtopic
->scroll
= link
;
708 * Now we're ready to start accumulating stuff in linkdata1 and
709 * linkdata2. Next we build up the paragraph info. Note that
710 * the TopicSize (cslong: size of LinkData1 minus the topicsize
711 * and topiclength fields) and TopicLength (cushort: size of
712 * LinkData2) fields are missing; we will put those on when we
715 whlp_linkdata(h
, 1, 0); /* must-be-0x00 */
716 whlp_linkdata(h
, 1, 0x80); /* must-be-0x80 */
717 whlp_linkdata_short(h
, 1, 0); /* Winterhoff says `id'; always 0 AFAICT */
718 whlp_linkdata_short(h
, 1, h
->para_flags
);
719 for (i
= WHLP_PARA_SPACEABOVE
; i
<= WHLP_PARA_FIRSTLINEINDENT
; i
++) {
720 if (h
->para_flags
& (1<<i
))
721 whlp_linkdata_csshort(h
, 1, h
->para_attrs
[i
]);
723 if (h
->para_flags
& 0x0200) {
726 * Write out tab stop data.
728 ntabs
= count234(h
->tabstops
);
729 whlp_linkdata_csshort(h
, 1, ntabs
);
730 for (i
= 0; i
< ntabs
; i
++) {
732 tabp
= index234(h
->tabstops
, i
);
736 whlp_linkdata_cushort(h
, 1, tab
& 0xFFFF);
738 whlp_linkdata_cushort(h
, 1, tab
>> 16);
743 * Fine. Now we're ready to start writing actual text and
744 * formatting commands.
748 void whlp_set_font(WHLP h
, int font_id
)
751 * Write a NUL into linkdata2 to cause the reader to flip over
752 * to linkdata1 to see the formatting command.
754 whlp_linkdata(h
, 2, 0);
756 * Now the formatting command is 0x80 followed by a short.
758 whlp_linkdata(h
, 1, 0x80);
759 whlp_linkdata_short(h
, 1, font_id
);
762 void whlp_start_hyperlink(WHLP h
, WHLP_TOPIC target
)
765 * Write a NUL into linkdata2.
767 whlp_linkdata(h
, 2, 0);
769 * Now the formatting command is 0xE3 followed by the context
772 whlp_linkdata(h
, 1, 0xE3);
773 whlp_linkdata_long(h
, 1, target
->hash
);
776 void whlp_end_hyperlink(WHLP h
)
779 * Write a NUL into linkdata2.
781 whlp_linkdata(h
, 2, 0);
783 * Now the formatting command is 0x89.
785 whlp_linkdata(h
, 1, 0x89);
788 void whlp_tab(WHLP h
)
791 * Write a NUL into linkdata2.
793 whlp_linkdata(h
, 2, 0);
795 * Now the formatting command is 0x83.
797 whlp_linkdata(h
, 1, 0x83);
800 void whlp_text(WHLP h
, char *text
)
803 whlp_linkdata(h
, 2, *text
++);
807 void whlp_end_para(WHLP h
)
812 * Round off the paragraph with 0x82 and 0xFF formatting
813 * commands. Each requires a NUL in linkdata2.
815 whlp_linkdata(h
, 2, 0);
816 whlp_linkdata(h
, 1, 0x82);
817 whlp_linkdata(h
, 2, 0);
818 whlp_linkdata(h
, 1, 0xFF);
821 * Now finish up: create the header of linkdata1 (TopicLength
822 * and TopicSize fields), allocate the real linkdata1 and
823 * linkdata2 fields, and copy them out of the buffers in h.
824 * Then insert the finished topiclink into the `text' tree, and
827 data1cut
= h
->link
->len1
;
828 whlp_linkdata_cslong(h
, 1, data1cut
);
829 whlp_linkdata_cushort(h
, 1, h
->link
->len2
);
831 h
->link
->data1
= snewn(h
->link
->len1
, unsigned char);
832 memcpy(h
->link
->data1
, h
->linkdata1
+ data1cut
, h
->link
->len1
- data1cut
);
833 memcpy(h
->link
->data1
+ h
->link
->len1
- data1cut
, h
->linkdata1
, data1cut
);
834 h
->link
->data2
= snewn(h
->link
->len2
, unsigned char);
835 memcpy(h
->link
->data2
, h
->linkdata2
, h
->link
->len2
);
837 addpos234(h
->text
, h
->link
, count234(h
->text
));
839 /* Hack: accumulate the `blocksize' parameter in the topic header. */
841 h
->prevtopic
->block_size
+= 21 + h
->link
->len1
+ h
->link
->len2
;
843 h
->link
= NULL
; /* this is now in the tree */
848 /* ----------------------------------------------------------------------
849 * Manage the layout and generation of the |TOPIC section.
852 static void whlp_topicsect_write(WHLP h
, struct file
*f
, void *data
, int len
,
855 unsigned char *p
= (unsigned char *)data
;
857 if (h
->topicblock_remaining
<= 0 ||
858 h
->topicblock_remaining
< can_break
) {
862 if (h
->topicblock_remaining
> 0)
863 whlp_file_fill(f
, h
->topicblock_remaining
);
864 whlp_file_add_long(f
, h
->lasttopiclink
);
865 h
->firsttopiclink_offset
= whlp_file_offset(f
);
866 whlp_file_add_long(f
, -1L); /* this will be filled in later */
867 whlp_file_add_long(f
, h
->lasttopicstart
);
868 h
->topicblock_remaining
= TOPIC_BLKSIZE
- 12;
871 int thislen
= (h
->topicblock_remaining
< len ?
872 h
->topicblock_remaining
: len
);
873 whlp_file_add(f
, p
, thislen
);
876 h
->topicblock_remaining
-= thislen
;
877 if (len
> 0 && h
->topicblock_remaining
<= 0) {
881 whlp_file_add_long(f
, h
->lasttopiclink
);
882 h
->firsttopiclink_offset
= whlp_file_offset(f
);
883 whlp_file_add_long(f
, -1L); /* this will be filled in later */
884 whlp_file_add_long(f
, h
->lasttopicstart
);
885 h
->topicblock_remaining
= TOPIC_BLKSIZE
- 12;
890 static void whlp_topic_layout(WHLP h
)
892 int block
, offset
, pos
;
895 struct topiclink
*link
;
899 * Create a final TOPICLINK containing no usable data.
901 link
= snew(struct topiclink
);
902 link
->nexttopic
= NULL
;
904 h
->prevtopic
->nexttopic
= link
;
906 link
->data1
= snewn(0x1c, unsigned char);
907 link
->block_size
= 0;
911 link
->nexttopic
= NULL
;
912 link
->recordtype
= 2;
913 link
->nonscroll
= link
->scroll
= NULL
;
914 link
->context
= NULL
;
915 addpos234(h
->text
, link
, count234(h
->text
));
918 * Each TOPICBLOCK has space for TOPIC_BLKSIZE-12 bytes. The
919 * size of each TOPICLINK is 21 bytes plus the combined lengths
920 * of LinkData1 and LinkData2. So we can now go through and
921 * break up the TOPICLINKs into TOPICBLOCKs, and also set up
922 * the TOPICOFFSET and TOPICPOS of each one while we do so.
928 nlinks
= count234(h
->text
);
929 for (i
= 0; i
< nlinks
; i
++) {
930 link
= index234(h
->text
, i
);
931 size
= 21 + link
->len1
+ link
->len2
;
933 * We can't split within the topicblock header or within
934 * linkdata1. So if the split would fall in that area,
935 * start a new block _now_.
937 if (TOPIC_BLKSIZE
- pos
< 21 + link
->len1
) {
942 link
->topicoffset
= block
* 0x8000 + offset
;
943 link
->topicpos
= block
* 0x4000 + pos
;
945 if (link
->recordtype
!= 2) /* TOPICOFFSET doesn't count titles */
946 offset
+= link
->len2
;
947 while (pos
> TOPIC_BLKSIZE
) {
950 pos
-= TOPIC_BLKSIZE
- 12;
955 * Now we have laid out the TOPICLINKs into blocks, and
956 * determined the final TOPICOFFSET and TOPICPOS of each one.
957 * So now we can go through and write the headers of the type-2
962 for (i
= 0; i
< nlinks
; i
++) {
963 link
= index234(h
->text
, i
);
964 if (link
->recordtype
!= 2)
967 PUT_32BIT_LSB_FIRST(link
->data1
+ 0, link
->block_size
);
968 if (link
->context
&& link
->context
->browse_prev
)
969 PUT_32BIT_LSB_FIRST(link
->data1
+ 4,
970 link
->context
->browse_prev
->link
->topicoffset
);
972 PUT_32BIT_LSB_FIRST(link
->data1
+ 4, 0xFFFFFFFFL
);
973 if (link
->context
&& link
->context
->browse_next
)
974 PUT_32BIT_LSB_FIRST(link
->data1
+ 8,
975 link
->context
->browse_next
->link
->topicoffset
);
977 PUT_32BIT_LSB_FIRST(link
->data1
+ 8, 0xFFFFFFFFL
);
978 PUT_32BIT_LSB_FIRST(link
->data1
+ 12, topicnum
);
981 PUT_32BIT_LSB_FIRST(link
->data1
+ 16, link
->nonscroll
->topicpos
);
983 PUT_32BIT_LSB_FIRST(link
->data1
+ 16, 0xFFFFFFFFL
);
985 PUT_32BIT_LSB_FIRST(link
->data1
+ 20, link
->scroll
->topicpos
);
987 PUT_32BIT_LSB_FIRST(link
->data1
+ 20, 0xFFFFFFFFL
);
989 PUT_32BIT_LSB_FIRST(link
->data1
+ 24, link
->nexttopic
->topicpos
);
991 PUT_32BIT_LSB_FIRST(link
->data1
+ 24, 0xFFFFFFFFL
);
995 * Having done all _that_, we're now finally ready to go
996 * through and create the |TOPIC section in its final form.
999 h
->lasttopiclink
= -1L;
1000 h
->lasttopicstart
= 0L;
1001 f
= whlp_new_file(h
, "|TOPIC");
1002 h
->topicblock_remaining
= -1;
1003 whlp_topicsect_write(h
, f
, NULL
, 0, 0); /* start the first block */
1004 for (i
= 0; i
< nlinks
; i
++) {
1005 unsigned char header
[21];
1006 struct topiclink
*otherlink
;
1008 link
= index234(h
->text
, i
);
1011 * Create and output the TOPICLINK header.
1013 PUT_32BIT_LSB_FIRST(header
+ 0, 21 + link
->len1
+ link
->len2
);
1014 PUT_32BIT_LSB_FIRST(header
+ 4, link
->len2
);
1016 PUT_32BIT_LSB_FIRST(header
+ 8, 0xFFFFFFFFL
);
1018 otherlink
= index234(h
->text
, i
-1);
1019 PUT_32BIT_LSB_FIRST(header
+ 8, otherlink
->topicpos
);
1021 if (i
+1 >= nlinks
) {
1022 PUT_32BIT_LSB_FIRST(header
+ 12, 0xFFFFFFFFL
);
1024 otherlink
= index234(h
->text
, i
+1);
1025 PUT_32BIT_LSB_FIRST(header
+ 12, otherlink
->topicpos
);
1027 PUT_32BIT_LSB_FIRST(header
+ 16, 21 + link
->len1
);
1028 header
[20] = link
->recordtype
;
1029 whlp_topicsect_write(h
, f
, header
, 21, 21 + link
->len1
);
1032 * Fill in the `first topiclink' pointer in the block
1033 * header if appropriate. (We do this _after_ outputting
1034 * the header because then we can be sure we'll be in the
1035 * same block as we think we are.)
1037 if (h
->firsttopiclink_offset
> 0) {
1038 whlp_file_seek(f
, h
->firsttopiclink_offset
, 0);
1039 whlp_file_add_long(f
, link
->topicpos
);
1040 h
->firsttopiclink_offset
= 0;
1041 whlp_file_seek(f
, 0, 2);
1045 * Update the `last topiclink', and possibly `last
1046 * topicstart', pointers.
1048 h
->lasttopiclink
= link
->topicpos
;
1049 if (link
->recordtype
== 2)
1050 h
->lasttopicstart
= link
->topicpos
;
1054 * Output LinkData1 and LinkData2.
1056 whlp_topicsect_write(h
, f
, link
->data1
, link
->len1
, link
->len1
);
1057 whlp_topicsect_write(h
, f
, link
->data2
, link
->len2
, 0);
1060 * Output the block header.
1063 link
= index234(h
->text
, i
);
1068 /* ----------------------------------------------------------------------
1069 * Manage the index sections (|KWDATA, |KWMAP, |KWBTREE).
1072 void whlp_index_term(WHLP h
, char *index
, WHLP_TOPIC topic
)
1074 struct indexrec
*idx
= snew(struct indexrec
);
1076 idx
->term
= dupstr(index
);
1079 * If this reference is already in the tree, just silently drop
1082 if (add234(h
->index
, idx
) != idx
) {
1088 static void whlp_build_kwdata(WHLP h
)
1092 struct indexrec
*first
, *next
;
1094 f
= whlp_new_file(h
, "|KWDATA");
1097 * Go through the index B-tree, condensing all sequences of
1098 * records with the same term into a single one with a valid
1099 * (count,offset) pair, and building up the KWDATA section.
1102 while ( (first
= index234(h
->index
, i
)) != NULL
) {
1104 first
->offset
= whlp_file_offset(f
);
1105 whlp_file_add_long(f
, first
->topic
->link
->topicoffset
);
1107 while ( (next
= index234(h
->index
, i
)) != NULL
&&
1108 !strcmp(first
->term
, next
->term
)) {
1110 * The next index record has the same term. Fold it
1111 * into this one and remove from the tree.
1113 whlp_file_add_long(f
, next
->topic
->link
->topicoffset
);
1115 delpos234(h
->index
, i
);
1122 * Now we should have `index' in a form that's ready to
1123 * construct |KWBTREE. So we can return.
1127 /* ----------------------------------------------------------------------
1128 * Standard chunks of data for the |SYSTEM and |FONT sections.
1131 static void whlp_system_record(struct file
*f
, int id
,
1132 const void *data
, int length
)
1134 whlp_file_add_short(f
, id
);
1135 whlp_file_add_short(f
, length
);
1136 whlp_file_add(f
, data
, length
);
1139 static void whlp_standard_systemsection(struct file
*f
)
1141 const char lcid
[] = { 0, 0, 0, 0, 0, 0, 0, 0, 9, 4 };
1142 const char charset
[] = { 0, 0, 0, 2, 0 };
1144 whlp_file_add_short(f
, 0x36C); /* magic number */
1145 whlp_file_add_short(f
, 33); /* minor version: HCW 4.00 Win95+ */
1146 whlp_file_add_short(f
, 1); /* major version */
1147 whlp_file_add_long(f
, time(NULL
)); /* generation date */
1148 whlp_file_add_short(f
, 0); /* flags=0 means no compression */
1151 * Add some magic locale identifier information. (We ought to
1152 * find out something about what all this means; see the TODO
1153 * list at the top of the file.)
1155 whlp_system_record(f
, 9, lcid
, sizeof(lcid
));
1156 whlp_system_record(f
, 11, charset
, sizeof(charset
));
1159 void whlp_title(WHLP h
, char *title
)
1161 whlp_system_record(h
->systemfile
, 1, title
, 1+strlen(title
));
1164 void whlp_copyright(WHLP h
, char *copyright
)
1166 whlp_system_record(h
->systemfile
, 2, copyright
, 1+strlen(copyright
));
1169 void whlp_start_macro(WHLP h
, char *macro
)
1171 whlp_system_record(h
->systemfile
, 4, macro
, 1+strlen(macro
));
1174 void whlp_primary_topic(WHLP h
, WHLP_TOPIC t
)
1179 static void whlp_do_primary_topic(WHLP h
)
1181 unsigned char firsttopic
[4];
1182 PUT_32BIT_LSB_FIRST(firsttopic
, h
->ptopic
->link
->topicoffset
);
1183 whlp_system_record(h
->systemfile
, 3, firsttopic
, sizeof(firsttopic
));
1186 int whlp_create_font(WHLP h
, char *font
, int family
, int halfpoints
,
1187 int rendition
, int r
, int g
, int b
)
1189 char *fontname
= dupstr(font
);
1190 struct fontdesc
*fontdesc
;
1193 font
= add234(h
->fontnames
, fontname
);
1194 if (font
!= fontname
) {
1195 /* The font name was already present. Free the new copy. */
1199 fontdesc
= snew(struct fontdesc
);
1200 fontdesc
->font
= font
;
1201 fontdesc
->family
= family
;
1202 fontdesc
->halfpoints
= halfpoints
;
1203 fontdesc
->rendition
= rendition
;
1208 index
= count234(h
->fontdescs
);
1209 addpos234(h
->fontdescs
, fontdesc
, index
);
1213 static void whlp_make_fontsection(WHLP h
, struct file
*f
)
1217 struct fontdesc
*fontdesc
;
1220 * Header block: number of font names, number of font
1221 * descriptors, offset to font names, and offset to font
1224 whlp_file_add_short(f
, count234(h
->fontnames
));
1225 whlp_file_add_short(f
, count234(h
->fontdescs
));
1226 whlp_file_add_short(f
, 8);
1227 whlp_file_add_short(f
, 8 + 32 * count234(h
->fontnames
));
1232 for (i
= 0; (fontname
= index234(h
->fontnames
, i
)) != NULL
; i
++) {
1234 memset(data
, i
, sizeof(data
));
1235 strncpy(data
, fontname
, sizeof(data
));
1236 whlp_file_add(f
, data
, sizeof(data
));
1242 for (i
= 0; (fontdesc
= index234(h
->fontdescs
, i
)) != NULL
; i
++) {
1246 ret
= findpos234(h
->fontnames
, fontdesc
->font
, NULL
, &fontpos
);
1247 assert(ret
!= NULL
);
1249 whlp_file_add_char(f
, fontdesc
->rendition
);
1250 whlp_file_add_char(f
, fontdesc
->halfpoints
);
1251 whlp_file_add_char(f
, fontdesc
->family
);
1252 whlp_file_add_short(f
, fontpos
);
1253 /* Foreground RGB */
1254 whlp_file_add_char(f
, fontdesc
->r
);
1255 whlp_file_add_char(f
, fontdesc
->g
);
1256 whlp_file_add_char(f
, fontdesc
->b
);
1257 /* Background RGB is apparently unused and always set to zero */
1258 whlp_file_add_char(f
, 0);
1259 whlp_file_add_char(f
, 0);
1260 whlp_file_add_char(f
, 0);
1265 /* ----------------------------------------------------------------------
1266 * Routines to manage a B-tree type file.
1269 static void whlp_make_btree(struct file
*f
, int flags
, int pagesize
,
1270 char *dataformat
, tree234
*tree
,
1272 bt_index_fn indexfn
, bt_leaf_fn leaffn
)
1274 void **page_elements
= NULL
;
1275 int npages
= 0, pagessize
= 0;
1276 int npages_this_level
, nentries
, nlevels
;
1277 int total_leaf_entries
;
1278 char btdata
[MAX_PAGE_SIZE
];
1280 int page_start
, fixups_offset
, unused_bytes
;
1284 assert(pagesize
<= MAX_PAGE_SIZE
);
1287 * Start with the B-tree header. We'll have to come back and
1288 * fill in a few bits later.
1290 whlp_file_add_short(f
, 0x293B); /* magic number */
1291 whlp_file_add_short(f
, flags
);
1292 whlp_file_add_short(f
, pagesize
);
1295 memset(data
, 0, sizeof(data
));
1296 assert(strlen(dataformat
) <= sizeof(data
));
1297 memcpy(data
, dataformat
, strlen(dataformat
));
1298 whlp_file_add(f
, data
, sizeof(data
));
1300 whlp_file_add_short(f
, 0); /* must-be-zero */
1301 fixups_offset
= whlp_file_offset(f
);
1302 whlp_file_add_short(f
, 0); /* page splits; fix up later */
1303 whlp_file_add_short(f
, 0); /* root page index; fix up later */
1304 whlp_file_add_short(f
, -1); /* must-be-minus-one */
1305 whlp_file_add_short(f
, 0); /* total number of pages; fix later */
1306 whlp_file_add_short(f
, 0); /* number of levels; fix later */
1307 whlp_file_add_long(f
, count234(tree
));/* total B-tree entries */
1310 * If we have a map section, leave space at the start for its
1314 whlp_file_add_short(map
, 0);
1318 * Now create the leaf pages.
1322 npages_this_level
= 0;
1323 total_leaf_entries
= 0;
1325 element
= index234(tree
, index
);
1328 * Make a new leaf page.
1330 npages_this_level
++;
1331 if (npages
>= pagessize
) {
1332 pagessize
= npages
+ 32;
1333 page_elements
= sresize(page_elements
, pagessize
, void *);
1335 page_elements
[npages
++] = element
;
1338 * Leave space in the leaf page for the header. We'll
1339 * come back and add it later.
1341 page_start
= whlp_file_offset(f
);
1342 whlp_file_add(f
, "12345678", 8);
1343 unused_bytes
= pagesize
- 8;
1347 * Now add leaf entries until we run out of room, or out of
1351 btlen
= leaffn(element
, btdata
);
1352 if (btlen
> unused_bytes
)
1354 whlp_file_add(f
, btdata
, btlen
);
1355 unused_bytes
-= btlen
;
1358 element
= index234(tree
, index
);
1362 * Now add the unused bytes, and then go back and put
1365 whlp_file_fill(f
, unused_bytes
);
1366 whlp_file_seek(f
, page_start
, 0);
1367 whlp_file_add_short(f
, unused_bytes
);
1368 whlp_file_add_short(f
, nentries
);
1369 /* Previous-page indicator will automatically go to -1 when
1371 whlp_file_add_short(f
, npages
-2);
1372 /* Next-page indicator must be -1 if we're at the end. */
1374 whlp_file_add_short(f
, -1);
1376 whlp_file_add_short(f
, npages
);
1377 whlp_file_seek(f
, 0, 2);
1380 * If we have a map section, add a map entry.
1383 whlp_file_add_long(map
, total_leaf_entries
);
1384 whlp_file_add_short(map
, npages_this_level
-1);
1386 total_leaf_entries
+= nentries
;
1390 * If we have a map section, write the total number of map
1394 whlp_file_seek(map
, 0, 0);
1395 whlp_file_add_short(map
, npages_this_level
);
1396 whlp_file_seek(map
, 0, 2);
1400 * Now create further levels until we're down to one page.
1403 while (npages_this_level
> 1) {
1404 int first
= npages
- npages_this_level
;
1405 int last
= npages
- 1;
1409 npages_this_level
= 0;
1412 while (current
<= last
) {
1414 * Make a new index page.
1416 npages_this_level
++;
1417 if (npages
>= pagessize
) {
1418 pagessize
= npages
+ 32;
1419 page_elements
= sresize(page_elements
, pagessize
, void *);
1421 page_elements
[npages
++] = page_elements
[current
];
1424 * Leave space for some of the header, but we can put
1425 * in the PreviousPage link already.
1427 page_start
= whlp_file_offset(f
);
1428 whlp_file_add(f
, "1234", 4);
1429 whlp_file_add_short(f
, current
);
1430 unused_bytes
= pagesize
- 6;
1433 * Now add index entries until we run out of either
1438 while (current
<= last
) {
1439 btlen
= indexfn(page_elements
[current
], btdata
);
1440 if (btlen
+ 2 > unused_bytes
)
1442 whlp_file_add(f
, btdata
, btlen
);
1443 whlp_file_add_short(f
, current
);
1444 unused_bytes
-= btlen
+2;
1450 * Now add the unused bytes, and then go back and put
1453 whlp_file_fill(f
, unused_bytes
);
1454 whlp_file_seek(f
, page_start
, 0);
1455 whlp_file_add_short(f
, unused_bytes
);
1456 whlp_file_add_short(f
, nentries
);
1457 whlp_file_seek(f
, 0, 2);
1462 * Now we have all our pages ready, and we know where our root
1463 * page is. Fix up the main B-tree header.
1465 whlp_file_seek(f
, fixups_offset
, 0);
1466 /* Creation of every page requires a split unless it's the first in
1467 * a new level. Hence, page splits equals pages minus levels. */
1468 whlp_file_add_short(f
, npages
- nlevels
);
1469 whlp_file_add_short(f
, npages
-1); /* root page index */
1470 whlp_file_add_short(f
, -1); /* must-be-minus-one */
1471 whlp_file_add_short(f
, npages
); /* total number of pages */
1472 whlp_file_add_short(f
, nlevels
); /* number of levels */
1474 /* Just for tidiness, seek to the end of the file :-) */
1475 whlp_file_seek(f
, 0, 2);
1478 sfree(page_elements
);
1482 /* ----------------------------------------------------------------------
1483 * Routines to manage the `internal file' structure.
1486 static struct file
*whlp_new_file(WHLP h
, char *name
)
1489 f
= snew(struct file
);
1491 f
->pos
= f
->len
= f
->size
= 0;
1493 f
->name
= dupstr(name
);
1494 add234(h
->files
, f
);
1501 static void whlp_free_file(struct file
*f
)
1504 sfree(f
->name
); /* may be NULL */
1508 static void whlp_file_add(struct file
*f
, const void *data
, int len
)
1510 if (f
->pos
+ len
> f
->size
) {
1511 f
->size
= f
->pos
+ len
+ 1024;
1512 f
->data
= sresize(f
->data
, f
->size
, unsigned char);
1514 memcpy(f
->data
+ f
->pos
, data
, len
);
1516 if (f
->len
< f
->pos
)
1520 static void whlp_file_add_char(struct file
*f
, int data
)
1524 whlp_file_add(f
, &s
, 1);
1527 static void whlp_file_add_short(struct file
*f
, int data
)
1530 PUT_16BIT_LSB_FIRST(s
, data
);
1531 whlp_file_add(f
, s
, 2);
1534 static void whlp_file_add_long(struct file
*f
, int data
)
1537 PUT_32BIT_LSB_FIRST(s
, data
);
1538 whlp_file_add(f
, s
, 4);
1541 static void whlp_file_fill(struct file
*f
, int len
)
1543 if (f
->pos
+ len
> f
->size
) {
1544 f
->size
= f
->pos
+ len
+ 1024;
1545 f
->data
= sresize(f
->data
, f
->size
, unsigned char);
1547 memset(f
->data
+ f
->pos
, 0, len
);
1549 if (f
->len
< f
->pos
)
1553 static void whlp_file_seek(struct file
*f
, int pos
, int whence
)
1555 f
->pos
= (whence
== 0 ?
0 : whence
== 1 ? f
->pos
: f
->len
) + pos
;
1558 static int whlp_file_offset(struct file
*f
)
1563 /* ----------------------------------------------------------------------
1564 * Open and close routines; final wrapper around everything.
1572 ret
= snew(struct WHLP_tag
);
1577 ret
->files
= newtree234(filecmp
);
1578 ret
->pre_contexts
= newtree234(NULL
);
1579 ret
->contexts
= newtree234(ctxcmp
);
1580 ret
->titles
= newtree234(ttlcmp
);
1581 ret
->text
= newtree234(NULL
);
1582 ret
->index
= newtree234(idxcmp
);
1583 ret
->tabstops
= newtree234(tabcmp
);
1584 ret
->fontnames
= newtree234(fontcmp
);
1585 ret
->fontdescs
= newtree234(NULL
);
1588 * Some standard files.
1590 f
= whlp_new_file(ret
, "|CTXOMAP");
1591 whlp_file_add_short(f
, 0); /* dummy section */
1592 f
= whlp_new_file(ret
, "|SYSTEM");
1593 whlp_standard_systemsection(f
);
1594 ret
->systemfile
= f
;
1599 ret
->prevtopic
= NULL
;
1606 void whlp_close(WHLP h
, char *filename
)
1609 int filecount
, offset
, index
, filelen
;
1610 struct file
*file
, *map
, *md
;
1615 * Lay out the topic section.
1617 whlp_topic_layout(h
);
1620 * Finish off the system section.
1622 whlp_do_primary_topic(h
);
1625 * Assemble the font section.
1627 file
= whlp_new_file(h
, "|FONT");
1628 whlp_make_fontsection(h
, file
);
1633 has_index
= (count234(h
->index
) != 0);
1635 whlp_build_kwdata(h
);
1638 * Set up the `titles' B-tree for the |TTLBTREE section.
1640 for (index
= 0; (ctx
= index234(h
->contexts
, index
)) != NULL
; index
++)
1641 add234(h
->titles
, ctx
);
1644 * Construct the various B-trees.
1646 file
= whlp_new_file(h
, "|CONTEXT");
1647 whlp_make_btree(file
, 0x0002, 0x0800, "L4",
1648 h
->contexts
, NULL
, ctxindex
, ctxleaf
);
1650 file
= whlp_new_file(h
, "|TTLBTREE");
1651 whlp_make_btree(file
, 0x0002, 0x0800, "Lz",
1652 h
->titles
, NULL
, ttlindex
, ttlleaf
);
1655 file
= whlp_new_file(h
, "|KWBTREE");
1656 map
= whlp_new_file(h
, "|KWMAP");
1657 whlp_make_btree(file
, 0x0002, 0x0800, "F24",
1658 h
->index
, map
, idxindex
, idxleaf
);
1662 * Open the output file.
1664 fp
= fopen(filename
, "wb");
1671 * Work out all the file offsets.
1673 filecount
= count234(h
->files
);
1674 offset
= 16; /* just after header */
1675 for (index
= 0; index
< filecount
; index
++) {
1676 file
= index234(h
->files
, index
);
1677 file
->fileoffset
= offset
;
1678 offset
+= 9 + file
->len
; /* 9 is size of file header */
1680 /* Now `offset' holds what will be the offset of the master directory. */
1682 md
= whlp_new_file(h
, NULL
); /* master directory file */
1683 whlp_make_btree(md
, 0x0402, 0x0400, "z4",
1684 h
->files
, NULL
, fileindex
, fileleaf
);
1686 filelen
= offset
+ 9 + md
->len
;
1689 * Write out the file header.
1692 unsigned char header
[16];
1693 PUT_32BIT_LSB_FIRST(header
+0, 0x00035F3FL
); /* magic */
1694 PUT_32BIT_LSB_FIRST(header
+4, offset
); /* offset to directory */
1695 PUT_32BIT_LSB_FIRST(header
+8, 0xFFFFFFFFL
); /* first free block */
1696 PUT_32BIT_LSB_FIRST(header
+12, filelen
); /* total file length */
1697 fwrite(header
, 1, 16, fp
);
1701 * Now write out each file.
1703 for (index
= 0; index
<= filecount
; index
++) {
1705 unsigned char header
[9];
1707 if (index
== filecount
)
1708 file
= md
; /* master directory comes last */
1710 file
= index234(h
->files
, index
);
1713 reserved
= used
+ 9;
1716 PUT_32BIT_LSB_FIRST(header
+0, reserved
);
1717 PUT_32BIT_LSB_FIRST(header
+4, used
);
1718 header
[8] = 0; /* flags */
1719 fwrite(header
, 1, 9, fp
);
1722 fwrite(file
->data
, 1, file
->len
, fp
);
1729 whlp_abandon(h
); /* now free everything */
1732 void whlp_abandon(WHLP h
)
1735 struct indexrec
*idx
;
1736 struct topiclink
*link
;
1737 struct fontdesc
*fontdesc
;
1741 /* Get rid of any lingering tab stops. */
1744 /* Delete the (now empty) tabstops tree. */
1745 freetree234(h
->tabstops
);
1747 /* Delete the index tree and all its entries. */
1748 while ( (idx
= index234(h
->index
, 0)) != NULL
) {
1749 delpos234(h
->index
, 0);
1753 freetree234(h
->index
);
1755 /* Delete the text tree and all its topiclinks. */
1756 while ( (link
= index234(h
->text
, 0)) != NULL
) {
1757 delpos234(h
->text
, 0);
1758 sfree(link
->data1
); /* may be NULL */
1759 sfree(link
->data2
); /* may be NULL */
1762 freetree234(h
->text
);
1764 /* Delete the fontdescs tree and all its entries. */
1765 while ( (fontdesc
= index234(h
->fontdescs
, 0)) != NULL
) {
1766 delpos234(h
->fontdescs
, 0);
1769 freetree234(h
->fontdescs
);
1771 /* Delete the fontnames tree and all its entries. */
1772 while ( (fontname
= index234(h
->fontnames
, 0)) != NULL
) {
1773 delpos234(h
->fontnames
, 0);
1776 freetree234(h
->fontnames
);
1778 /* There might be an unclosed paragraph in h->link. */
1780 sfree(h
->link
); /* if so it won't have data1 or data2 */
1783 * `titles' contains copies of the `contexts' entries, so we
1784 * don't need to free them here.
1786 freetree234(h
->titles
);
1789 * `contexts' and `pre_contexts' _both_ contain contexts that
1790 * need freeing. (pre_contexts shouldn't contain any, unless
1791 * the help generation was abandoned half-way through.)
1793 while ( (ctx
= index234(h
->pre_contexts
, 0)) != NULL
) {
1794 delpos234(h
->index
, 0);
1799 freetree234(h
->pre_contexts
);
1800 while ( (ctx
= index234(h
->contexts
, 0)) != NULL
) {
1801 delpos234(h
->contexts
, 0);
1806 freetree234(h
->contexts
);
1809 * Free all the internal files.
1811 while ( (f
= index234(h
->files
, 0)) != NULL
) {
1812 delpos234(h
->files
, 0);
1815 freetree234(h
->files
);
1825 WHLP_TOPIC t1
, t2
, t3
;
1831 whlp_title(h
, "Test Help File");
1832 whlp_copyright(h
, "This manual is copyright \251 2001 Simon Tatham."
1833 " All rights reversed.");
1834 whlp_start_macro(h
, "CB(\"btn_about\",\"&About\",\"About()\")");
1835 whlp_start_macro(h
, "CB(\"btn_up\",\"&Up\",\"Contents()\")");
1836 whlp_start_macro(h
, "BrowseButtons()");
1838 whlp_create_font(h
, "Arial", WHLP_FONTFAM_SANS
, 30,
1840 whlp_create_font(h
, "Times New Roman", WHLP_FONTFAM_SERIF
, 24,
1841 WHLP_FONT_STRIKEOUT
, 0, 0, 0);
1842 whlp_create_font(h
, "Times New Roman", WHLP_FONTFAM_SERIF
, 24,
1843 WHLP_FONT_ITALIC
, 0, 0, 0);
1844 whlp_create_font(h
, "Courier New", WHLP_FONTFAM_FIXED
, 24,
1847 t1
= whlp_register_topic(h
, "foobar", &e
);
1849 t2
= whlp_register_topic(h
, "M359HPEHGW", &e
);
1851 t3
= whlp_register_topic(h
, "Y5VQEXZQVJ", &e
);
1852 assert(t3
== NULL
&& !strcmp(e
, "M359HPEHGW"));
1853 t3
= whlp_register_topic(h
, NULL
, NULL
);
1856 whlp_primary_topic(h
, t2
);
1860 whlp_begin_topic(h
, t1
, "First Topic", "DB(\"btn_up\")", NULL
);
1862 whlp_begin_para(h
, WHLP_PARA_NONSCROLL
);
1863 whlp_set_font(h
, 0);
1864 whlp_text(h
, "Foobar");
1867 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1868 whlp_set_font(h
, 1);
1869 whlp_text(h
, "This is a silly paragraph with ");
1870 whlp_set_font(h
, 3);
1871 whlp_text(h
, "code");
1872 whlp_set_font(h
, 1);
1873 whlp_text(h
, " in it.");
1876 whlp_para_attr(h
, WHLP_PARA_SPACEABOVE
, 12);
1877 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1878 whlp_set_font(h
, 1);
1879 whlp_text(h
, "This second, equally silly, paragraph has ");
1880 whlp_set_font(h
, 2);
1881 whlp_text(h
, "emphasis");
1882 whlp_set_font(h
, 1);
1883 whlp_text(h
, " just to prove we can do it.");
1886 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1887 whlp_set_font(h
, 1);
1888 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1889 " to make some wrapping happen, and also to make the topicblock"
1890 " go across its boundaries. This is going to take a fair amount"
1891 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1894 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1895 whlp_set_font(h
, 1);
1896 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1897 " to make some wrapping happen, and also to make the topicblock"
1898 " go across its boundaries. This is going to take a fair amount"
1899 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1902 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1903 whlp_set_font(h
, 1);
1904 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1905 " to make some wrapping happen, and also to make the topicblock"
1906 " go across its boundaries. This is going to take a fair amount"
1907 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1910 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1911 whlp_set_font(h
, 1);
1912 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1913 " to make some wrapping happen, and also to make the topicblock"
1914 " go across its boundaries. This is going to take a fair amount"
1915 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1918 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1919 whlp_set_font(h
, 1);
1920 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1921 " to make some wrapping happen, and also to make the topicblock"
1922 " go across its boundaries. This is going to take a fair amount"
1923 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1926 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1927 whlp_set_font(h
, 1);
1928 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1929 " to make some wrapping happen, and also to make the topicblock"
1930 " go across its boundaries. This is going to take a fair amount"
1931 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1934 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1935 whlp_set_font(h
, 1);
1936 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1937 " to make some wrapping happen, and also to make the topicblock"
1938 " go across its boundaries. This is going to take a fair amount"
1939 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1942 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1943 whlp_set_font(h
, 1);
1944 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1945 " to make some wrapping happen, and also to make the topicblock"
1946 " go across its boundaries. This is going to take a fair amount"
1947 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1950 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1951 whlp_set_font(h
, 1);
1952 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1953 " to make some wrapping happen, and also to make the topicblock"
1954 " go across its boundaries. This is going to take a fair amount"
1955 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1958 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1959 whlp_set_font(h
, 1);
1960 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1961 " to make some wrapping happen, and also to make the topicblock"
1962 " go across its boundaries. This is going to take a fair amount"
1963 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1966 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1967 whlp_set_font(h
, 1);
1968 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1969 " to make some wrapping happen, and also to make the topicblock"
1970 " go across its boundaries. This is going to take a fair amount"
1971 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1974 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1975 whlp_set_font(h
, 1);
1976 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1977 " to make some wrapping happen, and also to make the topicblock"
1978 " go across its boundaries. This is going to take a fair amount"
1979 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1982 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1983 whlp_set_font(h
, 1);
1984 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1985 " to make some wrapping happen, and also to make the topicblock"
1986 " go across its boundaries. This is going to take a fair amount"
1987 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1990 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1991 whlp_set_font(h
, 1);
1992 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
1993 " to make some wrapping happen, and also to make the topicblock"
1994 " go across its boundaries. This is going to take a fair amount"
1995 " of text, so I'll just have to cheat and c'n'p a lot of it.");
1998 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
1999 whlp_set_font(h
, 1);
2000 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2001 " to make some wrapping happen, and also to make the topicblock"
2002 " go across its boundaries. This is going to take a fair amount"
2003 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2006 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2007 whlp_set_font(h
, 1);
2008 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2009 " to make some wrapping happen, and also to make the topicblock"
2010 " go across its boundaries. This is going to take a fair amount"
2011 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2014 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2015 whlp_set_font(h
, 1);
2016 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2017 " to make some wrapping happen, and also to make the topicblock"
2018 " go across its boundaries. This is going to take a fair amount"
2019 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2022 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2023 whlp_set_font(h
, 1);
2024 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2025 " to make some wrapping happen, and also to make the topicblock"
2026 " go across its boundaries. This is going to take a fair amount"
2027 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2030 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2031 whlp_set_font(h
, 1);
2032 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2033 " to make some wrapping happen, and also to make the topicblock"
2034 " go across its boundaries. This is going to take a fair amount"
2035 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2038 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2039 whlp_set_font(h
, 1);
2040 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2041 " to make some wrapping happen, and also to make the topicblock"
2042 " go across its boundaries. This is going to take a fair amount"
2043 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2046 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2047 whlp_set_font(h
, 1);
2048 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2049 " to make some wrapping happen, and also to make the topicblock"
2050 " go across its boundaries. This is going to take a fair amount"
2051 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2054 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2055 whlp_set_font(h
, 1);
2056 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2057 " to make some wrapping happen, and also to make the topicblock"
2058 " go across its boundaries. This is going to take a fair amount"
2059 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2062 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2063 whlp_set_font(h
, 1);
2064 whlp_text(h
, "Have a ");
2065 whlp_start_hyperlink(h
, t2
);
2066 whlp_text(h
, "hyperlink");
2067 whlp_end_hyperlink(h
);
2068 whlp_text(h
, " to another topic.");
2071 sprintf(mymacro
, "CBB(\"btn_up\",\"JI(`',`%s')\");EB(\"btn_up\")",
2074 whlp_begin_topic(h
, t2
, "Second Topic", mymacro
, NULL
);
2076 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2077 whlp_set_font(h
, 1);
2078 whlp_text(h
, "This topic contains no non-scrolling region. I would"
2079 " illustrate this with a ludicrously long paragraph, but that"
2080 " would get very tedious very quickly. Instead I'll just waffle"
2081 " on pointlessly for a little bit and then shut up.");
2084 whlp_set_tabstop(h
, 36, WHLP_ALIGN_LEFT
);
2085 whlp_para_attr(h
, WHLP_PARA_LEFTINDENT
, 36);
2086 whlp_para_attr(h
, WHLP_PARA_FIRSTLINEINDENT
, -36);
2087 whlp_para_attr(h
, WHLP_PARA_SPACEABOVE
, 12);
2088 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2089 whlp_set_font(h
, 1);
2090 whlp_text(h
, "\225"); /* bullet */
2092 whlp_text(h
, "This is a paragraph with a bullet. With any luck it should"
2093 " work exactly like it used to in the old NASM help file.");
2096 whlp_set_tabstop(h
, 128, WHLP_ALIGN_RIGHT
);
2097 whlp_set_tabstop(h
, 256, WHLP_ALIGN_CENTRE
);
2098 whlp_set_tabstop(h
, 384, WHLP_ALIGN_LEFT
);
2099 whlp_para_attr(h
, WHLP_PARA_SPACEABOVE
, 12);
2100 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2101 whlp_set_font(h
, 1);
2102 whlp_text(h
, "Ooh:"); whlp_tab(h
);
2103 whlp_text(h
, "Right?"); whlp_tab(h
);
2104 whlp_text(h
, "Centre?"); whlp_tab(h
);
2105 whlp_text(h
, "Left?");
2108 whlp_set_tabstop(h
, 128, WHLP_ALIGN_RIGHT
);
2109 whlp_set_tabstop(h
, 256, WHLP_ALIGN_CENTRE
);
2110 whlp_set_tabstop(h
, 384, WHLP_ALIGN_LEFT
);
2111 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2112 whlp_set_font(h
, 1);
2113 whlp_text(h
, "Aah:"); whlp_tab(h
);
2114 whlp_text(h
, "R?"); whlp_tab(h
);
2115 whlp_text(h
, "C?"); whlp_tab(h
);
2119 sprintf(mymacro
, "CBB(\"btn_up\",\"JI(`',`%s')\");EB(\"btn_up\")",
2122 whlp_begin_topic(h
, t3
, "Third Topic", mymacro
, NULL
);
2124 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2125 whlp_set_font(h
, 1);
2126 whlp_text(h
, "This third topic is almost as boring as the first. Woo!");
2132 whlp_browse_link(h
, t1
, t2
);
2133 whlp_browse_link(h
, t2
, t3
);
2138 whlp_index_term(h
, "foobarbaz", t1
);
2139 whlp_index_term(h
, "foobarbaz", t2
);
2140 whlp_index_term(h
, "foobarbaz", t3
);
2141 whlp_index_term(h
, "foobar", t1
);
2142 whlp_index_term(h
, "foobar", t2
);
2143 whlp_index_term(h
, "foobaz", t1
);
2144 whlp_index_term(h
, "foobaz", t3
);
2145 whlp_index_term(h
, "barbaz", t2
);
2146 whlp_index_term(h
, "barbaz", t3
);
2147 whlp_index_term(h
, "foo", t1
);
2148 whlp_index_term(h
, "bar", t2
);
2149 whlp_index_term(h
, "baz", t3
);
2151 whlp_close(h
, "test.hlp");