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