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