Oops; uninitialised variable.
[sgt/halibut] / bk_whlp.c
1 /*
2 * Windows Help backend for Halibut
3 *
4 * TODO:
5 * - allow user to specify section contexts.
6 */
7
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <assert.h>
11
12 #include "halibut.h"
13 #include "winhelp.h"
14
15 struct bk_whlp_state {
16 WHLP h;
17 indexdata *idx;
18 keywordlist *keywords;
19 WHLP_TOPIC curr_topic;
20 FILE *cntfp;
21 int cnt_last_level, cnt_workaround;
22 };
23
24 /*
25 * Indexes of fonts in our standard font descriptor set.
26 */
27 enum {
28 FONT_NORMAL,
29 FONT_EMPH,
30 FONT_CODE,
31 FONT_ITAL_CODE,
32 FONT_BOLD_CODE,
33 FONT_TITLE,
34 FONT_TITLE_EMPH,
35 FONT_TITLE_CODE,
36 FONT_RULE
37 };
38
39 static void whlp_rdaddwc(rdstringc *rs, word *text);
40 static int whlp_convert(wchar_t *s, int maxlen,
41 char **result, int hard_spaces);
42 static void whlp_mkparagraph(struct bk_whlp_state *state,
43 int font, word *text, int subsidiary);
44 static void whlp_navmenu(struct bk_whlp_state *state, paragraph *p);
45 static void whlp_contents_write(struct bk_whlp_state *state,
46 int level, char *text, WHLP_TOPIC topic);
47
48 void whlp_backend(paragraph *sourceform, keywordlist *keywords,
49 indexdata *idx) {
50 WHLP h;
51 char *filename, *cntname;
52 paragraph *p, *lastsect;
53 struct bk_whlp_state state;
54 WHLP_TOPIC contents_topic;
55 int i;
56 int nesting;
57 indexentry *ie;
58 int done_contents_topic = FALSE;
59
60 filename = "output.hlp"; /* FIXME: configurability */
61 cntname = "output.cnt"; /* corresponding contents file */
62
63 state.cntfp = fopen(cntname, "wb");
64 state.cnt_last_level = -1; state.cnt_workaround = 0;
65
66 h = state.h = whlp_new();
67 state.keywords = keywords;
68 state.idx = idx;
69
70 whlp_start_macro(h, "CB(\"btn_about\",\"&About\",\"About()\")");
71 whlp_start_macro(h, "CB(\"btn_up\",\"&Up\",\"Contents()\")");
72 whlp_start_macro(h, "BrowseButtons()");
73
74 whlp_create_font(h, "Times New Roman", WHLP_FONTFAM_SERIF, 24,
75 0, 0, 0, 0);
76 whlp_create_font(h, "Times New Roman", WHLP_FONTFAM_SERIF, 24,
77 WHLP_FONT_ITALIC, 0, 0, 0);
78 whlp_create_font(h, "Courier New", WHLP_FONTFAM_FIXED, 24,
79 0, 0, 0, 0);
80 whlp_create_font(h, "Courier New", WHLP_FONTFAM_FIXED, 24,
81 WHLP_FONT_ITALIC, 0, 0, 0);
82 whlp_create_font(h, "Courier New", WHLP_FONTFAM_FIXED, 24,
83 WHLP_FONT_BOLD, 0, 0, 0);
84 whlp_create_font(h, "Arial", WHLP_FONTFAM_SERIF, 30,
85 WHLP_FONT_BOLD, 0, 0, 0);
86 whlp_create_font(h, "Arial", WHLP_FONTFAM_SERIF, 30,
87 WHLP_FONT_BOLD|WHLP_FONT_ITALIC, 0, 0, 0);
88 whlp_create_font(h, "Courier New", WHLP_FONTFAM_FIXED, 30,
89 WHLP_FONT_BOLD, 0, 0, 0);
90 whlp_create_font(h, "Courier New", WHLP_FONTFAM_SANS, 18,
91 WHLP_FONT_STRIKEOUT, 0, 0, 0);
92
93 /*
94 * Loop over the source form finding out whether the user has
95 * specified particular help topic names for anything.
96 */
97 for (p = sourceform; p; p = p->next) {
98 p->private_data = NULL;
99 if (p->type == para_Config && p->parent) {
100 if (!ustricmp(p->keyword, L"winhelp-topic")) {
101 char *topicname;
102 whlp_convert(uadv(p->keyword), 0, &topicname, 0);
103 /* Store the topic name in the private_data field of the
104 * containing section. */
105 p->parent->private_data = topicname;
106 }
107 }
108 }
109
110 /*
111 * Loop over the source form registering WHLP_TOPICs for
112 * everything.
113 */
114
115 contents_topic = whlp_register_topic(h, "Top", NULL);
116 whlp_primary_topic(h, contents_topic);
117 for (p = sourceform; p; p = p->next) {
118 if (p->type == para_Chapter ||
119 p->type == para_Appendix ||
120 p->type == para_UnnumberedChapter ||
121 p->type == para_Heading ||
122 p->type == para_Subsect) {
123 char *topicid = p->private_data;
124 char *errstr;
125
126 p->private_data = whlp_register_topic(h, topicid, &errstr);
127 if (!p->private_data) {
128 p->private_data = whlp_register_topic(h, NULL, NULL);
129 error(err_winhelp_ctxclash, &p->fpos, topicid, errstr);
130 }
131 sfree(topicid);
132 }
133 }
134
135 /*
136 * Loop over the index entries, preparing final text forms for
137 * each one.
138 */
139 for (i = 0; (ie = index234(idx->entries, i)) != NULL; i++) {
140 rdstringc rs = {0, 0, NULL};
141 whlp_rdaddwc(&rs, ie->text);
142 ie->backend_data = rs.text;
143 }
144
145 whlp_prepare(h);
146
147 /* ------------------------------------------------------------------
148 * Begin the contents page.
149 */
150
151 whlp_begin_topic(h, contents_topic, "Contents", "DB(\"btn_up\")", NULL);
152
153 /*
154 * The manual title goes in the non-scroll region, and also
155 * goes into the system title slot.
156 */
157 {
158 rdstringc rs = {0, 0, NULL};
159 for (p = sourceform; p; p = p->next) {
160 if (p->type == para_Title) {
161 whlp_begin_para(h, WHLP_PARA_NONSCROLL);
162 whlp_mkparagraph(&state, FONT_TITLE, p->words, FALSE);
163 whlp_rdaddwc(&rs, p->words);
164 whlp_end_para(h);
165 }
166 }
167 if (rs.text) {
168 whlp_title(h, rs.text);
169 fprintf(state.cntfp, ":Title %s\r\n", rs.text);
170 sfree(rs.text);
171 }
172 whlp_contents_write(&state, 1, "Title page", contents_topic);
173 /* FIXME: configurability in that string */
174 }
175
176 /*
177 * Put the copyright into the system section.
178 */
179 {
180 rdstringc rs = {0, 0, NULL};
181 for (p = sourceform; p; p = p->next) {
182 if (p->type == para_Copyright)
183 whlp_rdaddwc(&rs, p->words);
184 }
185 if (rs.text) {
186 whlp_copyright(h, rs.text);
187 sfree(rs.text);
188 }
189 }
190
191 lastsect = NULL;
192
193 /* ------------------------------------------------------------------
194 * Now we've done the contents page, we're ready to go through
195 * and do the main manual text. Ooh.
196 */
197 nesting = 0;
198 for (p = sourceform; p; p = p->next) switch (p->type) {
199 /*
200 * Things we ignore because we've already processed them or
201 * aren't going to touch them in this pass.
202 */
203 case para_IM:
204 case para_BR:
205 case para_Biblio: /* only touch BiblioCited */
206 case para_VersionID:
207 case para_NoCite:
208 case para_Title:
209 break;
210
211 case para_LcontPush:
212 case para_QuotePush:
213 nesting++;
214 break;
215 case para_LcontPop:
216 case para_QuotePop:
217 assert(nesting > 0);
218 nesting--;
219 break;
220
221 /*
222 * Chapter and section titles: start a new Help topic.
223 */
224 case para_Chapter:
225 case para_Appendix:
226 case para_UnnumberedChapter:
227 case para_Heading:
228 case para_Subsect:
229
230 if (!done_contents_topic) {
231 paragraph *p;
232
233 /*
234 * If this is the first section title we've seen, then
235 * we're currently still in the contents topic. We
236 * should therefore finish up the contents page by
237 * writing a nav menu.
238 */
239 for (p = sourceform; p; p = p->next) {
240 if (p->type == para_Chapter ||
241 p->type == para_Appendix ||
242 p->type == para_UnnumberedChapter)
243 whlp_navmenu(&state, p);
244 }
245
246 state.curr_topic = contents_topic;
247
248 done_contents_topic = TRUE;
249 }
250
251 if (lastsect && lastsect->child) {
252 paragraph *q;
253 /*
254 * Do a navigation menu for the previous section we
255 * were in.
256 */
257 for (q = lastsect->child; q; q = q->sibling)
258 whlp_navmenu(&state, q);
259 }
260 {
261 rdstringc rs = {0, 0, NULL};
262 WHLP_TOPIC new_topic, parent_topic;
263 char *macro, *topicid;
264
265 new_topic = p->private_data;
266 whlp_browse_link(h, state.curr_topic, new_topic);
267 state.curr_topic = new_topic;
268
269 if (p->kwtext) {
270 whlp_rdaddwc(&rs, p->kwtext);
271 rdaddsc(&rs, ": "); /* FIXME: configurability */
272 }
273 whlp_rdaddwc(&rs, p->words);
274 if (p->parent == NULL)
275 parent_topic = contents_topic;
276 else
277 parent_topic = (WHLP_TOPIC)p->parent->private_data;
278 topicid = whlp_topic_id(parent_topic);
279 macro = smalloc(100+strlen(topicid));
280 sprintf(macro,
281 "CBB(\"btn_up\",\"JI(`',`%s')\");EB(\"btn_up\")",
282 topicid);
283 whlp_begin_topic(h, new_topic,
284 rs.text ? rs.text : "",
285 macro, NULL);
286 sfree(macro);
287
288 {
289 /*
290 * Output the .cnt entry.
291 *
292 * WinHelp has a bug involving having an internal
293 * node followed by a leaf at the same level: the
294 * leaf is output at the wrong level. We can mostly
295 * work around this by modifying the leaf level
296 * itself (see whlp_contents_write), but this
297 * doesn't work for top-level sections since we
298 * can't turn a level-1 leaf into a level-0 one. So
299 * for top-level leaf sections (Bibliography
300 * springs to mind), we output an internal node
301 * containing only the leaf for that section.
302 */
303 int i;
304 paragraph *q;
305
306 /* Count up the level. */
307 i = 1;
308 for (q = p; q->parent; q = q->parent) i++;
309
310 if (p->child || !p->parent) {
311 /*
312 * If p has children then it needs to be a
313 * folder; if it has no parent then it needs to
314 * be a folder to work around the bug.
315 */
316 whlp_contents_write(&state, i, rs.text, NULL);
317 i++;
318 }
319 whlp_contents_write(&state, i, rs.text, new_topic);
320 }
321
322 sfree(rs.text);
323
324 whlp_begin_para(h, WHLP_PARA_NONSCROLL);
325 if (p->kwtext) {
326 whlp_mkparagraph(&state, FONT_TITLE, p->kwtext, FALSE);
327 whlp_set_font(h, FONT_TITLE);
328 whlp_text(h, ": "); /* FIXME: configurability */
329 }
330 whlp_mkparagraph(&state, FONT_TITLE, p->words, FALSE);
331 whlp_end_para(h);
332
333 lastsect = p;
334 }
335 break;
336
337 case para_Rule:
338 whlp_para_attr(h, WHLP_PARA_SPACEBELOW, 12);
339 whlp_para_attr(h, WHLP_PARA_ALIGNMENT, WHLP_ALIGN_CENTRE);
340 whlp_begin_para(h, WHLP_PARA_SCROLL);
341 whlp_set_font(h, FONT_RULE);
342 #define TEN "\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0"
343 #define TWENTY TEN TEN
344 #define FORTY TWENTY TWENTY
345 #define EIGHTY FORTY FORTY
346 whlp_text(h, EIGHTY);
347 #undef TEN
348 #undef TWENTY
349 #undef FORTY
350 #undef EIGHTY
351 whlp_end_para(h);
352 break;
353
354 case para_Normal:
355 case para_Copyright:
356 case para_DescribedThing:
357 case para_Description:
358 case para_BiblioCited:
359 case para_Bullet:
360 case para_NumberedList:
361 whlp_para_attr(h, WHLP_PARA_SPACEBELOW, 12);
362 if (p->type == para_Bullet || p->type == para_NumberedList) {
363 whlp_para_attr(h, WHLP_PARA_LEFTINDENT, 72*nesting + 72);
364 whlp_para_attr(h, WHLP_PARA_FIRSTLINEINDENT, -36);
365 whlp_set_tabstop(h, 72, WHLP_ALIGN_LEFT);
366 whlp_begin_para(h, WHLP_PARA_SCROLL);
367 whlp_set_font(h, FONT_NORMAL);
368 if (p->type == para_Bullet) {
369 whlp_text(h, "\x95");
370 } else {
371 whlp_mkparagraph(&state, FONT_NORMAL, p->kwtext, FALSE);
372 whlp_text(h, ".");
373 }
374 whlp_tab(h);
375 } else {
376 whlp_para_attr(h, WHLP_PARA_LEFTINDENT,
377 72*nesting + (p->type==para_Description ? 72 : 0));
378 whlp_begin_para(h, WHLP_PARA_SCROLL);
379 }
380
381 if (p->type == para_BiblioCited) {
382 whlp_mkparagraph(&state, FONT_NORMAL, p->kwtext, FALSE);
383 whlp_text(h, " ");
384 }
385
386 whlp_mkparagraph(&state, FONT_NORMAL, p->words, FALSE);
387 whlp_end_para(h);
388 break;
389
390 case para_Code:
391 /*
392 * In a code paragraph, each individual word is a line. For
393 * Help files, we will have to output this as a set of
394 * paragraphs, all but the last of which don't set
395 * SPACEBELOW.
396 */
397 {
398 word *w;
399 wchar_t *t, *e;
400 char *c;
401
402 for (w = p->words; w; w = w->next) if (w->type == word_WeakCode) {
403 t = w->text;
404 if (w->next && w->next->type == word_Emph) {
405 w = w->next;
406 e = w->text;
407 } else
408 e = NULL;
409
410 if (!w->next)
411 whlp_para_attr(h, WHLP_PARA_SPACEBELOW, 12);
412
413 whlp_para_attr(h, WHLP_PARA_LEFTINDENT, 72*nesting);
414 whlp_begin_para(h, WHLP_PARA_SCROLL);
415 while (e && *e && *t) {
416 int n;
417 int ec = *e;
418
419 for (n = 0; t[n] && e[n] && e[n] == ec; n++);
420 if (ec == 'i')
421 whlp_set_font(h, FONT_ITAL_CODE);
422 else if (ec == 'b')
423 whlp_set_font(h, FONT_BOLD_CODE);
424 else
425 whlp_set_font(h, FONT_CODE);
426 whlp_convert(t, n, &c, FALSE);
427 whlp_text(h, c);
428 sfree(c);
429 t += n;
430 e += n;
431 }
432 whlp_set_font(h, FONT_CODE);
433 whlp_convert(t, 0, &c, FALSE);
434 whlp_text(h, c);
435 sfree(c);
436 whlp_end_para(h);
437 }
438 }
439 break;
440 }
441
442 fclose(state.cntfp);
443 whlp_close(h, filename);
444
445 /*
446 * Loop over the index entries, cleaning up our final text
447 * forms.
448 */
449 for (i = 0; (ie = index234(idx->entries, i)) != NULL; i++) {
450 sfree(ie->backend_data);
451 }
452 }
453
454 static void whlp_contents_write(struct bk_whlp_state *state,
455 int level, char *text, WHLP_TOPIC topic) {
456 /*
457 * Horrifying bug in WinHelp. When dropping a section level or
458 * more without using a folder-type entry, WinHelp accidentally
459 * adds one to the section level. So we correct for that here.
460 */
461 if (state->cnt_last_level > level && topic)
462 state->cnt_workaround = -1;
463 else if (!topic)
464 state->cnt_workaround = 0;
465 state->cnt_last_level = level;
466
467 fprintf(state->cntfp, "%d ", level + state->cnt_workaround);
468 while (*text) {
469 if (*text == '=')
470 fputc('\\', state->cntfp);
471 fputc(*text, state->cntfp);
472 text++;
473 }
474 if (topic)
475 fprintf(state->cntfp, "=%s", whlp_topic_id(topic));
476 fputc('\n', state->cntfp);
477 }
478
479 static void whlp_navmenu(struct bk_whlp_state *state, paragraph *p) {
480 whlp_begin_para(state->h, WHLP_PARA_NONSCROLL);
481 whlp_start_hyperlink(state->h, (WHLP_TOPIC)p->private_data);
482 if (p->kwtext) {
483 whlp_mkparagraph(state, FONT_NORMAL, p->kwtext, TRUE);
484 whlp_set_font(state->h, FONT_NORMAL);
485 whlp_text(state->h, ": "); /* FIXME: configurability */
486 }
487 whlp_mkparagraph(state, FONT_NORMAL, p->words, TRUE);
488 whlp_end_hyperlink(state->h);
489 whlp_end_para(state->h);
490
491 }
492
493 static void whlp_mkparagraph(struct bk_whlp_state *state,
494 int font, word *text, int subsidiary) {
495 keyword *kwl;
496 int deffont = font;
497 int currfont = -1;
498 int newfont;
499 char *c;
500 paragraph *xref_target = NULL;
501
502 for (; text; text = text->next) switch (text->type) {
503 case word_HyperLink:
504 case word_HyperEnd:
505 break;
506
507 case word_IndexRef:
508 if (subsidiary) break; /* disabled in subsidiary bits */
509 {
510 indextag *tag = index_findtag(state->idx, text->text);
511 int i;
512 if (!tag)
513 break;
514 for (i = 0; i < tag->nrefs; i++)
515 whlp_index_term(state->h, tag->refs[i]->backend_data,
516 state->curr_topic);
517 }
518 break;
519
520 case word_UpperXref:
521 case word_LowerXref:
522 if (subsidiary) break; /* disabled in subsidiary bits */
523 kwl = kw_lookup(state->keywords, text->text);
524 assert(xref_target == NULL);
525 if (kwl) {
526 if (kwl->para->type == para_NumberedList) {
527 break; /* don't xref to numbered list items */
528 } else if (kwl->para->type == para_BiblioCited) {
529 /*
530 * An xref to a bibliography item jumps to the section
531 * containing it.
532 */
533 if (kwl->para->parent)
534 xref_target = kwl->para->parent;
535 else
536 break;
537 } else {
538 xref_target = kwl->para;
539 }
540 whlp_start_hyperlink(state->h,
541 (WHLP_TOPIC)xref_target->private_data);
542 }
543 break;
544
545 case word_XrefEnd:
546 if (subsidiary) break; /* disabled in subsidiary bits */
547 if (xref_target)
548 whlp_end_hyperlink(state->h);
549 xref_target = NULL;
550 break;
551
552 case word_Normal:
553 case word_Emph:
554 case word_Code:
555 case word_WeakCode:
556 case word_WhiteSpace:
557 case word_EmphSpace:
558 case word_CodeSpace:
559 case word_WkCodeSpace:
560 case word_Quote:
561 case word_EmphQuote:
562 case word_CodeQuote:
563 case word_WkCodeQuote:
564 if (towordstyle(text->type) == word_Emph)
565 newfont = deffont + FONT_EMPH;
566 else if (towordstyle(text->type) == word_Code ||
567 towordstyle(text->type) == word_WeakCode)
568 newfont = deffont + FONT_CODE;
569 else
570 newfont = deffont;
571 if (newfont != currfont) {
572 currfont = newfont;
573 whlp_set_font(state->h, newfont);
574 }
575 if (removeattr(text->type) == word_Normal) {
576 if (whlp_convert(text->text, 0, &c, TRUE))
577 whlp_text(state->h, c);
578 else
579 whlp_mkparagraph(state, deffont, text->alt, FALSE);
580 sfree(c);
581 } else if (removeattr(text->type) == word_WhiteSpace) {
582 whlp_text(state->h, " ");
583 } else if (removeattr(text->type) == word_Quote) {
584 whlp_text(state->h,
585 quoteaux(text->aux) == quote_Open ? "\x91" : "\x92");
586 /* FIXME: configurability */
587 }
588 break;
589 }
590 }
591
592 static void whlp_rdaddwc(rdstringc *rs, word *text) {
593 char *c;
594
595 for (; text; text = text->next) switch (text->type) {
596 case word_HyperLink:
597 case word_HyperEnd:
598 case word_UpperXref:
599 case word_LowerXref:
600 case word_XrefEnd:
601 case word_IndexRef:
602 break;
603
604 case word_Normal:
605 case word_Emph:
606 case word_Code:
607 case word_WeakCode:
608 case word_WhiteSpace:
609 case word_EmphSpace:
610 case word_CodeSpace:
611 case word_WkCodeSpace:
612 case word_Quote:
613 case word_EmphQuote:
614 case word_CodeQuote:
615 case word_WkCodeQuote:
616 assert(text->type != word_CodeQuote &&
617 text->type != word_WkCodeQuote);
618 if (removeattr(text->type) == word_Normal) {
619 if (whlp_convert(text->text, 0, &c, FALSE))
620 rdaddsc(rs, c);
621 else
622 whlp_rdaddwc(rs, text->alt);
623 sfree(c);
624 } else if (removeattr(text->type) == word_WhiteSpace) {
625 rdaddc(rs, ' ');
626 } else if (removeattr(text->type) == word_Quote) {
627 rdaddc(rs, quoteaux(text->aux) == quote_Open ? '\x91' : '\x92');
628 /* FIXME: configurability */
629 }
630 break;
631 }
632 }
633
634 /*
635 * Convert a wide string into a string of chars. If `result' is
636 * non-NULL, mallocs the resulting string and stores a pointer to
637 * it in `*result'. If `result' is NULL, merely checks whether all
638 * characters in the string are feasible for the output character
639 * set.
640 *
641 * Return is nonzero if all characters are OK. If not all
642 * characters are OK but `result' is non-NULL, a result _will_
643 * still be generated!
644 */
645 static int whlp_convert(wchar_t *s, int maxlen,
646 char **result, int hard_spaces) {
647 /*
648 * FIXME. Currently this is ISO8859-1 only.
649 */
650 int doing = (result != 0);
651 int ok = TRUE;
652 char *p = NULL;
653 int plen = 0, psize = 0;
654
655 if (maxlen <= 0)
656 maxlen = -1;
657
658 for (; *s && maxlen != 0; s++, maxlen--) {
659 wchar_t c = *s;
660 char outc;
661
662 if ((c >= 32 && c <= 126) ||
663 (c >= 160 && c <= 255)) {
664 /* Char is OK. */
665 if (c == 32 && hard_spaces)
666 outc = '\240';
667 else
668 outc = (char)c;
669 } else {
670 /* Char is not OK. */
671 ok = FALSE;
672 outc = 0xBF; /* approximate the good old DEC `uh?' */
673 }
674 if (doing) {
675 if (plen >= psize) {
676 psize = plen + 256;
677 p = resize(p, psize);
678 }
679 p[plen++] = outc;
680 }
681 }
682 if (doing) {
683 p = resize(p, plen+1);
684 p[plen] = '\0';
685 *result = p;
686 }
687 return ok;
688 }