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