1 /*
2 * Windows Help backend for Halibut
3 */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <ctype.h>
8 #include <assert.h>
9
10 #include "halibut.h"
11 #include "winhelp.h"
12
13 struct bk_whlp_state {
14 WHLP h;
15 indexdata *idx;
16 keywordlist *keywords;
17 WHLP_TOPIC curr_topic;
18 int charset;
19 charset_state cstate;
20 FILE *cntfp;
21 int cnt_last_level, cnt_workaround;
22 };
23
24 typedef struct {
25 int charset;
26 wchar_t *bullet, *lquote, *rquote, *titlepage, *sectsuffix, *listsuffix;
27 wchar_t *contents_text;
28 char *filename;
29 } whlpconf;
30
31 /*
32 * Indexes of fonts in our standard font descriptor set.
33 */
34 enum {
35 FONT_NORMAL,
36 FONT_EMPH,
37 FONT_STRONG,
38 FONT_CODE,
39 FONT_ITAL_CODE,
40 FONT_BOLD_CODE,
41 FONT_TITLE,
42 FONT_TITLE_EMPH,
43 FONT_TITLE_STRONG,
44 FONT_TITLE_CODE,
45 FONT_RULE
46 };
47
48 static void whlp_rdaddwc(rdstringc *rs, word *text, whlpconf *conf,
49 charset_state *state);
50 static void whlp_rdadds(rdstringc *rs, const wchar_t *text, whlpconf *conf,
51 charset_state *state);
52 static void whlp_mkparagraph(struct bk_whlp_state *state,
53 int font, word *text, int subsidiary,
54 whlpconf *conf);
55 static void whlp_navmenu(struct bk_whlp_state *state, paragraph *p,
56 whlpconf *conf);
57 static void whlp_contents_write(struct bk_whlp_state *state,
58 int level, char *text, WHLP_TOPIC topic);
59 static void whlp_wtext(struct bk_whlp_state *state, const wchar_t *text);
60
whlp_config_filename(char * filename)61 paragraph *whlp_config_filename(char *filename)
62 {
63 return cmdline_cfg_simple("winhelp-filename", filename, NULL);
64 }
65
whlp_configure(paragraph * source)66 static whlpconf whlp_configure(paragraph *source) {
67 paragraph *p;
68 whlpconf ret;
69
70 /*
71 * Defaults.
72 */
73 ret.charset = CS_CP1252;
74 ret.bullet = L"\x2022\0-\0\0";
75 ret.lquote = L"\x2018\0\x2019\0\"\0\"\0\0";
76 ret.rquote = uadv(ret.lquote);
77 ret.filename = dupstr("output.hlp");
78 ret.titlepage = L"Title page";
79 ret.contents_text = L"Contents";
80 ret.sectsuffix = L": ";
81 ret.listsuffix = L".";
82
83 /*
84 * Two-pass configuration so that we can pick up global config
85 * (e.g. `quotes') before having it overridden by specific
86 * config (`win-quotes'), irrespective of the order in which
87 * they occur.
88 */
89 for (p = source; p; p = p->next) {
90 if (p->type == para_Config) {
91 if (!ustricmp(p->keyword, L"quotes")) {
92 if (*uadv(p->keyword) && *uadv(uadv(p->keyword))) {
93 ret.lquote = uadv(p->keyword);
94 ret.rquote = uadv(ret.lquote);
95 }
96 }
97 }
98 }
99
100 for (p = source; p; p = p->next) {
101 p->private_data = NULL;
102 if (p->type == para_Config) {
103 /*
104 * In principle we should support a `winhelp-charset'
105 * here. We don't, because my WinHelp output code
106 * doesn't know how to change character set. Once I
107 * find out, I'll support it.
108 */
109 if (p->parent && !ustricmp(p->keyword, L"winhelp-topic")) {
110 /* Store the topic name in the private_data field of the
111 * containing section. */
112 p->parent->private_data = uadv(p->keyword);
113 } else if (!ustricmp(p->keyword, L"winhelp-filename")) {
114 sfree(ret.filename);
115 ret.filename = dupstr(adv(p->origkeyword));
116 } else if (!ustricmp(p->keyword, L"winhelp-bullet")) {
117 ret.bullet = uadv(p->keyword);
118 } else if (!ustricmp(p->keyword, L"winhelp-section-suffix")) {
119 ret.sectsuffix = uadv(p->keyword);
120 } else if (!ustricmp(p->keyword, L"winhelp-list-suffix")) {
121 ret.listsuffix = uadv(p->keyword);
122 } else if (!ustricmp(p->keyword, L"winhelp-contents-titlepage")) {
123 ret.titlepage = uadv(p->keyword);
124 } else if (!ustricmp(p->keyword, L"winhelp-quotes")) {
125 if (*uadv(p->keyword) && *uadv(uadv(p->keyword))) {
126 ret.lquote = uadv(p->keyword);
127 ret.rquote = uadv(ret.lquote);
128 }
129 } else if (!ustricmp(p->keyword, L"contents")) {
130 ret.contents_text = uadv(p->keyword);
131 }
132 }
133 }
134
135 /*
136 * Now process fallbacks on quote characters and bullets.
137 */
138 while (*uadv(ret.rquote) && *uadv(uadv(ret.rquote)) &&
139 (!cvt_ok(ret.charset, ret.lquote) ||
140 !cvt_ok(ret.charset, ret.rquote))) {
141 ret.lquote = uadv(ret.rquote);
142 ret.rquote = uadv(ret.lquote);
143 }
144
145 while (*ret.bullet && *uadv(ret.bullet) &&
146 !cvt_ok(ret.charset, ret.bullet))
147 ret.bullet = uadv(ret.bullet);
148
149 return ret;
150 }
151
whlp_backend(paragraph * sourceform,keywordlist * keywords,indexdata * idx,void * unused)152 void whlp_backend(paragraph *sourceform, keywordlist *keywords,
153 indexdata *idx, void *unused) {
154 WHLP h;
155 char *cntname;
156 paragraph *p, *lastsect;
157 struct bk_whlp_state state;
158 WHLP_TOPIC contents_topic;
159 int i;
160 int nesting;
161 indexentry *ie;
162 int done_contents_topic = FALSE;
163 whlpconf conf;
164
165 IGNORE(unused);
166
167 h = state.h = whlp_new();
168 state.keywords = keywords;
169 state.idx = idx;
170
171 whlp_start_macro(h, "CB(\"btn_about\",\"&About\",\"About()\")");
172 whlp_start_macro(h, "CB(\"btn_up\",\"&Up\",\"Contents()\")");
173 whlp_start_macro(h, "BrowseButtons()");
174
175 whlp_create_font(h, "Times New Roman", WHLP_FONTFAM_SERIF, 24,
176 0, 0, 0, 0);
177 whlp_create_font(h, "Times New Roman", WHLP_FONTFAM_SERIF, 24,
178 WHLP_FONT_ITALIC, 0, 0, 0);
179 whlp_create_font(h, "Times New Roman", WHLP_FONTFAM_SERIF, 24,
180 WHLP_FONT_BOLD, 0, 0, 0);
181 whlp_create_font(h, "Courier New", WHLP_FONTFAM_FIXED, 24,
182 0, 0, 0, 0);
183 whlp_create_font(h, "Courier New", WHLP_FONTFAM_FIXED, 24,
184 WHLP_FONT_ITALIC, 0, 0, 0);
185 whlp_create_font(h, "Courier New", WHLP_FONTFAM_FIXED, 24,
186 WHLP_FONT_BOLD, 0, 0, 0);
187 whlp_create_font(h, "Arial", WHLP_FONTFAM_SERIF, 30,
188 WHLP_FONT_BOLD, 0, 0, 0);
189 whlp_create_font(h, "Arial", WHLP_FONTFAM_SERIF, 30,
190 WHLP_FONT_BOLD|WHLP_FONT_ITALIC, 0, 0, 0);
191 whlp_create_font(h, "Arial", WHLP_FONTFAM_SERIF, 30,
192 WHLP_FONT_BOLD, 0, 0, 0);
193 whlp_create_font(h, "Courier New", WHLP_FONTFAM_FIXED, 30,
194 WHLP_FONT_BOLD, 0, 0, 0);
195 whlp_create_font(h, "Courier New", WHLP_FONTFAM_SANS, 18,
196 WHLP_FONT_STRIKEOUT, 0, 0, 0);
197
198 conf = whlp_configure(sourceform);
199
200 state.charset = conf.charset;
201
202 /*
203 * Ensure the output file name has a .hlp extension. This is
204 * required since we must create the .cnt file in parallel with
205 * it.
206 */
207 {
208 int len = strlen(conf.filename);
209 if (len < 4 || conf.filename[len-4] != '.' ||
210 tolower(conf.filename[len-3] != 'h') ||
211 tolower(conf.filename[len-2] != 'l') ||
212 tolower(conf.filename[len-1] != 'p')) {
213 char *newf;
214 newf = snewn(len + 5, char);
215 sprintf(newf, "%s.hlp", conf.filename);
216 sfree(conf.filename);
217 conf.filename = newf;
218 len = strlen(newf);
219 }
220 cntname = snewn(len+1, char);
221 sprintf(cntname, "%.*s.cnt", len-4, conf.filename);
222 }
223
224 state.cntfp = fopen(cntname, "wb");
225 if (!state.cntfp) {
226 err_cantopenw(cntname);
227 return;
228 }
229 state.cnt_last_level = -1; state.cnt_workaround = 0;
230
231 /*
232 * Loop over the source form registering WHLP_TOPICs for
233 * everything.
234 */
235
236 contents_topic = whlp_register_topic(h, "Top", NULL);
237 whlp_primary_topic(h, contents_topic);
238 for (p = sourceform; p; p = p->next) {
239 if (p->type == para_Chapter ||
240 p->type == para_Appendix ||
241 p->type == para_UnnumberedChapter ||
242 p->type == para_Heading ||
243 p->type == para_Subsect) {
244
245 rdstringc rs = { 0, 0, NULL };
246 char *errstr;
247
248 whlp_rdadds(&rs, (wchar_t *)p->private_data, &conf, NULL);
249
250 p->private_data = whlp_register_topic(h, rs.text, &errstr);
251 if (!p->private_data) {
252 p->private_data = whlp_register_topic(h, NULL, NULL);
253 err_winhelp_ctxclash(&p->fpos, rs.text, errstr);
254 }
255 sfree(rs.text);
256 }
257 }
258
259 /*
260 * Loop over the index entries, preparing final text forms for
261 * each one.
262 */
263 {
264 indexentry *ie_prev = NULL;
265 int nspaces = 1;
266
267 for (i = 0; (ie = index234(idx->entries, i)) != NULL; i++) {
268 rdstringc rs = {0, 0, NULL};
269 charset_state state = CHARSET_INIT_STATE;
270 whlp_rdaddwc(&rs, ie->text, &conf, &state);
271
272 if (ie_prev) {
273 /*
274 * It appears that Windows Help's index mechanism
275 * is inherently case-insensitive. Therefore, if two
276 * adjacent index terms compare equal apart from
277 * case, I'm going to append nonbreaking spaces to
278 * the end of the second one so that Windows will
279 * treat them as distinct.
280 *
281 * This is nasty because we're depending on our
282 * case-insensitive comparison having the same
283 * semantics as the Windows one :-/ but I see no
284 * alternative.
285 */
286 wchar_t *a, *b;
287
288 a = ufroma_dup((char *)ie_prev->backend_data, conf.charset);
289 b = ufroma_dup(rs.text, conf.charset);
290 if (!ustricmp(a, b)) {
291 int j;
292 for (j = 0; j < nspaces; j++)
293 whlp_rdadds(&rs, L"\xA0", &conf, &state);
294 /*
295 * Add one to nspaces, so that if another term
296 * appears which is equivalent to the previous
297 * two it'll acquire one more space.
298 */
299 nspaces++;
300 } else
301 nspaces = 1;
302 sfree(a);
303 sfree(b);
304 }
305
306 whlp_rdadds(&rs, NULL, &conf, &state);
307
308 ie->backend_data = rs.text;
309
310 /*
311 * Only move ie_prev on if nspaces==1 (since when we
312 * have three or more adjacent terms differing only in
313 * case, we will want to compare with the _first_ of
314 * them because that won't have had any extra spaces
315 * added on which will foul up the comparison).
316 */
317 if (nspaces == 1)
318 ie_prev = ie;
319 }
320 }
321
322 whlp_prepare(h);
323
324 /* ------------------------------------------------------------------
325 * Begin the contents page.
326 */
327 {
328 rdstringc rs = {0, 0, NULL};
329 whlp_rdadds(&rs, conf.contents_text, &conf, NULL);
330 whlp_begin_topic(h, contents_topic, rs.text, "DB(\"btn_up\")", NULL);
331 state.curr_topic = contents_topic;
332 sfree(rs.text);
333 }
334
335 /*
336 * The manual title goes in the non-scroll region, and also
337 * goes into the system title slot.
338 */
339 {
340 rdstringc rs = {0, 0, NULL};
341 for (p = sourceform; p; p = p->next) {
342 if (p->type == para_Title) {
343 whlp_begin_para(h, WHLP_PARA_NONSCROLL);
344 state.cstate = charset_init_state;
345 whlp_mkparagraph(&state, FONT_TITLE, p->words, FALSE, &conf);
346 whlp_wtext(&state, NULL);
347 whlp_end_para(h);
348 whlp_rdaddwc(&rs, p->words, &conf, NULL);
349 }
350 }
351 if (rs.text) {
352 whlp_title(h, rs.text);
353 fprintf(state.cntfp, ":Title %s\r\n", rs.text);
354 sfree(rs.text);
355 }
356 {
357 rdstringc rs2 = {0,0,NULL};
358 whlp_rdadds(&rs2, conf.titlepage, &conf, NULL);
359 whlp_contents_write(&state, 1, rs2.text, contents_topic);
360 sfree(rs2.text);
361 }
362 }
363
364 /*
365 * Put the copyright into the system section.
366 */
367 {
368 rdstringc rs = {0, 0, NULL};
369 for (p = sourceform; p; p = p->next) {
370 if (p->type == para_Copyright)
371 whlp_rdaddwc(&rs, p->words, &conf, NULL);
372 }
373 if (rs.text) {
374 whlp_copyright(h, rs.text);
375 sfree(rs.text);
376 }
377 }
378
379 lastsect = NULL;
380
381 /* ------------------------------------------------------------------
382 * Now we've done the contents page, we're ready to go through
383 * and do the main manual text. Ooh.
384 */
385 nesting = 0;
386 for (p = sourceform; p; p = p->next) switch (p->type) {
387 /*
388 * Things we ignore because we've already processed them or
389 * aren't going to touch them in this pass.
390 */
391 case para_IM:
392 case para_BR:
393 case para_Biblio: /* only touch BiblioCited */
394 case para_VersionID:
395 case para_NoCite:
396 case para_Title:
397 break;
398
399 case para_LcontPush:
400 case para_QuotePush:
401 nesting++;
402 break;
403 case para_LcontPop:
404 case para_QuotePop:
405 assert(nesting > 0);
406 nesting--;
407 break;
408
409 /*
410 * Chapter and section titles: start a new Help topic.
411 */
412 case para_Chapter:
413 case para_Appendix:
414 case para_UnnumberedChapter:
415 case para_Heading:
416 case para_Subsect:
417
418 if (!done_contents_topic) {
419 paragraph *p;
420
421 /*
422 * If this is the first section title we've seen, then
423 * we're currently still in the contents topic. We
424 * should therefore finish up the contents page by
425 * writing a nav menu.
426 */
427 for (p = sourceform; p; p = p->next) {
428 if (p->type == para_Chapter ||
429 p->type == para_Appendix ||
430 p->type == para_UnnumberedChapter)
431 whlp_navmenu(&state, p, &conf);
432 }
433
434 done_contents_topic = TRUE;
435 }
436
437 if (lastsect && lastsect->child) {
438 paragraph *q;
439 /*
440 * Do a navigation menu for the previous section we
441 * were in.
442 */
443 for (q = lastsect->child; q; q = q->sibling)
444 whlp_navmenu(&state, q, &conf);
445 }
446 {
447 rdstringc rs = {0, 0, NULL};
448 WHLP_TOPIC new_topic, parent_topic;
449 char *macro, *topicid;
450 charset_state cstate = CHARSET_INIT_STATE;
451
452 new_topic = p->private_data;
453 whlp_browse_link(h, state.curr_topic, new_topic);
454 state.curr_topic = new_topic;
455
456 if (p->kwtext) {
457 whlp_rdaddwc(&rs, p->kwtext, &conf, &cstate);
458 whlp_rdadds(&rs, conf.sectsuffix, &conf, &cstate);
459 }
460 whlp_rdaddwc(&rs, p->words, &conf, &cstate);
461 whlp_rdadds(&rs, NULL, &conf, &cstate);
462
463 if (p->parent == NULL)
464 parent_topic = contents_topic;
465 else
466 parent_topic = (WHLP_TOPIC)p->parent->private_data;
467 topicid = whlp_topic_id(parent_topic);
468 macro = smalloc(100+strlen(topicid));
469 sprintf(macro,
470 "CBB(\"btn_up\",\"JI(`',`%s')\");EB(\"btn_up\")",
471 topicid);
472 whlp_begin_topic(h, new_topic,
473 rs.text ? rs.text : "",
474 macro, NULL);
475 sfree(macro);
476
477 {
478 /*
479 * Output the .cnt entry.
480 *
481 * WinHelp has a bug involving having an internal
482 * node followed by a leaf at the same level: the
483 * leaf is output at the wrong level. We can mostly
484 * work around this by modifying the leaf level
485 * itself (see whlp_contents_write), but this
486 * doesn't work for top-level sections since we
487 * can't turn a level-1 leaf into a level-0 one. So
488 * for top-level leaf sections (Bibliography
489 * springs to mind), we output an internal node
490 * containing only the leaf for that section.
491 */
492 int i;
493 paragraph *q;
494
495 /* Count up the level. */
496 i = 1;
497 for (q = p; q->parent; q = q->parent) i++;
498
499 if (p->child || !p->parent) {
500 /*
501 * If p has children then it needs to be a
502 * folder; if it has no parent then it needs to
503 * be a folder to work around the bug.
504 */
505 whlp_contents_write(&state, i, rs.text, NULL);
506 i++;
507 }
508 whlp_contents_write(&state, i, rs.text, new_topic);
509 }
510
511 sfree(rs.text);
512
513 whlp_begin_para(h, WHLP_PARA_NONSCROLL);
514 state.cstate = charset_init_state;
515 if (p->kwtext) {
516 whlp_mkparagraph(&state, FONT_TITLE, p->kwtext, FALSE, &conf);
517 whlp_set_font(h, FONT_TITLE);
518 whlp_wtext(&state, conf.sectsuffix);
519 }
520 whlp_mkparagraph(&state, FONT_TITLE, p->words, FALSE, &conf);
521 whlp_wtext(&state, NULL);
522 whlp_end_para(h);
523
524 lastsect = p;
525 }
526 break;
527
528 case para_Rule:
529 whlp_para_attr(h, WHLP_PARA_SPACEBELOW, 12);
530 whlp_para_attr(h, WHLP_PARA_ALIGNMENT, WHLP_ALIGN_CENTRE);
531 whlp_begin_para(h, WHLP_PARA_SCROLL);
532 whlp_set_font(h, FONT_RULE);
533 state.cstate = charset_init_state;
534 #define TEN L"\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0"
535 #define TWENTY TEN TEN
536 #define FORTY TWENTY TWENTY
537 #define EIGHTY FORTY FORTY
538 state.cstate = charset_init_state;
539 whlp_wtext(&state, EIGHTY);
540 whlp_wtext(&state, NULL);
541 #undef TEN
542 #undef TWENTY
543 #undef FORTY
544 #undef EIGHTY
545 whlp_end_para(h);
546 break;
547
548 case para_Normal:
549 case para_Copyright:
550 case para_DescribedThing:
551 case para_Description:
552 case para_BiblioCited:
553 case para_Bullet:
554 case para_NumberedList:
555 whlp_para_attr(h, WHLP_PARA_SPACEBELOW, 12);
556 if (p->type == para_Bullet || p->type == para_NumberedList) {
557 whlp_para_attr(h, WHLP_PARA_LEFTINDENT, 72*nesting + 72);
558 whlp_para_attr(h, WHLP_PARA_FIRSTLINEINDENT, -36);
559 whlp_set_tabstop(h, 72, WHLP_ALIGN_LEFT);
560 whlp_begin_para(h, WHLP_PARA_SCROLL);
561 whlp_set_font(h, FONT_NORMAL);
562 state.cstate = charset_init_state;
563 if (p->type == para_Bullet) {
564 whlp_wtext(&state, conf.bullet);
565 } else {
566 whlp_mkparagraph(&state, FONT_NORMAL, p->kwtext, FALSE, &conf);
567 whlp_wtext(&state, conf.listsuffix);
568 }
569 whlp_wtext(&state, NULL);
570 whlp_tab(h);
571 } else {
572 whlp_para_attr(h, WHLP_PARA_LEFTINDENT,
573 72*nesting + (p->type==para_Description ? 72 : 0));
574 whlp_begin_para(h, WHLP_PARA_SCROLL);
575 }
576
577 state.cstate = charset_init_state;
578
579 if (p->type == para_BiblioCited) {
580 whlp_mkparagraph(&state, FONT_NORMAL, p->kwtext, FALSE, &conf);
581 whlp_wtext(&state, L" ");
582 }
583
584 whlp_mkparagraph(&state, FONT_NORMAL, p->words, FALSE, &conf);
585 whlp_wtext(&state, NULL);
586 whlp_end_para(h);
587 break;
588
589 case para_Code:
590 /*
591 * In a code paragraph, each individual word is a line. For
592 * Help files, we will have to output this as a set of
593 * paragraphs, all but the last of which don't set
594 * SPACEBELOW.
595 */
596 {
597 word *w;
598 wchar_t *t, *e, *tmp;
599
600 for (w = p->words; w; w = w->next) if (w->type == word_WeakCode) {
601 t = w->text;
602 if (w->next && w->next->type == word_Emph) {
603 w = w->next;
604 e = w->text;
605 } else
606 e = NULL;
607
608 if (!w->next)
609 whlp_para_attr(h, WHLP_PARA_SPACEBELOW, 12);
610
611 whlp_para_attr(h, WHLP_PARA_LEFTINDENT, 72*nesting);
612 whlp_begin_para(h, WHLP_PARA_SCROLL);
613 state.cstate = charset_init_state;
614 while (e && *e && *t) {
615 int n;
616 int ec = *e;
617
618 for (n = 0; t[n] && e[n] && e[n] == ec; n++);
619 if (ec == 'i')
620 whlp_set_font(h, FONT_ITAL_CODE);
621 else if (ec == 'b')
622 whlp_set_font(h, FONT_BOLD_CODE);
623 else
624 whlp_set_font(h, FONT_CODE);
625 tmp = snewn(n+1, wchar_t);
626 ustrncpy(tmp, t, n);
627 tmp[n] = L'\0';
628 whlp_wtext(&state, tmp);
629 whlp_wtext(&state, NULL);
630 state.cstate = charset_init_state;
631 sfree(tmp);
632 t += n;
633 e += n;
634 }
635 whlp_set_font(h, FONT_CODE);
636 whlp_wtext(&state, t);
637 whlp_wtext(&state, NULL);
638 whlp_end_para(h);
639 }
640 }
641 break;
642 }
643
644 fclose(state.cntfp);
645 whlp_close(h, conf.filename);
646
647 /*
648 * Loop over the index entries, cleaning up our final text
649 * forms.
650 */
651 for (i = 0; (ie = index234(idx->entries, i)) != NULL; i++) {
652 sfree(ie->backend_data);
653 }
654
655 sfree(conf.filename);
656 sfree(cntname);
657 }
658
whlp_contents_write(struct bk_whlp_state * state,int level,char * text,WHLP_TOPIC topic)659 static void whlp_contents_write(struct bk_whlp_state *state,
660 int level, char *text, WHLP_TOPIC topic) {
661 /*
662 * Horrifying bug in WinHelp. When dropping a section level or
663 * more without using a folder-type entry, WinHelp accidentally
664 * adds one to the section level. So we correct for that here.
665 */
666 if (state->cnt_last_level > level && topic)
667 state->cnt_workaround = -1;
668 else if (!topic)
669 state->cnt_workaround = 0;
670 state->cnt_last_level = level;
671
672 fprintf(state->cntfp, "%d ", level + state->cnt_workaround);
673 while (*text) {
674 if (*text == '=')
675 fputc('\\', state->cntfp);
676 fputc(*text, state->cntfp);
677 text++;
678 }
679 if (topic)
680 fprintf(state->cntfp, "=%s", whlp_topic_id(topic));
681 fputc('\n', state->cntfp);
682 }
683
whlp_navmenu(struct bk_whlp_state * state,paragraph * p,whlpconf * conf)684 static void whlp_navmenu(struct bk_whlp_state *state, paragraph *p,
685 whlpconf *conf) {
686 whlp_begin_para(state->h, WHLP_PARA_SCROLL);
687 whlp_start_hyperlink(state->h, (WHLP_TOPIC)p->private_data);
688 state->cstate = charset_init_state;
689 if (p->kwtext) {
690 whlp_mkparagraph(state, FONT_NORMAL, p->kwtext, TRUE, conf);
691 whlp_set_font(state->h, FONT_NORMAL);
692 whlp_wtext(state, conf->sectsuffix);
693 }
694 whlp_mkparagraph(state, FONT_NORMAL, p->words, TRUE, conf);
695 whlp_wtext(state, NULL);
696 whlp_end_hyperlink(state->h);
697 whlp_end_para(state->h);
698
699 }
700
whlp_mkparagraph(struct bk_whlp_state * state,int font,word * text,int subsidiary,whlpconf * conf)701 static void whlp_mkparagraph(struct bk_whlp_state *state,
702 int font, word *text, int subsidiary,
703 whlpconf *conf) {
704 keyword *kwl;
705 int deffont = font;
706 int currfont = -1;
707 int newfont;
708 paragraph *xref_target = NULL;
709
710 for (; text; text = text->next) switch (text->type) {
711 case word_HyperLink:
712 case word_HyperEnd:
713 break;
714
715 case word_IndexRef:
716 if (subsidiary) break; /* disabled in subsidiary bits */
717 {
718 indextag *tag = index_findtag(state->idx, text->text);
719 int i;
720 if (!tag)
721 break;
722 for (i = 0; i < tag->nrefs; i++)
723 whlp_index_term(state->h, tag->refs[i]->backend_data,
724 state->curr_topic);
725 }
726 break;
727
728 case word_UpperXref:
729 case word_LowerXref:
730 if (subsidiary) break; /* disabled in subsidiary bits */
731 kwl = kw_lookup(state->keywords, text->text);
732 assert(xref_target == NULL);
733 if (kwl) {
734 if (kwl->para->type == para_NumberedList) {
735 break; /* don't xref to numbered list items */
736 } else if (kwl->para->type == para_BiblioCited) {
737 /*
738 * An xref to a bibliography item jumps to the section
739 * containing it.
740 */
741 if (kwl->para->parent)
742 xref_target = kwl->para->parent;
743 else
744 break;
745 } else {
746 xref_target = kwl->para;
747 }
748 whlp_start_hyperlink(state->h,
749 (WHLP_TOPIC)xref_target->private_data);
750 }
751 break;
752
753 case word_XrefEnd:
754 if (subsidiary) break; /* disabled in subsidiary bits */
755 if (xref_target)
756 whlp_end_hyperlink(state->h);
757 xref_target = NULL;
758 break;
759
760 case word_Normal:
761 case word_Emph:
762 case word_Strong:
763 case word_Code:
764 case word_WeakCode:
765 case word_WhiteSpace:
766 case word_EmphSpace:
767 case word_StrongSpace:
768 case word_CodeSpace:
769 case word_WkCodeSpace:
770 case word_Quote:
771 case word_EmphQuote:
772 case word_StrongQuote:
773 case word_CodeQuote:
774 case word_WkCodeQuote:
775 if (towordstyle(text->type) == word_Emph)
776 newfont = deffont + FONT_EMPH;
777 else if (towordstyle(text->type) == word_Strong)
778 newfont = deffont + FONT_STRONG;
779 else if (towordstyle(text->type) == word_Code ||
780 towordstyle(text->type) == word_WeakCode)
781 newfont = deffont + FONT_CODE;
782 else
783 newfont = deffont;
784 if (newfont != currfont) {
785 currfont = newfont;
786 whlp_set_font(state->h, newfont);
787 }
788 if (removeattr(text->type) == word_Normal) {
789 if (cvt_ok(conf->charset, text->text) || !text->alt)
790 whlp_wtext(state, text->text);
791 else
792 whlp_mkparagraph(state, deffont, text->alt, FALSE, conf);
793 } else if (removeattr(text->type) == word_WhiteSpace) {
794 whlp_wtext(state, L" ");
795 } else if (removeattr(text->type) == word_Quote) {
796 whlp_wtext(state,
797 quoteaux(text->aux) == quote_Open ?
798 conf->lquote : conf->rquote);
799 }
800 break;
801 }
802 }
803
whlp_rdaddwc(rdstringc * rs,word * text,whlpconf * conf,charset_state * state)804 static void whlp_rdaddwc(rdstringc *rs, word *text, whlpconf *conf,
805 charset_state *state) {
806 charset_state ourstate = CHARSET_INIT_STATE;
807
808 if (!state)
809 state = &ourstate;
810
811 for (; text; text = text->next) switch (text->type) {
812 case word_HyperLink:
813 case word_HyperEnd:
814 case word_UpperXref:
815 case word_LowerXref:
816 case word_XrefEnd:
817 case word_IndexRef:
818 break;
819
820 case word_Normal:
821 case word_Emph:
822 case word_Strong:
823 case word_Code:
824 case word_WeakCode:
825 case word_WhiteSpace:
826 case word_EmphSpace:
827 case word_StrongSpace:
828 case word_CodeSpace:
829 case word_WkCodeSpace:
830 case word_Quote:
831 case word_EmphQuote:
832 case word_StrongQuote:
833 case word_CodeQuote:
834 case word_WkCodeQuote:
835 assert(text->type != word_CodeQuote &&
836 text->type != word_WkCodeQuote);
837 if (removeattr(text->type) == word_Normal) {
838 if (cvt_ok(conf->charset, text->text) || !text->alt)
839 whlp_rdadds(rs, text->text, conf, state);
840 else
841 whlp_rdaddwc(rs, text->alt, conf, state);
842 } else if (removeattr(text->type) == word_WhiteSpace) {
843 whlp_rdadds(rs, L" ", conf, state);
844 } else if (removeattr(text->type) == word_Quote) {
845 whlp_rdadds(rs, quoteaux(text->aux) == quote_Open ?
846 conf->lquote : conf->rquote, conf, state);
847 }
848 break;
849 }
850
851 if (state == &ourstate)
852 whlp_rdadds(rs, NULL, conf, state);
853 }
854
whlp_rdadds(rdstringc * rs,const wchar_t * text,whlpconf * conf,charset_state * state)855 static void whlp_rdadds(rdstringc *rs, const wchar_t *text, whlpconf *conf,
856 charset_state *state)
857 {
858 charset_state ourstate = CHARSET_INIT_STATE;
859 int textlen = text ? ustrlen(text) : 0;
860 char outbuf[256];
861 int ret;
862
863 if (!state)
864 state = &ourstate;
865
866 while (textlen > 0 &&
867 (ret = charset_from_unicode(&text, &textlen, outbuf,
868 lenof(outbuf)-1,
869 conf->charset, state, NULL)) > 0) {
870 outbuf[ret] = '\0';
871 rdaddsc(rs, outbuf);
872 }
873
874 if (text == NULL || state == &ourstate) {
875 if ((ret = charset_from_unicode(NULL, 0, outbuf, lenof(outbuf)-1,
876 conf->charset, state, NULL)) > 0) {
877 outbuf[ret] = '\0';
878 rdaddsc(rs, outbuf);
879 }
880 }
881 }
882
whlp_wtext(struct bk_whlp_state * state,const wchar_t * text)883 static void whlp_wtext(struct bk_whlp_state *state, const wchar_t *text)
884 {
885 int textlen = text ? ustrlen(text) : 0;
886 char outbuf[256];
887 int ret;
888
889 while (textlen > 0 &&
890 (ret = charset_from_unicode(&text, &textlen, outbuf,
891 lenof(outbuf)-1,
892 state->charset, &state->cstate,
893 NULL)) > 0) {
894 outbuf[ret] = '\0';
895 whlp_text(state->h, outbuf);
896 }
897
898 if (text == NULL) {
899 if ((ret = charset_from_unicode(NULL, 0, outbuf, lenof(outbuf)-1,
900 state->charset, &state->cstate,
901 NULL)) > 0) {
902 outbuf[ret] = '\0';
903 whlp_text(state->h, outbuf);
904 }
905 }
906 }
907