2 * Windows Help backend for Halibut
5 * - allow user to specify section contexts.
15 struct bk_whlp_state
{
18 keywordlist
*keywords
;
19 WHLP_TOPIC curr_topic
;
21 int cnt_last_level
, cnt_workaround
;
25 * Indexes of fonts in our standard font descriptor set.
37 static void whlp_rdaddwc(rdstringc
*rs
, word
*text
);
38 static int whlp_convert(wchar_t *s
, char **result
, int hard_spaces
);
39 static void whlp_mkparagraph(struct bk_whlp_state
*state
,
40 int font
, word
*text
, int subsidiary
);
41 static void whlp_navmenu(struct bk_whlp_state
*state
, paragraph
*p
);
42 static void whlp_contents_write(struct bk_whlp_state
*state
,
43 int level
, char *text
, WHLP_TOPIC topic
);
45 void whlp_backend(paragraph
*sourceform
, keywordlist
*keywords
,
48 char *filename
, *cntname
;
49 paragraph
*p
, *lastsect
;
50 struct bk_whlp_state state
;
51 WHLP_TOPIC contents_topic
;
55 filename
= "output.hlp"; /* FIXME: configurability */
56 cntname
= "output.cnt"; /* corresponding contents file */
58 state
.cntfp
= fopen(cntname
, "wb");
59 state
.cnt_last_level
= -1; state
.cnt_workaround
= 0;
61 h
= state
.h
= whlp_new();
62 state
.keywords
= keywords
;
65 whlp_start_macro(h
, "CB(\"btn_about\",\"&About\",\"About()\")");
66 whlp_start_macro(h
, "CB(\"btn_up\",\"&Up\",\"Contents()\")");
67 whlp_start_macro(h
, "BrowseButtons()");
69 whlp_create_font(h
, "Times New Roman", WHLP_FONTFAM_SERIF
, 24,
71 whlp_create_font(h
, "Times New Roman", WHLP_FONTFAM_SERIF
, 24,
72 WHLP_FONT_ITALIC
, 0, 0, 0);
73 whlp_create_font(h
, "Courier New", WHLP_FONTFAM_FIXED
, 24,
75 whlp_create_font(h
, "Arial", WHLP_FONTFAM_SERIF
, 30,
76 WHLP_FONT_BOLD
, 0, 0, 0);
77 whlp_create_font(h
, "Arial", WHLP_FONTFAM_SERIF
, 30,
78 WHLP_FONT_BOLD
|WHLP_FONT_ITALIC
, 0, 0, 0);
79 whlp_create_font(h
, "Courier New", WHLP_FONTFAM_FIXED
, 30,
80 WHLP_FONT_BOLD
, 0, 0, 0);
81 whlp_create_font(h
, "Courier New", WHLP_FONTFAM_SANS
, 18,
82 WHLP_FONT_STRIKEOUT
, 0, 0, 0);
85 * Loop over the source form finding out whether the user has
86 * specified particular help topic names for anything.
88 for (p
= sourceform
; p
; p
= p
->next
) {
89 p
->private_data
= NULL
;
90 if (p
->type
== para_Config
&& p
->parent
) {
91 if (!ustricmp(p
->keyword
, L
"winhelp-topic")) {
93 whlp_convert(uadv(p
->keyword
), &topicname
, 0);
94 /* Store the topic name in the private_data field of the
95 * containing section. */
96 p
->parent
->private_data
= topicname
;
102 * Loop over the source form registering WHLP_TOPICs for
106 contents_topic
= whlp_register_topic(h
, "Top", NULL
);
107 whlp_primary_topic(h
, contents_topic
);
108 for (p
= sourceform
; p
; p
= p
->next
) {
109 if (p
->type
== para_Chapter
||
110 p
->type
== para_Appendix
||
111 p
->type
== para_UnnumberedChapter
||
112 p
->type
== para_Heading
||
113 p
->type
== para_Subsect
) {
114 char *topicid
= p
->private_data
;
117 p
->private_data
= whlp_register_topic(h
, topicid
, &errstr
);
118 if (!p
->private_data
) {
119 p
->private_data
= whlp_register_topic(h
, NULL
, NULL
);
120 error(err_winhelp_ctxclash
, &p
->fpos
, topicid
, errstr
);
127 * Loop over the index entries, preparing final text forms for
130 for (i
= 0; (ie
= index234(idx
->entries
, i
)) != NULL
; i
++) {
131 rdstringc rs
= {0, 0, NULL
};
132 whlp_rdaddwc(&rs
, ie
->text
);
133 ie
->backend_data
= rs
.text
;
138 /* ------------------------------------------------------------------
139 * Do the contents page, containing title, preamble and
143 whlp_begin_topic(h
, contents_topic
, "Contents", "DB(\"btn_up\")", NULL
);
146 * The manual title goes in the non-scroll region, and also
147 * goes into the system title slot.
150 rdstringc rs
= {0, 0, NULL
};
151 for (p
= sourceform
; p
; p
= p
->next
) {
152 if (p
->type
== para_Title
) {
153 whlp_begin_para(h
, WHLP_PARA_NONSCROLL
);
154 whlp_mkparagraph(&state
, FONT_TITLE
, p
->words
, FALSE
);
155 whlp_rdaddwc(&rs
, p
->words
);
160 whlp_title(h
, rs
.text
);
161 fprintf(state
.cntfp
, ":Title %s\r\n", rs
.text
);
164 whlp_contents_write(&state
, 1, "Title page", contents_topic
);
165 /* FIXME: configurability in that string */
169 * Next comes the preamble, which just goes into the ordinary
172 for (p
= sourceform
; p
; p
= p
->next
) {
173 if (p
->type
== para_Preamble
) {
174 whlp_para_attr(h
, WHLP_PARA_SPACEBELOW
, 12);
175 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
176 whlp_mkparagraph(&state
, FONT_NORMAL
, p
->words
, FALSE
);
182 * The copyright goes to two places, again: into the contents
183 * page and also into the system section.
186 rdstringc rs
= {0, 0, NULL
};
187 for (p
= sourceform
; p
; p
= p
->next
) {
188 if (p
->type
== para_Copyright
) {
189 whlp_para_attr(h
, WHLP_PARA_SPACEBELOW
, 12);
190 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
191 whlp_mkparagraph(&state
, FONT_NORMAL
, p
->words
, FALSE
);
193 whlp_rdaddwc(&rs
, p
->words
);
197 whlp_copyright(h
, rs
.text
);
203 * Now do the primary navigation menu.
205 for (p
= sourceform
; p
; p
= p
->next
) {
206 if (p
->type
== para_Chapter
||
207 p
->type
== para_Appendix
||
208 p
->type
== para_UnnumberedChapter
)
209 whlp_navmenu(&state
, p
);
212 state
.curr_topic
= contents_topic
;
215 /* ------------------------------------------------------------------
216 * Now we've done the contents page, we're ready to go through
217 * and do the main manual text. Ooh.
219 for (p
= sourceform
; p
; p
= p
->next
) switch (p
->type
) {
221 * Things we ignore because we've already processed them or
222 * aren't going to touch them in this pass.
226 case para_Biblio
: /* only touch BiblioCited */
235 * Chapter and section titles: start a new Help topic.
239 case para_UnnumberedChapter
:
242 if (lastsect
&& lastsect
->child
) {
245 * Do a navigation menu for the previous section we
248 for (q
= lastsect
->child
; q
; q
= q
->sibling
)
249 whlp_navmenu(&state
, q
);
252 rdstringc rs
= {0, 0, NULL
};
253 WHLP_TOPIC new_topic
, parent_topic
;
254 char *macro
, *topicid
;
256 new_topic
= p
->private_data
;
257 whlp_browse_link(h
, state
.curr_topic
, new_topic
);
258 state
.curr_topic
= new_topic
;
261 whlp_rdaddwc(&rs
, p
->kwtext
);
262 rdaddsc(&rs
, ": "); /* FIXME: configurability */
264 whlp_rdaddwc(&rs
, p
->words
);
265 if (p
->parent
== NULL
)
266 parent_topic
= contents_topic
;
268 parent_topic
= (WHLP_TOPIC
)p
->parent
->private_data
;
269 topicid
= whlp_topic_id(parent_topic
);
270 macro
= smalloc(100+strlen(topicid
));
272 "CBB(\"btn_up\",\"JI(`',`%s')\");EB(\"btn_up\")",
274 whlp_begin_topic(h
, new_topic
,
275 rs
.text ? rs
.text
: "",
281 * Output the .cnt entry.
283 * WinHelp has a bug involving having an internal
284 * node followed by a leaf at the same level: the
285 * leaf is output at the wrong level. We can mostly
286 * work around this by modifying the leaf level
287 * itself (see whlp_contents_write), but this
288 * doesn't work for top-level sections since we
289 * can't turn a level-1 leaf into a level-0 one. So
290 * for top-level leaf sections (Bibliography
291 * springs to mind), we output an internal node
292 * containing only the leaf for that section.
297 /* Count up the level. */
299 for (q
= p
; q
->parent
; q
= q
->parent
) i
++;
301 if (p
->child
|| !p
->parent
) {
303 * If p has children then it needs to be a
304 * folder; if it has no parent then it needs to
305 * be a folder to work around the bug.
307 whlp_contents_write(&state
, i
, rs
.text
, NULL
);
310 whlp_contents_write(&state
, i
, rs
.text
, new_topic
);
315 whlp_begin_para(h
, WHLP_PARA_NONSCROLL
);
317 whlp_mkparagraph(&state
, FONT_TITLE
, p
->kwtext
, FALSE
);
318 whlp_set_font(h
, FONT_TITLE
);
319 whlp_text(h
, ": "); /* FIXME: configurability */
321 whlp_mkparagraph(&state
, FONT_TITLE
, p
->words
, FALSE
);
329 whlp_para_attr(h
, WHLP_PARA_SPACEBELOW
, 12);
330 whlp_para_attr(h
, WHLP_PARA_ALIGNMENT
, WHLP_ALIGN_CENTRE
);
331 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
332 whlp_set_font(h
, FONT_RULE
);
333 #define TEN "\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0"
334 #define TWENTY TEN TEN
335 #define FORTY TWENTY TWENTY
336 #define EIGHTY FORTY FORTY
337 whlp_text(h
, EIGHTY
);
346 case para_BiblioCited
:
348 case para_NumberedList
:
349 whlp_para_attr(h
, WHLP_PARA_SPACEBELOW
, 12);
350 if (p
->type
== para_Bullet
|| p
->type
== para_NumberedList
) {
351 whlp_para_attr(h
, WHLP_PARA_LEFTINDENT
, 72);
352 whlp_para_attr(h
, WHLP_PARA_FIRSTLINEINDENT
, -36);
353 whlp_set_tabstop(h
, 72, WHLP_ALIGN_LEFT
);
354 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
355 whlp_set_font(h
, FONT_NORMAL
);
356 if (p
->type
== para_Bullet
) {
357 whlp_text(h
, "\x95");
359 whlp_mkparagraph(&state
, FONT_NORMAL
, p
->kwtext
, FALSE
);
364 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
367 if (p
->type
== para_BiblioCited
) {
368 whlp_mkparagraph(&state
, FONT_NORMAL
, p
->kwtext
, FALSE
);
372 whlp_mkparagraph(&state
, FONT_NORMAL
, p
->words
, FALSE
);
378 * In a code paragraph, each individual word is a line. For
379 * Help files, we will have to output this as a set of
380 * paragraphs, all but the last of which don't set
386 for (w
= p
->words
; w
; w
= w
->next
) {
388 whlp_para_attr(h
, WHLP_PARA_SPACEBELOW
, 12);
389 whlp_begin_para(h
, WHLP_PARA_SCROLL
);
390 whlp_set_font(h
, FONT_CODE
);
391 whlp_convert(w
->text
, &c
, FALSE
);
401 whlp_close(h
, filename
);
404 * Loop over the index entries, cleaning up our final text
407 for (i
= 0; (ie
= index234(idx
->entries
, i
)) != NULL
; i
++) {
408 sfree(ie
->backend_data
);
412 static void whlp_contents_write(struct bk_whlp_state
*state
,
413 int level
, char *text
, WHLP_TOPIC topic
) {
415 * Horrifying bug in WinHelp. When dropping a section level or
416 * more without using a folder-type entry, WinHelp accidentally
417 * adds one to the section level. So we correct for that here.
419 if (state
->cnt_last_level
> level
&& topic
)
420 state
->cnt_workaround
= -1;
422 state
->cnt_workaround
= 0;
423 state
->cnt_last_level
= level
;
425 fprintf(state
->cntfp
, "%d ", level
+ state
->cnt_workaround
);
428 fputc('\\', state
->cntfp
);
429 fputc(*text
, state
->cntfp
);
433 fprintf(state
->cntfp
, "=%s", whlp_topic_id(topic
));
434 fputc('\n', state
->cntfp
);
437 static void whlp_navmenu(struct bk_whlp_state
*state
, paragraph
*p
) {
438 whlp_begin_para(state
->h
, WHLP_PARA_NONSCROLL
);
439 whlp_start_hyperlink(state
->h
, (WHLP_TOPIC
)p
->private_data
);
441 whlp_mkparagraph(state
, FONT_NORMAL
, p
->kwtext
, TRUE
);
442 whlp_set_font(state
->h
, FONT_NORMAL
);
443 whlp_text(state
->h
, ": "); /* FIXME: configurability */
445 whlp_mkparagraph(state
, FONT_NORMAL
, p
->words
, TRUE
);
446 whlp_end_hyperlink(state
->h
);
447 whlp_end_para(state
->h
);
451 static void whlp_mkparagraph(struct bk_whlp_state
*state
,
452 int font
, word
*text
, int subsidiary
) {
458 paragraph
*xref_target
= NULL
;
460 for (; text
; text
= text
->next
) switch (text
->type
) {
466 if (subsidiary
) break; /* disabled in subsidiary bits */
468 indextag
*tag
= index_findtag(state
->idx
, text
->text
);
472 for (i
= 0; i
< tag
->nrefs
; i
++)
473 whlp_index_term(state
->h
, tag
->refs
[i
]->backend_data
,
480 if (subsidiary
) break; /* disabled in subsidiary bits */
481 kwl
= kw_lookup(state
->keywords
, text
->text
);
482 assert(xref_target
== NULL
);
484 if (kwl
->para
->type
== para_NumberedList
) {
485 break; /* don't xref to numbered list items */
486 } else if (kwl
->para
->type
== para_BiblioCited
) {
488 * An xref to a bibliography item jumps to the section
491 if (kwl
->para
->parent
)
492 xref_target
= kwl
->para
->parent
;
496 xref_target
= kwl
->para
;
498 whlp_start_hyperlink(state
->h
,
499 (WHLP_TOPIC
)xref_target
->private_data
);
504 if (subsidiary
) break; /* disabled in subsidiary bits */
506 whlp_end_hyperlink(state
->h
);
514 case word_WhiteSpace
:
517 case word_WkCodeSpace
:
521 case word_WkCodeQuote
:
522 if (towordstyle(text
->type
) == word_Emph
)
523 newfont
= deffont
+ FONT_EMPH
;
524 else if (towordstyle(text
->type
) == word_Code
||
525 towordstyle(text
->type
) == word_WeakCode
)
526 newfont
= deffont
+ FONT_CODE
;
529 if (newfont
!= currfont
) {
531 whlp_set_font(state
->h
, newfont
);
533 if (removeattr(text
->type
) == word_Normal
) {
534 if (whlp_convert(text
->text
, &c
, TRUE
))
535 whlp_text(state
->h
, c
);
537 whlp_mkparagraph(state
, deffont
, text
->alt
, FALSE
);
539 } else if (removeattr(text
->type
) == word_WhiteSpace
) {
540 whlp_text(state
->h
, " ");
541 } else if (removeattr(text
->type
) == word_Quote
) {
543 quoteaux(text
->aux
) == quote_Open ?
"\x91" : "\x92");
544 /* FIXME: configurability */
550 static void whlp_rdaddwc(rdstringc
*rs
, word
*text
) {
553 for (; text
; text
= text
->next
) switch (text
->type
) {
566 case word_WhiteSpace
:
569 case word_WkCodeSpace
:
573 case word_WkCodeQuote
:
574 assert(text
->type
!= word_CodeQuote
&&
575 text
->type
!= word_WkCodeQuote
);
576 if (removeattr(text
->type
) == word_Normal
) {
577 if (whlp_convert(text
->text
, &c
, FALSE
))
580 whlp_rdaddwc(rs
, text
->alt
);
582 } else if (removeattr(text
->type
) == word_WhiteSpace
) {
584 } else if (removeattr(text
->type
) == word_Quote
) {
585 rdaddc(rs
, quoteaux(text
->aux
) == quote_Open ?
'\x91' : '\x92');
586 /* FIXME: configurability */
593 * Convert a wide string into a string of chars. If `result' is
594 * non-NULL, mallocs the resulting string and stores a pointer to
595 * it in `*result'. If `result' is NULL, merely checks whether all
596 * characters in the string are feasible for the output character
599 * Return is nonzero if all characters are OK. If not all
600 * characters are OK but `result' is non-NULL, a result _will_
601 * still be generated!
603 static int whlp_convert(wchar_t *s
, char **result
, int hard_spaces
) {
605 * FIXME. Currently this is ISO8859-1 only.
607 int doing
= (result
!= 0);
610 int plen
= 0, psize
= 0;
616 if ((c
>= 32 && c
<= 126) ||
617 (c
>= 160 && c
<= 255)) {
619 if (c
== 32 && hard_spaces
)
624 /* Char is not OK. */
626 outc
= 0xBF; /* approximate the good old DEC `uh?' */
631 p
= resize(p
, psize
);
637 p
= resize(p
, plen
+1);