Moderately nasty workaround for the fact that Windows Help's index
[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 state.cnt_last_level = -1; state.cnt_workaround = 0;
216
217 /*
218 * Loop over the source form registering WHLP_TOPICs for
219 * everything.
220 */
221
222 contents_topic = whlp_register_topic(h, "Top", NULL);
223 whlp_primary_topic(h, contents_topic);
224 for (p = sourceform; p; p = p->next) {
225 if (p->type == para_Chapter ||
226 p->type == para_Appendix ||
227 p->type == para_UnnumberedChapter ||
228 p->type == para_Heading ||
229 p->type == para_Subsect) {
230
231 rdstringc rs = { 0, 0, NULL };
232 char *errstr;
233
234 whlp_rdadds(&rs, (wchar_t *)p->private_data, &conf, NULL);
235
236 p->private_data = whlp_register_topic(h, rs.text, &errstr);
237 if (!p->private_data) {
238 p->private_data = whlp_register_topic(h, NULL, NULL);
239 error(err_winhelp_ctxclash, &p->fpos, rs.text, errstr);
240 }
241 sfree(rs.text);
242 }
243 }
244
245 /*
246 * Loop over the index entries, preparing final text forms for
247 * each one.
248 */
249 {
250 indexentry *ie_prev = NULL;
251 int nspaces = 1;
252
253 for (i = 0; (ie = index234(idx->entries, i)) != NULL; i++) {
254 rdstringc rs = {0, 0, NULL};
255 charset_state state = CHARSET_INIT_STATE;
256 whlp_rdaddwc(&rs, ie->text, &conf, &state);
257
258 if (ie_prev) {
259 /*
260 * It appears that Windows Help's index mechanism
261 * is inherently case-sensitive. Therefore, if two
262 * adjacent index terms compare equal apart from
263 * case, I'm going to append nonbreaking spaces to
264 * the end of the second one so that Windows will
265 * treat them as distinct.
266 *
267 * This is nasty because we're depending on our
268 * case-insensitive comparison having the same
269 * semantics as the Windows one :-/ but I see no
270 * alternative.
271 */
272 wchar_t *a, *b;
273
274 a = ufroma_dup((char *)ie_prev->backend_data, conf.charset);
275 b = ufroma_dup(rs.text, conf.charset);
276 if (!ustricmp(a, b)) {
277 int j;
278 for (j = 0; j < nspaces; j++)
279 whlp_rdadds(&rs, L"\xA0", &conf, &state);
280 /*
281 * Add one to nspaces, so that if another term
282 * appears which is equivalent to the previous
283 * two it'll acquire one more space.
284 */
285 nspaces++;
286 } else
287 nspaces = 1;
288 sfree(a);
289 sfree(b);
290 }
291
292 whlp_rdadds(&rs, NULL, &conf, &state);
293
294 ie->backend_data = rs.text;
295
296 /*
297 * Only move ie_prev on if nspaces==1 (since when we
298 * have three or more adjacent terms differing only in
299 * case, we will want to compare with the _first_ of
300 * them because that won't have had any extra spaces
301 * added on which will foul up the comparison).
302 */
303 if (nspaces == 1)
304 ie_prev = ie;
305 }
306 }
307
308 whlp_prepare(h);
309
310 /* ------------------------------------------------------------------
311 * Begin the contents page.
312 */
313
314 whlp_begin_topic(h, contents_topic, "Contents", "DB(\"btn_up\")", NULL);
315
316 /*
317 * The manual title goes in the non-scroll region, and also
318 * goes into the system title slot.
319 */
320 {
321 rdstringc rs = {0, 0, NULL};
322 for (p = sourceform; p; p = p->next) {
323 if (p->type == para_Title) {
324 whlp_begin_para(h, WHLP_PARA_NONSCROLL);
325 state.cstate = charset_init_state;
326 whlp_mkparagraph(&state, FONT_TITLE, p->words, FALSE, &conf);
327 whlp_wtext(&state, NULL);
328 whlp_end_para(h);
329 whlp_rdaddwc(&rs, p->words, &conf, NULL);
330 }
331 }
332 if (rs.text) {
333 whlp_title(h, rs.text);
334 fprintf(state.cntfp, ":Title %s\r\n", rs.text);
335 sfree(rs.text);
336 }
337 {
338 rdstringc rs2 = {0,0,NULL};
339 whlp_rdadds(&rs2, conf.titlepage, &conf, NULL);
340 whlp_contents_write(&state, 1, rs2.text, contents_topic);
341 sfree(rs2.text);
342 }
343 }
344
345 /*
346 * Put the copyright into the system section.
347 */
348 {
349 rdstringc rs = {0, 0, NULL};
350 for (p = sourceform; p; p = p->next) {
351 if (p->type == para_Copyright)
352 whlp_rdaddwc(&rs, p->words, &conf, NULL);
353 }
354 if (rs.text) {
355 whlp_copyright(h, rs.text);
356 sfree(rs.text);
357 }
358 }
359
360 lastsect = NULL;
361
362 /* ------------------------------------------------------------------
363 * Now we've done the contents page, we're ready to go through
364 * and do the main manual text. Ooh.
365 */
366 nesting = 0;
367 for (p = sourceform; p; p = p->next) switch (p->type) {
368 /*
369 * Things we ignore because we've already processed them or
370 * aren't going to touch them in this pass.
371 */
372 case para_IM:
373 case para_BR:
374 case para_Biblio: /* only touch BiblioCited */
375 case para_VersionID:
376 case para_NoCite:
377 case para_Title:
378 break;
379
380 case para_LcontPush:
381 case para_QuotePush:
382 nesting++;
383 break;
384 case para_LcontPop:
385 case para_QuotePop:
386 assert(nesting > 0);
387 nesting--;
388 break;
389
390 /*
391 * Chapter and section titles: start a new Help topic.
392 */
393 case para_Chapter:
394 case para_Appendix:
395 case para_UnnumberedChapter:
396 case para_Heading:
397 case para_Subsect:
398
399 if (!done_contents_topic) {
400 paragraph *p;
401
402 /*
403 * If this is the first section title we've seen, then
404 * we're currently still in the contents topic. We
405 * should therefore finish up the contents page by
406 * writing a nav menu.
407 */
408 for (p = sourceform; p; p = p->next) {
409 if (p->type == para_Chapter ||
410 p->type == para_Appendix ||
411 p->type == para_UnnumberedChapter)
412 whlp_navmenu(&state, p, &conf);
413 }
414
415 state.curr_topic = contents_topic;
416
417 done_contents_topic = TRUE;
418 }
419
420 if (lastsect && lastsect->child) {
421 paragraph *q;
422 /*
423 * Do a navigation menu for the previous section we
424 * were in.
425 */
426 for (q = lastsect->child; q; q = q->sibling)
427 whlp_navmenu(&state, q, &conf);
428 }
429 {
430 rdstringc rs = {0, 0, NULL};
431 WHLP_TOPIC new_topic, parent_topic;
432 char *macro, *topicid;
433 charset_state cstate = CHARSET_INIT_STATE;
434
435 new_topic = p->private_data;
436 whlp_browse_link(h, state.curr_topic, new_topic);
437 state.curr_topic = new_topic;
438
439 if (p->kwtext) {
440 whlp_rdaddwc(&rs, p->kwtext, &conf, &cstate);
441 whlp_rdadds(&rs, conf.sectsuffix, &conf, &cstate);
442 }
443 whlp_rdaddwc(&rs, p->words, &conf, &cstate);
444 whlp_rdadds(&rs, NULL, &conf, &cstate);
445
446 if (p->parent == NULL)
447 parent_topic = contents_topic;
448 else
449 parent_topic = (WHLP_TOPIC)p->parent->private_data;
450 topicid = whlp_topic_id(parent_topic);
451 macro = smalloc(100+strlen(topicid));
452 sprintf(macro,
453 "CBB(\"btn_up\",\"JI(`',`%s')\");EB(\"btn_up\")",
454 topicid);
455 whlp_begin_topic(h, new_topic,
456 rs.text ? rs.text : "",
457 macro, NULL);
458 sfree(macro);
459
460 {
461 /*
462 * Output the .cnt entry.
463 *
464 * WinHelp has a bug involving having an internal
465 * node followed by a leaf at the same level: the
466 * leaf is output at the wrong level. We can mostly
467 * work around this by modifying the leaf level
468 * itself (see whlp_contents_write), but this
469 * doesn't work for top-level sections since we
470 * can't turn a level-1 leaf into a level-0 one. So
471 * for top-level leaf sections (Bibliography
472 * springs to mind), we output an internal node
473 * containing only the leaf for that section.
474 */
475 int i;
476 paragraph *q;
477
478 /* Count up the level. */
479 i = 1;
480 for (q = p; q->parent; q = q->parent) i++;
481
482 if (p->child || !p->parent) {
483 /*
484 * If p has children then it needs to be a
485 * folder; if it has no parent then it needs to
486 * be a folder to work around the bug.
487 */
488 whlp_contents_write(&state, i, rs.text, NULL);
489 i++;
490 }
491 whlp_contents_write(&state, i, rs.text, new_topic);
492 }
493
494 sfree(rs.text);
495
496 whlp_begin_para(h, WHLP_PARA_NONSCROLL);
497 state.cstate = charset_init_state;
498 if (p->kwtext) {
499 whlp_mkparagraph(&state, FONT_TITLE, p->kwtext, FALSE, &conf);
500 whlp_set_font(h, FONT_TITLE);
501 whlp_wtext(&state, conf.sectsuffix);
502 }
503 whlp_mkparagraph(&state, FONT_TITLE, p->words, FALSE, &conf);
504 whlp_wtext(&state, NULL);
505 whlp_end_para(h);
506
507 lastsect = p;
508 }
509 break;
510
511 case para_Rule:
512 whlp_para_attr(h, WHLP_PARA_SPACEBELOW, 12);
513 whlp_para_attr(h, WHLP_PARA_ALIGNMENT, WHLP_ALIGN_CENTRE);
514 whlp_begin_para(h, WHLP_PARA_SCROLL);
515 whlp_set_font(h, FONT_RULE);
516 state.cstate = charset_init_state;
517 #define TEN L"\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0"
518 #define TWENTY TEN TEN
519 #define FORTY TWENTY TWENTY
520 #define EIGHTY FORTY FORTY
521 state.cstate = charset_init_state;
522 whlp_wtext(&state, EIGHTY);
523 whlp_wtext(&state, NULL);
524 #undef TEN
525 #undef TWENTY
526 #undef FORTY
527 #undef EIGHTY
528 whlp_end_para(h);
529 break;
530
531 case para_Normal:
532 case para_Copyright:
533 case para_DescribedThing:
534 case para_Description:
535 case para_BiblioCited:
536 case para_Bullet:
537 case para_NumberedList:
538 whlp_para_attr(h, WHLP_PARA_SPACEBELOW, 12);
539 if (p->type == para_Bullet || p->type == para_NumberedList) {
540 whlp_para_attr(h, WHLP_PARA_LEFTINDENT, 72*nesting + 72);
541 whlp_para_attr(h, WHLP_PARA_FIRSTLINEINDENT, -36);
542 whlp_set_tabstop(h, 72, WHLP_ALIGN_LEFT);
543 whlp_begin_para(h, WHLP_PARA_SCROLL);
544 whlp_set_font(h, FONT_NORMAL);
545 state.cstate = charset_init_state;
546 if (p->type == para_Bullet) {
547 whlp_wtext(&state, conf.bullet);
548 } else {
549 whlp_mkparagraph(&state, FONT_NORMAL, p->kwtext, FALSE, &conf);
550 whlp_wtext(&state, conf.listsuffix);
551 }
552 whlp_wtext(&state, NULL);
553 whlp_tab(h);
554 } else {
555 whlp_para_attr(h, WHLP_PARA_LEFTINDENT,
556 72*nesting + (p->type==para_Description ? 72 : 0));
557 whlp_begin_para(h, WHLP_PARA_SCROLL);
558 }
559
560 state.cstate = charset_init_state;
561
562 if (p->type == para_BiblioCited) {
563 whlp_mkparagraph(&state, FONT_NORMAL, p->kwtext, FALSE, &conf);
564 whlp_wtext(&state, L" ");
565 }
566
567 whlp_mkparagraph(&state, FONT_NORMAL, p->words, FALSE, &conf);
568 whlp_wtext(&state, NULL);
569 whlp_end_para(h);
570 break;
571
572 case para_Code:
573 /*
574 * In a code paragraph, each individual word is a line. For
575 * Help files, we will have to output this as a set of
576 * paragraphs, all but the last of which don't set
577 * SPACEBELOW.
578 */
579 {
580 word *w;
581 wchar_t *t, *e, *tmp;
582
583 for (w = p->words; w; w = w->next) if (w->type == word_WeakCode) {
584 t = w->text;
585 if (w->next && w->next->type == word_Emph) {
586 w = w->next;
587 e = w->text;
588 } else
589 e = NULL;
590
591 if (!w->next)
592 whlp_para_attr(h, WHLP_PARA_SPACEBELOW, 12);
593
594 whlp_para_attr(h, WHLP_PARA_LEFTINDENT, 72*nesting);
595 whlp_begin_para(h, WHLP_PARA_SCROLL);
596 state.cstate = charset_init_state;
597 while (e && *e && *t) {
598 int n;
599 int ec = *e;
600
601 for (n = 0; t[n] && e[n] && e[n] == ec; n++);
602 if (ec == 'i')
603 whlp_set_font(h, FONT_ITAL_CODE);
604 else if (ec == 'b')
605 whlp_set_font(h, FONT_BOLD_CODE);
606 else
607 whlp_set_font(h, FONT_CODE);
608 tmp = snewn(n+1, wchar_t);
609 ustrncpy(tmp, t, n);
610 tmp[n] = L'\0';
611 whlp_wtext(&state, tmp);
612 whlp_wtext(&state, NULL);
613 state.cstate = charset_init_state;
614 sfree(tmp);
615 t += n;
616 e += n;
617 }
618 whlp_set_font(h, FONT_CODE);
619 whlp_wtext(&state, t);
620 whlp_wtext(&state, NULL);
621 whlp_end_para(h);
622 }
623 }
624 break;
625 }
626
627 fclose(state.cntfp);
628 whlp_close(h, conf.filename);
629
630 /*
631 * Loop over the index entries, cleaning up our final text
632 * forms.
633 */
634 for (i = 0; (ie = index234(idx->entries, i)) != NULL; i++) {
635 sfree(ie->backend_data);
636 }
637
638 sfree(conf.filename);
639 sfree(cntname);
640 }
641
642 static void whlp_contents_write(struct bk_whlp_state *state,
643 int level, char *text, WHLP_TOPIC topic) {
644 /*
645 * Horrifying bug in WinHelp. When dropping a section level or
646 * more without using a folder-type entry, WinHelp accidentally
647 * adds one to the section level. So we correct for that here.
648 */
649 if (state->cnt_last_level > level && topic)
650 state->cnt_workaround = -1;
651 else if (!topic)
652 state->cnt_workaround = 0;
653 state->cnt_last_level = level;
654
655 fprintf(state->cntfp, "%d ", level + state->cnt_workaround);
656 while (*text) {
657 if (*text == '=')
658 fputc('\\', state->cntfp);
659 fputc(*text, state->cntfp);
660 text++;
661 }
662 if (topic)
663 fprintf(state->cntfp, "=%s", whlp_topic_id(topic));
664 fputc('\n', state->cntfp);
665 }
666
667 static void whlp_navmenu(struct bk_whlp_state *state, paragraph *p,
668 whlpconf *conf) {
669 whlp_begin_para(state->h, WHLP_PARA_SCROLL);
670 whlp_start_hyperlink(state->h, (WHLP_TOPIC)p->private_data);
671 state->cstate = charset_init_state;
672 if (p->kwtext) {
673 whlp_mkparagraph(state, FONT_NORMAL, p->kwtext, TRUE, conf);
674 whlp_set_font(state->h, FONT_NORMAL);
675 whlp_wtext(state, conf->sectsuffix);
676 }
677 whlp_mkparagraph(state, FONT_NORMAL, p->words, TRUE, conf);
678 whlp_wtext(state, NULL);
679 whlp_end_hyperlink(state->h);
680 whlp_end_para(state->h);
681
682 }
683
684 static void whlp_mkparagraph(struct bk_whlp_state *state,
685 int font, word *text, int subsidiary,
686 whlpconf *conf) {
687 keyword *kwl;
688 int deffont = font;
689 int currfont = -1;
690 int newfont;
691 paragraph *xref_target = NULL;
692
693 for (; text; text = text->next) switch (text->type) {
694 case word_HyperLink:
695 case word_HyperEnd:
696 break;
697
698 case word_IndexRef:
699 if (subsidiary) break; /* disabled in subsidiary bits */
700 {
701 indextag *tag = index_findtag(state->idx, text->text);
702 int i;
703 if (!tag)
704 break;
705 for (i = 0; i < tag->nrefs; i++)
706 whlp_index_term(state->h, tag->refs[i]->backend_data,
707 state->curr_topic);
708 }
709 break;
710
711 case word_UpperXref:
712 case word_LowerXref:
713 if (subsidiary) break; /* disabled in subsidiary bits */
714 kwl = kw_lookup(state->keywords, text->text);
715 assert(xref_target == NULL);
716 if (kwl) {
717 if (kwl->para->type == para_NumberedList) {
718 break; /* don't xref to numbered list items */
719 } else if (kwl->para->type == para_BiblioCited) {
720 /*
721 * An xref to a bibliography item jumps to the section
722 * containing it.
723 */
724 if (kwl->para->parent)
725 xref_target = kwl->para->parent;
726 else
727 break;
728 } else {
729 xref_target = kwl->para;
730 }
731 whlp_start_hyperlink(state->h,
732 (WHLP_TOPIC)xref_target->private_data);
733 }
734 break;
735
736 case word_XrefEnd:
737 if (subsidiary) break; /* disabled in subsidiary bits */
738 if (xref_target)
739 whlp_end_hyperlink(state->h);
740 xref_target = NULL;
741 break;
742
743 case word_Normal:
744 case word_Emph:
745 case word_Code:
746 case word_WeakCode:
747 case word_WhiteSpace:
748 case word_EmphSpace:
749 case word_CodeSpace:
750 case word_WkCodeSpace:
751 case word_Quote:
752 case word_EmphQuote:
753 case word_CodeQuote:
754 case word_WkCodeQuote:
755 if (towordstyle(text->type) == word_Emph)
756 newfont = deffont + FONT_EMPH;
757 else if (towordstyle(text->type) == word_Code ||
758 towordstyle(text->type) == word_WeakCode)
759 newfont = deffont + FONT_CODE;
760 else
761 newfont = deffont;
762 if (newfont != currfont) {
763 currfont = newfont;
764 whlp_set_font(state->h, newfont);
765 }
766 if (removeattr(text->type) == word_Normal) {
767 if (cvt_ok(conf->charset, text->text) || !text->alt)
768 whlp_wtext(state, text->text);
769 else
770 whlp_mkparagraph(state, deffont, text->alt, FALSE, conf);
771 } else if (removeattr(text->type) == word_WhiteSpace) {
772 whlp_wtext(state, L" ");
773 } else if (removeattr(text->type) == word_Quote) {
774 whlp_wtext(state,
775 quoteaux(text->aux) == quote_Open ?
776 conf->lquote : conf->rquote);
777 }
778 break;
779 }
780 }
781
782 static void whlp_rdaddwc(rdstringc *rs, word *text, whlpconf *conf,
783 charset_state *state) {
784 charset_state ourstate = CHARSET_INIT_STATE;
785
786 if (!state)
787 state = &ourstate;
788
789 for (; text; text = text->next) switch (text->type) {
790 case word_HyperLink:
791 case word_HyperEnd:
792 case word_UpperXref:
793 case word_LowerXref:
794 case word_XrefEnd:
795 case word_IndexRef:
796 break;
797
798 case word_Normal:
799 case word_Emph:
800 case word_Code:
801 case word_WeakCode:
802 case word_WhiteSpace:
803 case word_EmphSpace:
804 case word_CodeSpace:
805 case word_WkCodeSpace:
806 case word_Quote:
807 case word_EmphQuote:
808 case word_CodeQuote:
809 case word_WkCodeQuote:
810 assert(text->type != word_CodeQuote &&
811 text->type != word_WkCodeQuote);
812 if (removeattr(text->type) == word_Normal) {
813 if (cvt_ok(conf->charset, text->text) || !text->alt)
814 whlp_rdadds(rs, text->text, conf, state);
815 else
816 whlp_rdaddwc(rs, text->alt, conf, state);
817 } else if (removeattr(text->type) == word_WhiteSpace) {
818 whlp_rdadds(rs, L" ", conf, state);
819 } else if (removeattr(text->type) == word_Quote) {
820 whlp_rdadds(rs, quoteaux(text->aux) == quote_Open ?
821 conf->lquote : conf->rquote, conf, state);
822 }
823 break;
824 }
825
826 if (state == &ourstate)
827 whlp_rdadds(rs, NULL, conf, state);
828 }
829
830 static void whlp_rdadds(rdstringc *rs, const wchar_t *text, whlpconf *conf,
831 charset_state *state)
832 {
833 charset_state ourstate = CHARSET_INIT_STATE;
834 int textlen = text ? ustrlen(text) : 0;
835 char outbuf[256];
836 int ret;
837
838 if (!state)
839 state = &ourstate;
840
841 while (textlen > 0 &&
842 (ret = charset_from_unicode(&text, &textlen, outbuf,
843 lenof(outbuf)-1,
844 conf->charset, state, NULL)) > 0) {
845 outbuf[ret] = '\0';
846 rdaddsc(rs, outbuf);
847 }
848
849 if (text == NULL || state == &ourstate) {
850 if ((ret = charset_from_unicode(NULL, 0, outbuf, lenof(outbuf)-1,
851 conf->charset, state, NULL)) > 0) {
852 outbuf[ret] = '\0';
853 rdaddsc(rs, outbuf);
854 }
855 }
856 }
857
858 static void whlp_wtext(struct bk_whlp_state *state, const wchar_t *text)
859 {
860 int textlen = text ? ustrlen(text) : 0;
861 char outbuf[256];
862 int ret;
863
864 while (textlen > 0 &&
865 (ret = charset_from_unicode(&text, &textlen, outbuf,
866 lenof(outbuf)-1,
867 state->charset, &state->cstate,
868 NULL)) > 0) {
869 outbuf[ret] = '\0';
870 whlp_text(state->h, outbuf);
871 }
872
873 if (text == NULL) {
874 if ((ret = charset_from_unicode(NULL, 0, outbuf, lenof(outbuf)-1,
875 state->charset, &state->cstate,
876 NULL)) > 0) {
877 outbuf[ret] = '\0';
878 whlp_text(state->h, outbuf);
879 }
880 }
881 }