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.
78 #ifdef WINHELP_TESTMODE
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 */
189 /* Functions to return the index and leaf data for B-tree contents. */
190 typedef int (*bt_index_fn
)(const void *item
, unsigned char *outbuf
);
191 typedef int (*bt_leaf_fn
)(const void *item
, unsigned char *outbuf
);
193 /* Forward references. */
194 static void whlp_para_reset(WHLP h
);
195 static struct file
*whlp_new_file(WHLP h
, char *name
);
196 static void whlp_file_add(struct file
*f
, const void *data
, int len
);
197 static void whlp_file_add_char(struct file
*f
, int data
);
198 static void whlp_file_add_short(struct file
*f
, int data
);
199 static void whlp_file_add_long(struct file
*f
, int data
);
200 static void whlp_file_add_cushort(struct file
*f
, int data
);
201 #if 0 /* currently unused */
202 static void whlp_file_add_csshort(struct file
*f
, int data
);
204 static void whlp_file_add_culong(struct file
*f
, int data
);
205 #if 0 /* currently unused */
206 static void whlp_file_add_cslong(struct file
*f
, int data
);
208 static void whlp_file_fill(struct file
*f
, int len
);
209 static void whlp_file_seek(struct file
*f
, int pos
, int whence
);
210 static int whlp_file_offset(struct file
*f
);
212 /* ----------------------------------------------------------------------
213 * Fiddly little functions: B-tree compare, index and leaf functions.
216 /* The master index maps file names to help-file offsets. */
218 static int filecmp(void *av
, void *bv
)
220 const struct file
*a
= (const struct file
*)av
;
221 const struct file
*b
= (const struct file
*)bv
;
222 return strcmp(a
->name
, b
->name
);
225 static int fileindex(const void *av
, unsigned char *outbuf
)
227 const struct file
*a
= (const struct file
*)av
;
228 int len
= 1+strlen(a
->name
);
229 memcpy(outbuf
, a
->name
, len
);
233 static int fileleaf(const void *av
, unsigned char *outbuf
)
235 const struct file
*a
= (const struct file
*)av
;
236 int len
= 1+strlen(a
->name
);
237 memcpy(outbuf
, a
->name
, len
);
238 PUT_32BIT_LSB_FIRST(outbuf
+len
, a
->fileoffset
);
242 /* The |CONTEXT internal file maps help context hashes to TOPICOFFSETs. */
244 static int ctxcmp(void *av
, void *bv
)
246 const context
*a
= (const context
*)av
;
247 const context
*b
= (const context
*)bv
;
248 if ((signed long)a
->hash
< (signed long)b
->hash
)
250 if ((signed long)a
->hash
> (signed long)b
->hash
)
255 static int ctxindex(const void *av
, unsigned char *outbuf
)
257 const context
*a
= (const context
*)av
;
258 PUT_32BIT_LSB_FIRST(outbuf
, a
->hash
);
262 static int ctxleaf(const void *av
, unsigned char *outbuf
)
264 const context
*a
= (const context
*)av
;
265 PUT_32BIT_LSB_FIRST(outbuf
, a
->hash
);
266 PUT_32BIT_LSB_FIRST(outbuf
+4, a
->link
->topicoffset
);
270 /* The |TTLBTREE internal file maps TOPICOFFSETs to title strings. */
272 static int ttlcmp(void *av
, void *bv
)
274 const context
*a
= (const context
*)av
;
275 const context
*b
= (const context
*)bv
;
276 if (a
->link
->topicoffset
< b
->link
->topicoffset
)
278 if (a
->link
->topicoffset
> b
->link
->topicoffset
)
283 static int ttlindex(const void *av
, unsigned char *outbuf
)
285 const context
*a
= (const context
*)av
;
286 PUT_32BIT_LSB_FIRST(outbuf
, a
->link
->topicoffset
);
290 static int ttlleaf(const void *av
, unsigned char *outbuf
)
292 const context
*a
= (const context
*)av
;
294 PUT_32BIT_LSB_FIRST(outbuf
, a
->link
->topicoffset
);
295 slen
= 1+strlen(a
->title
);
296 memcpy(outbuf
+4, a
->title
, slen
);
300 /* The |KWBTREE internal file maps index strings to TOPICOFFSETs. */
302 static int idxcmp(void *av
, void *bv
)
304 const struct indexrec
*a
= (const struct indexrec
*)av
;
305 const struct indexrec
*b
= (const struct indexrec
*)bv
;
307 if ( (cmp
= strcmp(a
->term
, b
->term
)) != 0)
309 /* Now sort on the index field of the topics. */
310 if (a
->topic
->index
< b
->topic
->index
)
312 if (a
->topic
->index
> b
->topic
->index
)
317 static int idxindex(const void *av
, unsigned char *outbuf
)
319 const struct indexrec
*a
= (const struct indexrec
*)av
;
320 int len
= 1+strlen(a
->term
);
321 memcpy(outbuf
, a
->term
, len
);
325 static int idxleaf(const void *av
, unsigned char *outbuf
)
327 const struct indexrec
*a
= (const struct indexrec
*)av
;
328 int len
= 1+strlen(a
->term
);
329 memcpy(outbuf
, a
->term
, len
);
330 PUT_16BIT_LSB_FIRST(outbuf
+len
, a
->count
);
331 PUT_32BIT_LSB_FIRST(outbuf
+len
+2, a
->offset
);
336 * The internal `tabstops' B-tree stores pointers-to-int. Sorting
337 * is by the low 16 bits of the number (above that is flags).
340 static int tabcmp(void *av
, void *bv
)
342 const int *a
= (const int *)av
;
343 const int *b
= (const int *)bv
;
344 if ((*a
& 0xFFFF) < (*b
& 0xFFFF))
346 if ((*a
& 0xFFFF) > (*b
& 0xFFFF))
351 /* The internal `fontnames' B-tree stores strings. */
352 static int fontcmp(void *av
, void *bv
)
354 const char *a
= (const char *)av
;
355 const char *b
= (const char *)bv
;
359 /* ----------------------------------------------------------------------
360 * Manage help contexts and topics.
364 * This is the code to compute the hash of a context name. Copied
365 * straight from Winterhoff's documentation.
367 static unsigned long context_hash(char *context
)
369 signed char bytemapping
[256] =
370 "\x00\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF"
371 "\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF"
372 "\xF0\x0B\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\x0C\xFF"
373 "\x0A\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"
374 "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
375 "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2A\x0B\x0C\x0D\x0E\x0D"
376 "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
377 "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2A\x2B\x2C\x2D\x2E\x2F"
378 "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5A\x5B\x5C\x5D\x5E\x5F"
379 "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6A\x6B\x6C\x6D\x6E\x6F"
380 "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7A\x7B\x7C\x7D\x7E\x7F"
381 "\x80\x81\x82\x83\x0B\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F"
382 "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F"
383 "\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF"
384 "\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF"
385 "\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF";
389 * The hash algorithm starts the hash at 0 and updates it with
390 * each character. Therefore, logically, the hash of an empty
391 * string should be 0 (it starts at 0 and is never updated);
392 * but Winterhoff says it is in fact 1. Shouldn't matter, since
393 * I never plan to use empty context names, but I'll stick the
394 * special case in here anyway.
400 * Now compute the hash in the normal way.
405 * Be careful of overflowing `unsigned long', for maximum
410 * Multiply `hash' by 43.
413 unsigned long bottom
, top
;
414 bottom
= (hash
& 0xFFFFUL
) * 43;
415 top
= ((hash
>> 16) & 0xFFFFUL
) * 43;
416 top
+= (bottom
>> 16);
419 hash
= (top
<< 16) | bottom
;
423 * Add the mapping value for this byte to `hash'.
426 int val
= bytemapping
[(unsigned char)*context
];
428 if (val
> 0 && hash
> (0xFFFFFFFFUL
- val
)) {
429 hash
-= (0xFFFFFFFFUL
- val
) + 1;
430 } else if (val
< 0 && hash
< (unsigned long)-val
) {
431 hash
+= (0xFFFFFFFFUL
+ val
) + 1;
441 WHLP_TOPIC
whlp_register_topic(WHLP h
, char *context_name
, char **clash
)
443 context
*ctx
= snew(context
);
447 * Index contexts in order of creation, just so there's some
448 * sort of non-arbitrary ordering in the index B-tree. Call me
449 * fussy, but I don't like indexing on pointer values because I
450 * prefer the code to be deterministic when run under different
453 ctx
->index
= h
->ncontexts
++;
454 ctx
->browse_prev
= ctx
->browse_next
= NULL
;
458 * We have a context name, which means we can put this
459 * context straight into the `contexts' tree.
461 ctx
->name
= dupstr(context_name
);
462 ctx
->hash
= context_hash(context_name
);
463 otherctx
= add234(h
->contexts
, ctx
);
464 if (otherctx
!= ctx
) {
466 * Hash clash. Destroy the new context and return NULL,
467 * providing the clashing string.
471 if (clash
) *clash
= otherctx
->name
;
476 * We have no context name yet. Enter this into the
477 * pre_contexts tree of anonymous topics, which we will go
478 * through later and allocate unique context names and hash
482 addpos234(h
->pre_contexts
, ctx
, count234(h
->pre_contexts
));
487 void whlp_prepare(WHLP h
)
490 * We must go through pre_contexts and allocate a context ID to
491 * each anonymous context, making sure it doesn't clash with
492 * the existing contexts.
494 * Our own context IDs will just be of the form `t00000001',
495 * and we'll increment the number each time and skip over any
496 * IDs that clash with existing context names.
499 context
*ctx
, *otherctx
;
501 while ( (ctx
= index234(h
->pre_contexts
, 0)) != NULL
) {
502 delpos234(h
->pre_contexts
, 0);
503 ctx
->name
= snewn(20, char);
505 sprintf(ctx
->name
, "t%08d", ctx_num
++);
506 ctx
->hash
= context_hash(ctx
->name
);
507 otherctx
= add234(h
->contexts
, ctx
);
508 } while (otherctx
!= ctx
);
512 * Ensure paragraph attributes are clear for the start of text
518 char *whlp_topic_id(WHLP_TOPIC topic
)
523 void whlp_begin_topic(WHLP h
, WHLP_TOPIC topic
, char *title
, ...)
525 struct topiclink
*link
= snew(struct topiclink
);
530 link
->nexttopic
= NULL
;
532 h
->prevtopic
->nexttopic
= link
;
535 link
->nonscroll
= link
->scroll
= NULL
;
536 link
->context
= topic
;
537 link
->block_size
= 0;
539 link
->recordtype
= 2; /* topic header */
540 link
->len1
= 4*7; /* standard linkdata1 size */
541 link
->data1
= snewn(link
->len1
, unsigned char);
543 slen
= strlen(title
);
544 assert(slen
+1 <= TOPIC_BLKSIZE
);
545 memcpy(h
->linkdata2
, title
, slen
+1);
549 while ( (macro
= va_arg(ap
, char *)) != NULL
) {
550 slen
= strlen(macro
);
551 assert(len
+slen
+1 <= TOPIC_BLKSIZE
);
552 memcpy(h
->linkdata2
+len
, macro
, slen
+1);
556 len
--; /* lose the last \0 on the last macro */
559 link
->data2
= snewn(link
->len2
, unsigned char);
560 memcpy(link
->data2
, h
->linkdata2
, link
->len2
);
562 topic
->title
= dupstr(title
);
565 addpos234(h
->text
, link
, count234(h
->text
));
568 void whlp_browse_link(WHLP h
, WHLP_TOPIC before
, WHLP_TOPIC after
)
573 * See if the `before' topic is already linked to another one,
574 * and break the link to that if so. Likewise the `after'
577 if (before
->browse_next
)
578 before
->browse_next
->browse_prev
= NULL
;
579 if (after
->browse_prev
)
580 after
->browse_prev
->browse_next
= NULL
;
581 before
->browse_next
= after
;
582 after
->browse_prev
= before
;
585 /* ----------------------------------------------------------------------
586 * Manage the actual generation of paragraph and text records.
589 static void whlp_linkdata(WHLP h
, int which
, int c
)
591 int *len
= (which
== 1 ?
&h
->link
->len1
: &h
->link
->len2
);
592 unsigned char *data
= (which
== 1 ? h
->linkdata1
: h
->linkdata2
);
593 assert(*len
< TOPIC_BLKSIZE
);
597 static void whlp_linkdata_short(WHLP h
, int which
, int data
)
599 whlp_linkdata(h
, which
, data
& 0xFF);
600 whlp_linkdata(h
, which
, (data
>> 8) & 0xFF);
603 static void whlp_linkdata_long(WHLP h
, int which
, int data
)
605 whlp_linkdata(h
, which
, data
& 0xFF);
606 whlp_linkdata(h
, which
, (data
>> 8) & 0xFF);
607 whlp_linkdata(h
, which
, (data
>> 16) & 0xFF);
608 whlp_linkdata(h
, which
, (data
>> 24) & 0xFF);
611 static void whlp_linkdata_cushort(WHLP h
, int which
, int data
)
614 whlp_linkdata(h
, which
, data
*2);
616 whlp_linkdata(h
, which
, 1 + (data
%128 * 2));
617 whlp_linkdata(h
, which
, data
/128);
621 static void whlp_linkdata_csshort(WHLP h
, int which
, int data
)
623 if (data
>= -0x40 && data
<= 0x3F)
624 whlp_linkdata_cushort(h
, which
, data
+64);
626 whlp_linkdata_cushort(h
, which
, data
+16384);
629 static void whlp_linkdata_culong(WHLP h
, int which
, int data
)
631 if (data
<= 0x7FFF) {
632 whlp_linkdata_short(h
, which
, data
*2);
634 whlp_linkdata_short(h
, which
, 1 + (data
%32768 * 2));
635 whlp_linkdata_short(h
, which
, data
/32768);
639 static void whlp_linkdata_cslong(WHLP h
, int which
, int data
)
641 if (data
>= -0x4000 && data
<= 0x3FFF)
642 whlp_linkdata_culong(h
, which
, data
+16384);
644 whlp_linkdata_culong(h
, which
, data
+67108864);
647 static void whlp_para_reset(WHLP h
)
653 while ( (p
= index234(h
->tabstops
, 0)) != NULL
) {
654 delpos234(h
->tabstops
, 0);
659 void whlp_para_attr(WHLP h
, int attr_id
, int attr_param
)
661 if (attr_id
>= WHLP_PARA_SPACEABOVE
&&
662 attr_id
<= WHLP_PARA_FIRSTLINEINDENT
) {
663 h
->para_flags
|= 1 << attr_id
;
664 h
->para_attrs
[attr_id
] = attr_param
;
665 } else if (attr_id
== WHLP_PARA_ALIGNMENT
) {
666 h
->para_flags
&= ~0xC00;
667 if (attr_param
== WHLP_ALIGN_RIGHT
)
668 h
->para_flags
|= 0x400;
669 else if (attr_param
== WHLP_ALIGN_CENTRE
)
670 h
->para_flags
|= 0x800;
674 void whlp_set_tabstop(WHLP h
, int tabstop
, int alignment
)
678 if (alignment
== WHLP_ALIGN_CENTRE
)
680 if (alignment
== WHLP_ALIGN_RIGHT
)
685 add234(h
->tabstops
, p
);
686 h
->para_flags
|= 0x0200;
689 void whlp_begin_para(WHLP h
, int para_type
)
691 struct topiclink
*link
= snew(struct topiclink
);
695 * Clear these to NULL out of paranoia, although in records
696 * that aren't type 2 they should never actually be needed.
698 link
->nexttopic
= NULL
;
699 link
->context
= NULL
;
700 link
->nonscroll
= link
->scroll
= NULL
;
702 link
->recordtype
= 32; /* text record */
705 link
->len1
= link
->len2
= 0;
706 link
->data1
= h
->linkdata1
;
707 link
->data2
= h
->linkdata2
;
709 if (para_type
== WHLP_PARA_NONSCROLL
&& h
->prevtopic
&&
710 !h
->prevtopic
->nonscroll
)
711 h
->prevtopic
->nonscroll
= link
;
712 if (para_type
== WHLP_PARA_SCROLL
&& h
->prevtopic
&&
713 !h
->prevtopic
->scroll
)
714 h
->prevtopic
->scroll
= link
;
717 * Now we're ready to start accumulating stuff in linkdata1 and
718 * linkdata2. Next we build up the paragraph info. Note that
719 * the TopicSize (cslong: size of LinkData1 minus the topicsize
720 * and topiclength fields) and TopicLength (cushort: size of
721 * LinkData2) fields are missing; we will put those on when we
724 whlp_linkdata(h
, 1, 0); /* must-be-0x00 */
725 whlp_linkdata(h
, 1, 0x80); /* must-be-0x80 */
726 whlp_linkdata_short(h
, 1, 0); /* Winterhoff says `id'; always 0 AFAICT */
727 whlp_linkdata_short(h
, 1, h
->para_flags
);
728 for (i
= WHLP_PARA_SPACEABOVE
; i
<= WHLP_PARA_FIRSTLINEINDENT
; i
++) {
729 if (h
->para_flags
& (1<<i
))
730 whlp_linkdata_csshort(h
, 1, h
->para_attrs
[i
]);
732 if (h
->para_flags
& 0x0200) {
735 * Write out tab stop data.
737 ntabs
= count234(h
->tabstops
);
738 whlp_linkdata_csshort(h
, 1, ntabs
);
739 for (i
= 0; i
< ntabs
; i
++) {
741 tabp
= index234(h
->tabstops
, i
);
745 whlp_linkdata_cushort(h
, 1, tab
& 0xFFFF);
747 whlp_linkdata_cushort(h
, 1, tab
>> 16);
752 * Fine. Now we're ready to start writing actual text and
753 * formatting commands.
757 void whlp_set_font(WHLP h
, int font_id
)
760 * Write a NUL into linkdata2 to cause the reader to flip over
761 * to linkdata1 to see the formatting command.
763 whlp_linkdata(h
, 2, 0);
765 * Now the formatting command is 0x80 followed by a short.
767 whlp_linkdata(h
, 1, 0x80);
768 whlp_linkdata_short(h
, 1, font_id
);
771 void whlp_start_hyperlink(WHLP h
, WHLP_TOPIC target
)
774 * Write a NUL into linkdata2.
776 whlp_linkdata(h
, 2, 0);
778 * Now the formatting command is 0xE3 followed by the context
781 whlp_linkdata(h
, 1, 0xE3);
782 whlp_linkdata_long(h
, 1, target
->hash
);
785 void whlp_end_hyperlink(WHLP h
)
788 * Write a NUL into linkdata2.
790 whlp_linkdata(h
, 2, 0);
792 * Now the formatting command is 0x89.
794 whlp_linkdata(h
, 1, 0x89);
797 void whlp_tab(WHLP h
)
800 * Write a NUL into linkdata2.
802 whlp_linkdata(h
, 2, 0);
804 * Now the formatting command is 0x83.
806 whlp_linkdata(h
, 1, 0x83);
809 int whlp_add_picture(WHLP h
, int wd
, int ht
, const void *vpicdata
,
810 const unsigned long *palette
)
814 const unsigned char *picdata
= (const unsigned char *)vpicdata
;
815 int picstart
, picoff
, imgoff
, imgstart
;
821 * Determine the limit of the colour palette.
824 for (i
= 0; i
< wd
*ht
; i
++)
825 if (palettelen
< picdata
[i
])
826 palettelen
= picdata
[i
];
830 * Round up the width to the next multiple of 4.
832 wdrounded
= (wd
+ 3) & ~3;
834 index
= h
->picture_index
++;
835 sprintf(filename
, "bm%d", index
);
837 f
= whlp_new_file(h
, filename
);
838 whlp_file_add_short(f
, 0x706C); /* magic number */
839 whlp_file_add_short(f
, 1); /* number of pictures */
840 picoff
= whlp_file_offset(f
);
841 whlp_file_add_long(f
, 0); /* offset of first (only) picture */
842 picstart
= whlp_file_offset(f
);
843 whlp_file_add_char(f
, 6); /* DIB */
844 whlp_file_add_char(f
, 0); /* no packing */
845 whlp_file_add_culong(f
, 100); /* xdpi */
846 whlp_file_add_culong(f
, 100); /* ydpi */
847 whlp_file_add_cushort(f
, 1); /* planes (?) */
848 whlp_file_add_cushort(f
, 8); /* bitcount */
849 whlp_file_add_culong(f
, wd
); /* width */
850 whlp_file_add_culong(f
, ht
); /* height */
851 whlp_file_add_culong(f
, palettelen
);/* colours used */
852 whlp_file_add_culong(f
, palettelen
);/* colours important */
853 whlp_file_add_culong(f
, wdrounded
*ht
); /* `compressed' data size */
854 whlp_file_add_culong(f
, 0); /* hotspot size (no hotspots) */
855 imgoff
= whlp_file_offset(f
);
856 whlp_file_add_long(f
, 0); /* offset of `compressed' data */
857 whlp_file_add_long(f
, 0); /* offset of hotspot data (none) */
858 for (i
= 0; i
< palettelen
; i
++)
859 whlp_file_add_long(f
, palette
[i
]);
860 imgstart
= whlp_file_offset(f
);
862 * Windows Help files, like BMP, start from the bottom scanline.
864 for (i
= ht
; i
-- > 0 ;) {
865 whlp_file_add(f
, picdata
+ i
*wd
, wd
);
867 whlp_file_add(f
, "\0\0\0", wdrounded
- wd
);
870 /* Now go back and fix up internal offsets */
871 whlp_file_seek(f
, picoff
, 0);
872 whlp_file_add_long(f
, picstart
);
873 whlp_file_seek(f
, imgoff
, 0);
874 whlp_file_add_long(f
, imgstart
- picstart
);
875 whlp_file_seek(f
, 0, 2);
880 void whlp_ref_picture(WHLP h
, int picid
)
883 * Write a NUL into linkdata2.
885 whlp_linkdata(h
, 2, 0);
887 * Write the formatting command and its followup data to
888 * specify a picture in a separate file.
890 whlp_linkdata(h
, 1, 0x86);
891 whlp_linkdata(h
, 1, 3); /* type (picture without hotspots) */
892 whlp_linkdata_cslong(h
, 1, 4);
893 whlp_linkdata_short(h
, 1, 0);
894 whlp_linkdata_short(h
, 1, picid
);
897 void whlp_text(WHLP h
, char *text
)
900 whlp_linkdata(h
, 2, *text
++);
904 void whlp_end_para(WHLP h
)
909 * Round off the paragraph with 0x82 and 0xFF formatting
910 * commands. Each requires a NUL in linkdata2.
912 whlp_linkdata(h
, 2, 0);
913 whlp_linkdata(h
, 1, 0x82);
914 whlp_linkdata(h
, 2, 0);
915 whlp_linkdata(h
, 1, 0xFF);
918 * Now finish up: create the header of linkdata1 (TopicLength
919 * and TopicSize fields), allocate the real linkdata1 and
920 * linkdata2 fields, and copy them out of the buffers in h.
921 * Then insert the finished topiclink into the `text' tree, and
924 data1cut
= h
->link
->len1
;
925 whlp_linkdata_cslong(h
, 1, data1cut
);
926 whlp_linkdata_cushort(h
, 1, h
->link
->len2
);
928 h
->link
->data1
= snewn(h
->link
->len1
, unsigned char);
929 memcpy(h
->link
->data1
, h
->linkdata1
+ data1cut
, h
->link
->len1
- data1cut
);
930 memcpy(h
->link
->data1
+ h
->link
->len1
- data1cut
, h
->linkdata1
, data1cut
);
931 h
->link
->data2
= snewn(h
->link
->len2
, unsigned char);
932 memcpy(h
->link
->data2
, h
->linkdata2
, h
->link
->len2
);
934 addpos234(h
->text
, h
->link
, count234(h
->text
));
936 /* Hack: accumulate the `blocksize' parameter in the topic header. */
938 h
->prevtopic
->block_size
+= 21 + h
->link
->len1
+ h
->link
->len2
;
940 h
->link
= NULL
; /* this is now in the tree */
945 /* ----------------------------------------------------------------------
946 * Manage the layout and generation of the |TOPIC section.
949 static void whlp_topicsect_write(WHLP h
, struct file
*f
, void *data
, int len
,
952 unsigned char *p
= (unsigned char *)data
;
954 if (h
->topicblock_remaining
<= 0 ||
955 h
->topicblock_remaining
< can_break
) {
959 if (h
->topicblock_remaining
> 0)
960 whlp_file_fill(f
, h
->topicblock_remaining
);
961 whlp_file_add_long(f
, h
->lasttopiclink
);
962 h
->firsttopiclink_offset
= whlp_file_offset(f
);
963 whlp_file_add_long(f
, -1L); /* this will be filled in later */
964 whlp_file_add_long(f
, h
->lasttopicstart
);
965 h
->topicblock_remaining
= TOPIC_BLKSIZE
- 12;
968 int thislen
= (h
->topicblock_remaining
< len ?
969 h
->topicblock_remaining
: len
);
970 whlp_file_add(f
, p
, thislen
);
973 h
->topicblock_remaining
-= thislen
;
974 if (len
> 0 && h
->topicblock_remaining
<= 0) {
978 whlp_file_add_long(f
, h
->lasttopiclink
);
979 h
->firsttopiclink_offset
= whlp_file_offset(f
);
980 whlp_file_add_long(f
, -1L); /* this will be filled in later */
981 whlp_file_add_long(f
, h
->lasttopicstart
);
982 h
->topicblock_remaining
= TOPIC_BLKSIZE
- 12;
987 static void whlp_topic_layout(WHLP h
)
989 int block
, offset
, pos
;
992 struct topiclink
*link
;
996 * Create a final TOPICLINK containing no usable data.
998 link
= snew(struct topiclink
);
999 link
->nexttopic
= NULL
;
1001 h
->prevtopic
->nexttopic
= link
;
1002 h
->prevtopic
= link
;
1003 link
->data1
= snewn(0x1c, unsigned char);
1004 link
->block_size
= 0;
1008 link
->nexttopic
= NULL
;
1009 link
->recordtype
= 2;
1010 link
->nonscroll
= link
->scroll
= NULL
;
1011 link
->context
= NULL
;
1012 addpos234(h
->text
, link
, count234(h
->text
));
1015 * Each TOPICBLOCK has space for TOPIC_BLKSIZE-12 bytes. The
1016 * size of each TOPICLINK is 21 bytes plus the combined lengths
1017 * of LinkData1 and LinkData2. So we can now go through and
1018 * break up the TOPICLINKs into TOPICBLOCKs, and also set up
1019 * the TOPICOFFSET and TOPICPOS of each one while we do so.
1025 nlinks
= count234(h
->text
);
1026 for (i
= 0; i
< nlinks
; i
++) {
1027 link
= index234(h
->text
, i
);
1028 size
= 21 + link
->len1
+ link
->len2
;
1030 * We can't split within the topicblock header or within
1031 * linkdata1. So if the split would fall in that area,
1032 * start a new block _now_.
1034 if (TOPIC_BLKSIZE
- pos
< 21 + link
->len1
) {
1039 link
->topicoffset
= block
* 0x8000 + offset
;
1040 link
->topicpos
= block
* 0x4000 + pos
;
1042 if (link
->recordtype
!= 2) /* TOPICOFFSET doesn't count titles */
1043 offset
+= link
->len2
;
1044 while (pos
> TOPIC_BLKSIZE
) {
1047 pos
-= TOPIC_BLKSIZE
- 12;
1052 * Now we have laid out the TOPICLINKs into blocks, and
1053 * determined the final TOPICOFFSET and TOPICPOS of each one.
1054 * So now we can go through and write the headers of the type-2
1059 for (i
= 0; i
< nlinks
; i
++) {
1060 link
= index234(h
->text
, i
);
1061 if (link
->recordtype
!= 2)
1064 PUT_32BIT_LSB_FIRST(link
->data1
+ 0, link
->block_size
);
1065 if (link
->context
&& link
->context
->browse_prev
)
1066 PUT_32BIT_LSB_FIRST(link
->data1
+ 4,
1067 link
->context
->browse_prev
->link
->topicoffset
);
1069 PUT_32BIT_LSB_FIRST(link
->data1
+ 4, 0xFFFFFFFFL
);
1070 if (link
->context
&& link
->context
->browse_next
)
1071 PUT_32BIT_LSB_FIRST(link
->data1
+ 8,
1072 link
->context
->browse_next
->link
->topicoffset
);
1074 PUT_32BIT_LSB_FIRST(link
->data1
+ 8, 0xFFFFFFFFL
);
1075 PUT_32BIT_LSB_FIRST(link
->data1
+ 12, topicnum
);
1077 if (link
->nonscroll
)
1078 PUT_32BIT_LSB_FIRST(link
->data1
+ 16, link
->nonscroll
->topicpos
);
1080 PUT_32BIT_LSB_FIRST(link
->data1
+ 16, 0xFFFFFFFFL
);
1082 PUT_32BIT_LSB_FIRST(link
->data1
+ 20, link
->scroll
->topicpos
);
1084 PUT_32BIT_LSB_FIRST(link
->data1
+ 20, 0xFFFFFFFFL
);
1085 if (link
->nexttopic
)
1086 PUT_32BIT_LSB_FIRST(link
->data1
+ 24, link
->nexttopic
->topicpos
);
1088 PUT_32BIT_LSB_FIRST(link
->data1
+ 24, 0xFFFFFFFFL
);
1092 * Having done all _that_, we're now finally ready to go
1093 * through and create the |TOPIC section in its final form.
1096 h
->lasttopiclink
= -1L;
1097 h
->lasttopicstart
= 0L;
1098 f
= whlp_new_file(h
, "|TOPIC");
1099 h
->topicblock_remaining
= -1;
1100 whlp_topicsect_write(h
, f
, NULL
, 0, 0); /* start the first block */
1101 for (i
= 0; i
< nlinks
; i
++) {
1102 unsigned char header
[21];
1103 struct topiclink
*otherlink
;
1105 link
= index234(h
->text
, i
);
1108 * Create and output the TOPICLINK header.
1110 PUT_32BIT_LSB_FIRST(header
+ 0, 21 + link
->len1
+ link
->len2
);
1111 PUT_32BIT_LSB_FIRST(header
+ 4, link
->len2
);
1113 PUT_32BIT_LSB_FIRST(header
+ 8, 0xFFFFFFFFL
);
1115 otherlink
= index234(h
->text
, i
-1);
1116 PUT_32BIT_LSB_FIRST(header
+ 8, otherlink
->topicpos
);
1118 if (i
+1 >= nlinks
) {
1119 PUT_32BIT_LSB_FIRST(header
+ 12, 0xFFFFFFFFL
);
1121 otherlink
= index234(h
->text
, i
+1);
1122 PUT_32BIT_LSB_FIRST(header
+ 12, otherlink
->topicpos
);
1124 PUT_32BIT_LSB_FIRST(header
+ 16, 21 + link
->len1
);
1125 header
[20] = link
->recordtype
;
1126 whlp_topicsect_write(h
, f
, header
, 21, 21 + link
->len1
);
1129 * Fill in the `first topiclink' pointer in the block
1130 * header if appropriate. (We do this _after_ outputting
1131 * the header because then we can be sure we'll be in the
1132 * same block as we think we are.)
1134 if (h
->firsttopiclink_offset
> 0) {
1135 whlp_file_seek(f
, h
->firsttopiclink_offset
, 0);
1136 whlp_file_add_long(f
, link
->topicpos
);
1137 h
->firsttopiclink_offset
= 0;
1138 whlp_file_seek(f
, 0, 2);
1142 * Update the `last topiclink', and possibly `last
1143 * topicstart', pointers.
1145 h
->lasttopiclink
= link
->topicpos
;
1146 if (link
->recordtype
== 2)
1147 h
->lasttopicstart
= link
->topicpos
;
1151 * Output LinkData1 and LinkData2.
1153 whlp_topicsect_write(h
, f
, link
->data1
, link
->len1
, link
->len1
);
1154 whlp_topicsect_write(h
, f
, link
->data2
, link
->len2
, 0);
1157 * Output the block header.
1160 link
= index234(h
->text
, i
);
1165 /* ----------------------------------------------------------------------
1166 * Manage the index sections (|KWDATA, |KWMAP, |KWBTREE).
1169 void whlp_index_term(WHLP h
, char *index
, WHLP_TOPIC topic
)
1171 struct indexrec
*idx
= snew(struct indexrec
);
1173 idx
->term
= dupstr(index
);
1176 * If this reference is already in the tree, just silently drop
1179 if (add234(h
->index
, idx
) != idx
) {
1185 static void whlp_build_kwdata(WHLP h
)
1189 struct indexrec
*first
, *next
;
1191 f
= whlp_new_file(h
, "|KWDATA");
1194 * Go through the index B-tree, condensing all sequences of
1195 * records with the same term into a single one with a valid
1196 * (count,offset) pair, and building up the KWDATA section.
1199 while ( (first
= index234(h
->index
, i
)) != NULL
) {
1201 first
->offset
= whlp_file_offset(f
);
1202 whlp_file_add_long(f
, first
->topic
->link
->topicoffset
);
1204 while ( (next
= index234(h
->index
, i
)) != NULL
&&
1205 !strcmp(first
->term
, next
->term
)) {
1207 * The next index record has the same term. Fold it
1208 * into this one and remove from the tree.
1210 whlp_file_add_long(f
, next
->topic
->link
->topicoffset
);
1212 delpos234(h
->index
, i
);
1219 * Now we should have `index' in a form that's ready to
1220 * construct |KWBTREE. So we can return.
1224 /* ----------------------------------------------------------------------
1225 * Standard chunks of data for the |SYSTEM and |FONT sections.
1228 static void whlp_system_record(struct file
*f
, int id
,
1229 const void *data
, int length
)
1231 whlp_file_add_short(f
, id
);
1232 whlp_file_add_short(f
, length
);
1233 whlp_file_add(f
, data
, length
);
1236 static void whlp_standard_systemsection(struct file
*f
)
1238 const char lcid
[] = { 0, 0, 0, 0, 0, 0, 0, 0, 9, 4 };
1239 const char charset
[] = { 0, 0, 0, 2, 0 };
1241 whlp_file_add_short(f
, 0x36C); /* magic number */
1242 whlp_file_add_short(f
, 33); /* minor version: HCW 4.00 Win95+ */
1243 whlp_file_add_short(f
, 1); /* major version */
1244 whlp_file_add_long(f
, time(NULL
)); /* generation date */
1245 whlp_file_add_short(f
, 0); /* flags=0 means no compression */
1248 * Add some magic locale identifier information. (We ought to
1249 * find out something about what all this means; see the TODO
1250 * list at the top of the file.)
1252 whlp_system_record(f
, 9, lcid
, sizeof(lcid
));
1253 whlp_system_record(f
, 11, charset
, sizeof(charset
));
1256 void whlp_title(WHLP h
, char *title
)
1258 whlp_system_record(h
->systemfile
, 1, title
, 1+strlen(title
));
1261 void whlp_copyright(WHLP h
, char *copyright
)
1263 whlp_system_record(h
->systemfile
, 2, copyright
, 1+strlen(copyright
));
1266 void whlp_start_macro(WHLP h
, char *macro
)
1268 whlp_system_record(h
->systemfile
, 4, macro
, 1+strlen(macro
));
1271 void whlp_primary_topic(WHLP h
, WHLP_TOPIC t
)
1276 static void whlp_do_primary_topic(WHLP h
)
1278 unsigned char firsttopic
[4];
1279 PUT_32BIT_LSB_FIRST(firsttopic
, h
->ptopic
->link
->topicoffset
);
1280 whlp_system_record(h
->systemfile
, 3, firsttopic
, sizeof(firsttopic
));
1283 int whlp_create_font(WHLP h
, char *font
, int family
, int halfpoints
,
1284 int rendition
, int r
, int g
, int b
)
1286 char *fontname
= dupstr(font
);
1287 struct fontdesc
*fontdesc
;
1290 font
= add234(h
->fontnames
, fontname
);
1291 if (font
!= fontname
) {
1292 /* The font name was already present. Free the new copy. */
1296 fontdesc
= snew(struct fontdesc
);
1297 fontdesc
->font
= font
;
1298 fontdesc
->family
= family
;
1299 fontdesc
->halfpoints
= halfpoints
;
1300 fontdesc
->rendition
= rendition
;
1305 index
= count234(h
->fontdescs
);
1306 addpos234(h
->fontdescs
, fontdesc
, index
);
1310 static void whlp_make_fontsection(WHLP h
, struct file
*f
)
1314 struct fontdesc
*fontdesc
;
1317 * Header block: number of font names, number of font
1318 * descriptors, offset to font names, and offset to font
1321 whlp_file_add_short(f
, count234(h
->fontnames
));
1322 whlp_file_add_short(f
, count234(h
->fontdescs
));
1323 whlp_file_add_short(f
, 8);
1324 whlp_file_add_short(f
, 8 + 32 * count234(h
->fontnames
));
1329 for (i
= 0; (fontname
= index234(h
->fontnames
, i
)) != NULL
; i
++) {
1331 memset(data
, i
, sizeof(data
));
1332 strncpy(data
, fontname
, sizeof(data
));
1333 whlp_file_add(f
, data
, sizeof(data
));
1339 for (i
= 0; (fontdesc
= index234(h
->fontdescs
, i
)) != NULL
; i
++) {
1343 ret
= findpos234(h
->fontnames
, fontdesc
->font
, NULL
, &fontpos
);
1344 assert(ret
!= NULL
);
1346 whlp_file_add_char(f
, fontdesc
->rendition
);
1347 whlp_file_add_char(f
, fontdesc
->halfpoints
);
1348 whlp_file_add_char(f
, fontdesc
->family
);
1349 whlp_file_add_short(f
, fontpos
);
1350 /* Foreground RGB */
1351 whlp_file_add_char(f
, fontdesc
->r
);
1352 whlp_file_add_char(f
, fontdesc
->g
);
1353 whlp_file_add_char(f
, fontdesc
->b
);
1354 /* Background RGB is apparently unused and always set to zero */
1355 whlp_file_add_char(f
, 0);
1356 whlp_file_add_char(f
, 0);
1357 whlp_file_add_char(f
, 0);
1362 /* ----------------------------------------------------------------------
1363 * Routines to manage a B-tree type file.
1366 static void whlp_make_btree(struct file
*f
, int flags
, int pagesize
,
1367 char *dataformat
, tree234
*tree
,
1369 bt_index_fn indexfn
, bt_leaf_fn leaffn
)
1371 void **page_elements
= NULL
;
1372 int npages
= 0, pagessize
= 0;
1373 int npages_this_level
, nentries
, nlevels
;
1374 int total_leaf_entries
;
1375 unsigned char btdata
[MAX_PAGE_SIZE
];
1377 int page_start
, fixups_offset
, unused_bytes
;
1381 assert(pagesize
<= MAX_PAGE_SIZE
);
1384 * Start with the B-tree header. We'll have to come back and
1385 * fill in a few bits later.
1387 whlp_file_add_short(f
, 0x293B); /* magic number */
1388 whlp_file_add_short(f
, flags
);
1389 whlp_file_add_short(f
, pagesize
);
1392 memset(data
, 0, sizeof(data
));
1393 assert(strlen(dataformat
) <= sizeof(data
));
1394 memcpy(data
, dataformat
, strlen(dataformat
));
1395 whlp_file_add(f
, data
, sizeof(data
));
1397 whlp_file_add_short(f
, 0); /* must-be-zero */
1398 fixups_offset
= whlp_file_offset(f
);
1399 whlp_file_add_short(f
, 0); /* page splits; fix up later */
1400 whlp_file_add_short(f
, 0); /* root page index; fix up later */
1401 whlp_file_add_short(f
, -1); /* must-be-minus-one */
1402 whlp_file_add_short(f
, 0); /* total number of pages; fix later */
1403 whlp_file_add_short(f
, 0); /* number of levels; fix later */
1404 whlp_file_add_long(f
, count234(tree
));/* total B-tree entries */
1407 * If we have a map section, leave space at the start for its
1411 whlp_file_add_short(map
, 0);
1415 * Now create the leaf pages.
1419 npages_this_level
= 0;
1420 total_leaf_entries
= 0;
1422 element
= index234(tree
, index
);
1425 * Make a new leaf page.
1427 npages_this_level
++;
1428 if (npages
>= pagessize
) {
1429 pagessize
= npages
+ 32;
1430 page_elements
= sresize(page_elements
, pagessize
, void *);
1432 page_elements
[npages
++] = element
;
1435 * Leave space in the leaf page for the header. We'll
1436 * come back and add it later.
1438 page_start
= whlp_file_offset(f
);
1439 whlp_file_add(f
, "12345678", 8);
1440 unused_bytes
= pagesize
- 8;
1444 * Now add leaf entries until we run out of room, or out of
1448 btlen
= leaffn(element
, btdata
);
1449 if (btlen
> unused_bytes
)
1451 whlp_file_add(f
, btdata
, btlen
);
1452 unused_bytes
-= btlen
;
1455 element
= index234(tree
, index
);
1459 * Now add the unused bytes, and then go back and put
1462 whlp_file_fill(f
, unused_bytes
);
1463 whlp_file_seek(f
, page_start
, 0);
1464 whlp_file_add_short(f
, unused_bytes
);
1465 whlp_file_add_short(f
, nentries
);
1466 /* Previous-page indicator will automatically go to -1 when
1468 whlp_file_add_short(f
, npages
-2);
1469 /* Next-page indicator must be -1 if we're at the end. */
1471 whlp_file_add_short(f
, -1);
1473 whlp_file_add_short(f
, npages
);
1474 whlp_file_seek(f
, 0, 2);
1477 * If we have a map section, add a map entry.
1480 whlp_file_add_long(map
, total_leaf_entries
);
1481 whlp_file_add_short(map
, npages_this_level
-1);
1483 total_leaf_entries
+= nentries
;
1487 * If we have a map section, write the total number of map
1491 whlp_file_seek(map
, 0, 0);
1492 whlp_file_add_short(map
, npages_this_level
);
1493 whlp_file_seek(map
, 0, 2);
1497 * Now create further levels until we're down to one page.
1500 while (npages_this_level
> 1) {
1501 int first
= npages
- npages_this_level
;
1502 int last
= npages
- 1;
1506 npages_this_level
= 0;
1509 while (current
<= last
) {
1511 * Make a new index page.
1513 npages_this_level
++;
1514 if (npages
>= pagessize
) {
1515 pagessize
= npages
+ 32;
1516 page_elements
= sresize(page_elements
, pagessize
, void *);
1518 page_elements
[npages
++] = page_elements
[current
];
1521 * Leave space for some of the header, but we can put
1522 * in the PreviousPage link already.
1524 page_start
= whlp_file_offset(f
);
1525 whlp_file_add(f
, "1234", 4);
1526 whlp_file_add_short(f
, current
);
1527 unused_bytes
= pagesize
- 6;
1530 * Now add index entries until we run out of either
1535 while (current
<= last
) {
1536 btlen
= indexfn(page_elements
[current
], btdata
);
1537 if (btlen
+ 2 > unused_bytes
)
1539 whlp_file_add(f
, btdata
, btlen
);
1540 whlp_file_add_short(f
, current
);
1541 unused_bytes
-= btlen
+2;
1547 * Now add the unused bytes, and then go back and put
1550 whlp_file_fill(f
, unused_bytes
);
1551 whlp_file_seek(f
, page_start
, 0);
1552 whlp_file_add_short(f
, unused_bytes
);
1553 whlp_file_add_short(f
, nentries
);
1554 whlp_file_seek(f
, 0, 2);
1559 * Now we have all our pages ready, and we know where our root
1560 * page is. Fix up the main B-tree header.
1562 whlp_file_seek(f
, fixups_offset
, 0);
1563 /* Creation of every page requires a split unless it's the first in
1564 * a new level. Hence, page splits equals pages minus levels. */
1565 whlp_file_add_short(f
, npages
- nlevels
);
1566 whlp_file_add_short(f
, npages
-1); /* root page index */
1567 whlp_file_add_short(f
, -1); /* must-be-minus-one */
1568 whlp_file_add_short(f
, npages
); /* total number of pages */
1569 whlp_file_add_short(f
, nlevels
); /* number of levels */
1571 /* Just for tidiness, seek to the end of the file :-) */
1572 whlp_file_seek(f
, 0, 2);
1575 sfree(page_elements
);
1579 /* ----------------------------------------------------------------------
1580 * Routines to manage the `internal file' structure.
1583 static struct file
*whlp_new_file(WHLP h
, char *name
)
1586 f
= snew(struct file
);
1588 f
->pos
= f
->len
= f
->size
= 0;
1590 f
->name
= dupstr(name
);
1591 add234(h
->files
, f
);
1598 static void whlp_free_file(struct file
*f
)
1601 sfree(f
->name
); /* may be NULL */
1605 static void whlp_file_add(struct file
*f
, const void *data
, int len
)
1607 if (f
->pos
+ len
> f
->size
) {
1608 f
->size
= f
->pos
+ len
+ 1024;
1609 f
->data
= sresize(f
->data
, f
->size
, unsigned char);
1611 memcpy(f
->data
+ f
->pos
, data
, len
);
1613 if (f
->len
< f
->pos
)
1617 static void whlp_file_add_char(struct file
*f
, int data
)
1621 whlp_file_add(f
, &s
, 1);
1624 static void whlp_file_add_short(struct file
*f
, int data
)
1627 PUT_16BIT_LSB_FIRST(s
, data
);
1628 whlp_file_add(f
, s
, 2);
1631 static void whlp_file_add_long(struct file
*f
, int data
)
1634 PUT_32BIT_LSB_FIRST(s
, data
);
1635 whlp_file_add(f
, s
, 4);
1638 static void whlp_file_add_cushort(struct file
*f
, int data
)
1641 whlp_file_add_char(f
, data
*2);
1643 whlp_file_add_char(f
, 1 + (data
%128 * 2));
1644 whlp_file_add_char(f
, data
/128);
1648 #if 0 /* currently unused */
1649 static void whlp_file_add_csshort(struct file
*f
, int data
)
1651 if (data
>= -0x40 && data
<= 0x3F)
1652 whlp_file_add_cushort(f
, data
+64);
1654 whlp_file_add_cushort(f
, data
+16384);
1658 static void whlp_file_add_culong(struct file
*f
, int data
)
1660 if (data
<= 0x7FFF) {
1661 whlp_file_add_short(f
, data
*2);
1663 whlp_file_add_short(f
, 1 + (data
%32768 * 2));
1664 whlp_file_add_short(f
, data
/32768);
1668 #if 0 /* currently unused */
1669 static void whlp_file_add_cslong(struct file
*f
, int data
)
1671 if (data
>= -0x4000 && data
<= 0x3FFF)
1672 whlp_file_add_culong(f
, data
+16384);
1674 whlp_file_add_culong(f
, data
+67108864);
1678 static void whlp_file_fill(struct file
*f
, int len
)
1680 if (f
->pos
+ len
> f
->size
) {
1681 f
->size
= f
->pos
+ len
+ 1024;
1682 f
->data
= sresize(f
->data
, f
->size
, unsigned char);
1684 memset(f
->data
+ f
->pos
, 0, len
);
1686 if (f
->len
< f
->pos
)
1690 static void whlp_file_seek(struct file
*f
, int pos
, int whence
)
1692 f
->pos
= (whence
== 0 ?
0 : whence
== 1 ? f
->pos
: f
->len
) + pos
;
1695 static int whlp_file_offset(struct file
*f
)
1700 /* ----------------------------------------------------------------------
1701 * Open and close routines; final wrapper around everything.
1709 ret
= snew(struct WHLP_tag
);
1714 ret
->files
= newtree234(filecmp
);
1715 ret
->pre_contexts
= newtree234(NULL
);
1716 ret
->contexts
= newtree234(ctxcmp
);
1717 ret
->titles
= newtree234(ttlcmp
);
1718 ret
->text
= newtree234(NULL
);
1719 ret
->index
= newtree234(idxcmp
);
1720 ret
->tabstops
= newtree234(tabcmp
);
1721 ret
->fontnames
= newtree234(fontcmp
);
1722 ret
->fontdescs
= newtree234(NULL
);
1725 * Some standard files.
1727 f
= whlp_new_file(ret
, "|CTXOMAP");
1728 whlp_file_add_short(f
, 0); /* dummy section */
1729 f
= whlp_new_file(ret
, "|SYSTEM");
1730 whlp_standard_systemsection(f
);
1731 ret
->systemfile
= f
;
1736 ret
->prevtopic
= NULL
;
1739 ret
->picture_index
= 0;
1744 void whlp_close(WHLP h
, char *filename
)
1747 int filecount
, offset
, index
, filelen
;
1748 struct file
*file
, *map
, *md
;
1753 * Lay out the topic section.
1755 whlp_topic_layout(h
);
1758 * Finish off the system section.
1760 whlp_do_primary_topic(h
);
1763 * Assemble the font section.
1765 file
= whlp_new_file(h
, "|FONT");
1766 whlp_make_fontsection(h
, file
);
1771 has_index
= (count234(h
->index
) != 0);
1773 whlp_build_kwdata(h
);
1776 * Set up the `titles' B-tree for the |TTLBTREE section.
1778 for (index
= 0; (ctx
= index234(h
->contexts
, index
)) != NULL
; index
++)
1779 add234(h
->titles
, ctx
);
1782 * Construct the various B-trees.
1784 file
= whlp_new_file(h
, "|CONTEXT");
1785 whlp_make_btree(file
, 0x0002, 0x0800, "L4",
1786 h
->contexts
, NULL
, ctxindex
, ctxleaf
);
1788 file
= whlp_new_file(h
, "|TTLBTREE");
1789 whlp_make_btree(file
, 0x0002, 0x0800, "Lz",
1790 h
->titles
, NULL
, ttlindex
, ttlleaf
);
1793 file
= whlp_new_file(h
, "|KWBTREE");
1794 map
= whlp_new_file(h
, "|KWMAP");
1795 whlp_make_btree(file
, 0x0002, 0x0800, "F24",
1796 h
->index
, map
, idxindex
, idxleaf
);
1800 * Open the output file.
1802 fp
= fopen(filename
, "wb");
1809 * Work out all the file offsets.
1811 filecount
= count234(h
->files
);
1812 offset
= 16; /* just after header */
1813 for (index
= 0; index
< filecount
; index
++) {
1814 file
= index234(h
->files
, index
);
1815 file
->fileoffset
= offset
;
1816 offset
+= 9 + file
->len
; /* 9 is size of file header */
1818 /* Now `offset' holds what will be the offset of the master directory. */
1820 md
= whlp_new_file(h
, NULL
); /* master directory file */
1821 whlp_make_btree(md
, 0x0402, 0x0400, "z4",
1822 h
->files
, NULL
, fileindex
, fileleaf
);
1824 filelen
= offset
+ 9 + md
->len
;
1827 * Write out the file header.
1830 unsigned char header
[16];
1831 PUT_32BIT_LSB_FIRST(header
+0, 0x00035F3FL
); /* magic */
1832 PUT_32BIT_LSB_FIRST(header
+4, offset
); /* offset to directory */
1833 PUT_32BIT_LSB_FIRST(header
+8, 0xFFFFFFFFL
); /* first free block */
1834 PUT_32BIT_LSB_FIRST(header
+12, filelen
); /* total file length */
1835 fwrite(header
, 1, 16, fp
);
1839 * Now write out each file.
1841 for (index
= 0; index
<= filecount
; index
++) {
1843 unsigned char header
[9];
1845 if (index
== filecount
)
1846 file
= md
; /* master directory comes last */
1848 file
= index234(h
->files
, index
);
1851 reserved
= used
+ 9;
1854 PUT_32BIT_LSB_FIRST(header
+0, reserved
);
1855 PUT_32BIT_LSB_FIRST(header
+4, used
);
1856 header
[8] = 0; /* flags */
1857 fwrite(header
, 1, 9, fp
);
1860 fwrite(file
->data
, 1, file
->len
, fp
);
1867 whlp_abandon(h
); /* now free everything */
1870 void whlp_abandon(WHLP h
)
1873 struct indexrec
*idx
;
1874 struct topiclink
*link
;
1875 struct fontdesc
*fontdesc
;
1879 /* Get rid of any lingering tab stops. */
1882 /* Delete the (now empty) tabstops tree. */
1883 freetree234(h
->tabstops
);
1885 /* Delete the index tree and all its entries. */
1886 while ( (idx
= index234(h
->index
, 0)) != NULL
) {
1887 delpos234(h
->index
, 0);
1891 freetree234(h
->index
);
1893 /* Delete the text tree and all its topiclinks. */
1894 while ( (link
= index234(h
->text
, 0)) != NULL
) {
1895 delpos234(h
->text
, 0);
1896 sfree(link
->data1
); /* may be NULL */
1897 sfree(link
->data2
); /* may be NULL */
1900 freetree234(h
->text
);
1902 /* Delete the fontdescs tree and all its entries. */
1903 while ( (fontdesc
= index234(h
->fontdescs
, 0)) != NULL
) {
1904 delpos234(h
->fontdescs
, 0);
1907 freetree234(h
->fontdescs
);
1909 /* Delete the fontnames tree and all its entries. */
1910 while ( (fontname
= index234(h
->fontnames
, 0)) != NULL
) {
1911 delpos234(h
->fontnames
, 0);
1914 freetree234(h
->fontnames
);
1916 /* There might be an unclosed paragraph in h->link. */
1918 sfree(h
->link
); /* if so it won't have data1 or data2 */
1921 * `titles' contains copies of the `contexts' entries, so we
1922 * don't need to free them here.
1924 freetree234(h
->titles
);
1927 * `contexts' and `pre_contexts' _both_ contain contexts that
1928 * need freeing. (pre_contexts shouldn't contain any, unless
1929 * the help generation was abandoned half-way through.)
1931 while ( (ctx
= index234(h
->pre_contexts
, 0)) != NULL
) {
1932 delpos234(h
->index
, 0);
1937 freetree234(h
->pre_contexts
);
1938 while ( (ctx
= index234(h
->contexts
, 0)) != NULL
) {
1939 delpos234(h
->contexts
, 0);
1944 freetree234(h
->contexts
);
1947 * Free all the internal files.
1949 while ( (f
= index234(h
->files
, 0)) != NULL
) {
1950 delpos234(h
->files
, 0);
1953 freetree234(h
->files
);
1958 #ifdef WINHELP_TESTMODE
1960 #ifdef PICTURE_FROM_CMDLINE
1962 #include "colquant.h"
1966 int main(int argc
, char **argv
)
1969 WHLP_TOPIC t1
, t2
, t3
;
1975 whlp_title(h
, "Test Help File");
1976 whlp_copyright(h
, "This manual is copyright \251 2001 Simon Tatham."
1977 " All rights reversed.");
1978 whlp_start_macro(h
, "CB(\"btn_about\",\"&About\",\"About()\")");
1979 whlp_start_macro(h
, "CB(\"btn_up\",\"&Up\",\"Contents()\")");
1980 whlp_start_macro(h
, "BrowseButtons()");
1982 whlp_create_font(h
, "Arial", WHLP_FONTFAM_SANS
, 30,
1984 whlp_create_font(h
, "Times New Roman", WHLP_FONTFAM_SERIF
, 24,
1985 WHLP_FONT_STRIKEOUT
, 0, 0, 0);
1986 whlp_create_font(h
, "Times New Roman", WHLP_FONTFAM_SERIF
, 24,
1987 WHLP_FONT_ITALIC
, 0, 0, 0);
1988 whlp_create_font(h
, "Courier New", WHLP_FONTFAM_FIXED
, 24,
1991 t1
= whlp_register_topic(h
, "foobar", &e
);
1993 t2
= whlp_register_topic(h
, "M359HPEHGW", &e
);
1995 t3
= whlp_register_topic(h
, "Y5VQEXZQVJ", &e
);
1996 assert(t3
== NULL
&& !strcmp(e
, "M359HPEHGW"));
1997 t3
= whlp_register_topic(h
, NULL
, NULL
);
2000 whlp_primary_topic(h
, t2
);
2004 whlp_begin_topic(h
, t1
, "First Topic", "DB(\"btn_up\")", NULL
);
2006 whlp_begin_para(h
, WHLP_PARA_NONSCROLL
);
2007 whlp_set_font(h
, 0);
2008 whlp_text(h
, "Foobar");
2011 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2012 whlp_set_font(h
, 1);
2013 whlp_text(h
, "This is a silly paragraph with ");
2014 whlp_set_font(h
, 3);
2015 whlp_text(h
, "code");
2016 whlp_set_font(h
, 1);
2017 whlp_text(h
, " in it.");
2020 whlp_para_attr(h
, WHLP_PARA_SPACEABOVE
, 12);
2021 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2022 whlp_set_font(h
, 1);
2023 whlp_text(h
, "This second, equally silly, paragraph has ");
2024 whlp_set_font(h
, 2);
2025 whlp_text(h
, "emphasis");
2026 whlp_set_font(h
, 1);
2027 whlp_text(h
, " just to prove we can do it.");
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
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2065 " to make some wrapping happen, and also to make the topicblock"
2066 " go across its boundaries. This is going to take a fair amount"
2067 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2070 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2071 whlp_set_font(h
, 1);
2072 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2073 " to make some wrapping happen, and also to make the topicblock"
2074 " go across its boundaries. This is going to take a fair amount"
2075 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2078 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2079 whlp_set_font(h
, 1);
2080 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2081 " to make some wrapping happen, and also to make the topicblock"
2082 " go across its boundaries. This is going to take a fair amount"
2083 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2086 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2087 whlp_set_font(h
, 1);
2088 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2089 " to make some wrapping happen, and also to make the topicblock"
2090 " go across its boundaries. This is going to take a fair amount"
2091 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2094 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2095 whlp_set_font(h
, 1);
2096 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2097 " to make some wrapping happen, and also to make the topicblock"
2098 " go across its boundaries. This is going to take a fair amount"
2099 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2102 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2103 whlp_set_font(h
, 1);
2104 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2105 " to make some wrapping happen, and also to make the topicblock"
2106 " go across its boundaries. This is going to take a fair amount"
2107 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2110 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2111 whlp_set_font(h
, 1);
2112 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2113 " to make some wrapping happen, and also to make the topicblock"
2114 " go across its boundaries. This is going to take a fair amount"
2115 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2118 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2119 whlp_set_font(h
, 1);
2120 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2121 " to make some wrapping happen, and also to make the topicblock"
2122 " go across its boundaries. This is going to take a fair amount"
2123 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2126 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2127 whlp_set_font(h
, 1);
2128 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2129 " to make some wrapping happen, and also to make the topicblock"
2130 " go across its boundaries. This is going to take a fair amount"
2131 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2134 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2135 whlp_set_font(h
, 1);
2136 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2137 " to make some wrapping happen, and also to make the topicblock"
2138 " go across its boundaries. This is going to take a fair amount"
2139 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2142 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2143 whlp_set_font(h
, 1);
2144 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2145 " to make some wrapping happen, and also to make the topicblock"
2146 " go across its boundaries. This is going to take a fair amount"
2147 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2150 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2151 whlp_set_font(h
, 1);
2152 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2153 " to make some wrapping happen, and also to make the topicblock"
2154 " go across its boundaries. This is going to take a fair amount"
2155 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2158 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2159 whlp_set_font(h
, 1);
2160 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2161 " to make some wrapping happen, and also to make the topicblock"
2162 " go across its boundaries. This is going to take a fair amount"
2163 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2166 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2167 whlp_set_font(h
, 1);
2168 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2169 " to make some wrapping happen, and also to make the topicblock"
2170 " go across its boundaries. This is going to take a fair amount"
2171 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2174 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2175 whlp_set_font(h
, 1);
2176 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2177 " to make some wrapping happen, and also to make the topicblock"
2178 " go across its boundaries. This is going to take a fair amount"
2179 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2182 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2183 whlp_set_font(h
, 1);
2184 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2185 " to make some wrapping happen, and also to make the topicblock"
2186 " go across its boundaries. This is going to take a fair amount"
2187 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2190 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2191 whlp_set_font(h
, 1);
2192 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2193 " to make some wrapping happen, and also to make the topicblock"
2194 " go across its boundaries. This is going to take a fair amount"
2195 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2198 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2199 whlp_set_font(h
, 1);
2200 whlp_text(h
, "Now I'm going to waffle on indefinitely, in a vague attempt"
2201 " to make some wrapping happen, and also to make the topicblock"
2202 " go across its boundaries. This is going to take a fair amount"
2203 " of text, so I'll just have to cheat and c'n'p a lot of it.");
2206 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2207 whlp_set_font(h
, 1);
2208 whlp_text(h
, "Have a ");
2209 whlp_start_hyperlink(h
, t2
);
2210 whlp_text(h
, "hyperlink");
2211 whlp_end_hyperlink(h
);
2212 whlp_text(h
, " to another topic.");
2215 sprintf(mymacro
, "CBB(\"btn_up\",\"JI(`',`%s')\");EB(\"btn_up\")",
2218 whlp_begin_topic(h
, t2
, "Second Topic", mymacro
, NULL
);
2220 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2221 whlp_set_font(h
, 1);
2222 whlp_text(h
, "This topic contains no non-scrolling region. I would"
2223 " illustrate this with a ludicrously long paragraph, but that"
2224 " would get very tedious very quickly. Instead I'll just waffle"
2225 " on pointlessly for a little bit and then shut up.");
2228 whlp_set_tabstop(h
, 36, WHLP_ALIGN_LEFT
);
2229 whlp_para_attr(h
, WHLP_PARA_LEFTINDENT
, 36);
2230 whlp_para_attr(h
, WHLP_PARA_FIRSTLINEINDENT
, -36);
2231 whlp_para_attr(h
, WHLP_PARA_SPACEABOVE
, 12);
2232 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2233 whlp_set_font(h
, 1);
2234 whlp_text(h
, "\225"); /* bullet */
2236 whlp_text(h
, "This is a paragraph with a bullet. With any luck it should"
2237 " work exactly like it used to in the old NASM help file.");
2240 whlp_set_tabstop(h
, 128, WHLP_ALIGN_RIGHT
);
2241 whlp_set_tabstop(h
, 256, WHLP_ALIGN_CENTRE
);
2242 whlp_set_tabstop(h
, 384, WHLP_ALIGN_LEFT
);
2243 whlp_para_attr(h
, WHLP_PARA_SPACEABOVE
, 12);
2244 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2245 whlp_set_font(h
, 1);
2246 whlp_text(h
, "Ooh:"); whlp_tab(h
);
2247 whlp_text(h
, "Right?"); whlp_tab(h
);
2248 whlp_text(h
, "Centre?"); whlp_tab(h
);
2249 whlp_text(h
, "Left?");
2252 whlp_set_tabstop(h
, 128, WHLP_ALIGN_RIGHT
);
2253 whlp_set_tabstop(h
, 256, WHLP_ALIGN_CENTRE
);
2254 whlp_set_tabstop(h
, 384, WHLP_ALIGN_LEFT
);
2255 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2256 whlp_set_font(h
, 1);
2257 whlp_text(h
, "Aah:"); whlp_tab(h
);
2258 whlp_text(h
, "R?"); whlp_tab(h
);
2259 whlp_text(h
, "C?"); whlp_tab(h
);
2263 sprintf(mymacro
, "CBB(\"btn_up\",\"JI(`',`%s')\");EB(\"btn_up\")",
2266 whlp_begin_topic(h
, t3
, "Third Topic", mymacro
, NULL
);
2268 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
2269 whlp_set_font(h
, 1);
2270 whlp_text(h
, "This third topic is not nearly as boring as the first, "
2271 "because it has a picture: ");
2273 #ifndef PICTURE_FROM_CMDLINE
2274 const unsigned long palette
[] = {
2281 const unsigned char picture
[] = {
2282 0, 0, 0, 0, 1, 2, 3, 4,
2283 0, 0, 0, 0, 1, 2, 3, 4,
2284 0, 0, 0, 0, 1, 2, 3, 4,
2285 0, 0, 0, 1, 2, 3, 4, 4,
2286 0, 0, 0, 1, 2, 3, 4, 4,
2287 0, 0, 0, 1, 2, 3, 4, 4,
2288 0, 0, 1, 2, 3, 4, 4, 4,
2289 0, 0, 1, 2, 3, 4, 4, 4,
2290 0, 0, 1, 2, 3, 4, 4, 4,
2291 0, 1, 2, 3, 4, 4, 4, 4,
2292 0, 1, 2, 3, 4, 4, 4, 4,
2293 0, 1, 2, 3, 4, 4, 4, 4,
2295 int wid
= 8, ht
= 12;
2297 png_pixel ppalette
[256];
2298 unsigned long palette
[256];
2299 unsigned char *picture
;
2302 int plen
, i
, err
, wid
, ht
;
2305 fprintf(stderr
, "in this mode I need a .png file on the"
2309 png
= png_decode_file(argv
[1], &err
);
2311 fprintf(stderr
, "%s: PNG read error: %s\n", argv
[1],
2312 png_error_msg
[err
]);
2316 cq
= colquant_new(256, 8);
2317 colquant_data(cq
, png
->pixels
, png
->width
* png
->height
);
2318 plen
= colquant_get_palette(cq
, ppalette
);
2320 assert(plen
<= 256);
2321 for (i
= 0; i
< plen
; i
++) {
2322 palette
[i
] = ppalette
[i
].r
>> 8;
2324 palette
[i
] |= ppalette
[i
].g
>> 8;
2326 palette
[i
] |= ppalette
[i
].b
>> 8;
2328 picture
= malloc(png
->width
* png
->height
);
2329 dither_image(png
->width
, png
->height
, png
->pixels
,
2330 ppalette
, plen
, picture
);
2336 whlp_ref_picture(h
, whlp_add_picture(h
, wid
, ht
, picture
, palette
));
2343 whlp_browse_link(h
, t1
, t2
);
2344 whlp_browse_link(h
, t2
, t3
);
2349 whlp_index_term(h
, "foobarbaz", t1
);
2350 whlp_index_term(h
, "foobarbaz", t2
);
2351 whlp_index_term(h
, "foobarbaz", t3
);
2352 whlp_index_term(h
, "foobar", t1
);
2353 whlp_index_term(h
, "foobar", t2
);
2354 whlp_index_term(h
, "foobaz", t1
);
2355 whlp_index_term(h
, "foobaz", t3
);
2356 whlp_index_term(h
, "barbaz", t2
);
2357 whlp_index_term(h
, "barbaz", t3
);
2358 whlp_index_term(h
, "foo", t1
);
2359 whlp_index_term(h
, "bar", t2
);
2360 whlp_index_term(h
, "baz", t3
);
2362 whlp_close(h
, "test.hlp");