d7482997 |
1 | /* |
2 | * Windows Help backend for Halibut |
d7482997 |
3 | */ |
4 | |
5 | #include <stdio.h> |
6 | #include <stdlib.h> |
50d6b4bd |
7 | #include <ctype.h> |
d7482997 |
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 | FILE *cntfp; |
19 | int cnt_last_level, cnt_workaround; |
20 | }; |
21 | |
22 | /* |
23 | * Indexes of fonts in our standard font descriptor set. |
24 | */ |
25 | enum { |
26 | FONT_NORMAL, |
27 | FONT_EMPH, |
28 | FONT_CODE, |
4b3c5afb |
29 | FONT_ITAL_CODE, |
30 | FONT_BOLD_CODE, |
d7482997 |
31 | FONT_TITLE, |
32 | FONT_TITLE_EMPH, |
33 | FONT_TITLE_CODE, |
34 | FONT_RULE |
35 | }; |
36 | |
37 | static void whlp_rdaddwc(rdstringc *rs, word *text); |
4b3c5afb |
38 | static int whlp_convert(wchar_t *s, int maxlen, |
39 | char **result, int hard_spaces); |
d7482997 |
40 | static void whlp_mkparagraph(struct bk_whlp_state *state, |
41 | int font, word *text, int subsidiary); |
42 | static void whlp_navmenu(struct bk_whlp_state *state, paragraph *p); |
43 | static void whlp_contents_write(struct bk_whlp_state *state, |
44 | int level, char *text, WHLP_TOPIC topic); |
45 | |
ba9c1487 |
46 | paragraph *whlp_config_filename(char *filename) |
47 | { |
48 | paragraph *p; |
49 | wchar_t *ufilename, *up; |
50 | int len; |
51 | |
52 | p = mknew(paragraph); |
53 | memset(p, 0, sizeof(*p)); |
54 | p->type = para_Config; |
55 | p->next = NULL; |
56 | p->fpos.filename = "<command line>"; |
57 | p->fpos.line = p->fpos.col = -1; |
58 | |
59 | ufilename = ufroma_dup(filename); |
60 | len = ustrlen(ufilename) + 2 + lenof(L"winhelp-filename"); |
61 | p->keyword = mknewa(wchar_t, len); |
62 | up = p->keyword; |
63 | ustrcpy(up, L"winhelp-filename"); |
64 | up = uadv(up); |
65 | ustrcpy(up, ufilename); |
66 | up = uadv(up); |
67 | *up = L'\0'; |
68 | assert(up - p->keyword < len); |
69 | sfree(ufilename); |
70 | |
71 | return p; |
72 | } |
73 | |
d7482997 |
74 | void whlp_backend(paragraph *sourceform, keywordlist *keywords, |
75 | indexdata *idx) { |
76 | WHLP h; |
77 | char *filename, *cntname; |
78 | paragraph *p, *lastsect; |
79 | struct bk_whlp_state state; |
80 | WHLP_TOPIC contents_topic; |
81 | int i; |
7136a6c7 |
82 | int nesting; |
d7482997 |
83 | indexentry *ie; |
de967e1b |
84 | int done_contents_topic = FALSE; |
d7482997 |
85 | |
d7482997 |
86 | h = state.h = whlp_new(); |
87 | state.keywords = keywords; |
88 | state.idx = idx; |
89 | |
90 | whlp_start_macro(h, "CB(\"btn_about\",\"&About\",\"About()\")"); |
91 | whlp_start_macro(h, "CB(\"btn_up\",\"&Up\",\"Contents()\")"); |
92 | whlp_start_macro(h, "BrowseButtons()"); |
93 | |
94 | whlp_create_font(h, "Times New Roman", WHLP_FONTFAM_SERIF, 24, |
95 | 0, 0, 0, 0); |
96 | whlp_create_font(h, "Times New Roman", WHLP_FONTFAM_SERIF, 24, |
97 | WHLP_FONT_ITALIC, 0, 0, 0); |
98 | whlp_create_font(h, "Courier New", WHLP_FONTFAM_FIXED, 24, |
99 | 0, 0, 0, 0); |
4b3c5afb |
100 | whlp_create_font(h, "Courier New", WHLP_FONTFAM_FIXED, 24, |
101 | WHLP_FONT_ITALIC, 0, 0, 0); |
102 | whlp_create_font(h, "Courier New", WHLP_FONTFAM_FIXED, 24, |
103 | WHLP_FONT_BOLD, 0, 0, 0); |
d7482997 |
104 | whlp_create_font(h, "Arial", WHLP_FONTFAM_SERIF, 30, |
105 | WHLP_FONT_BOLD, 0, 0, 0); |
106 | whlp_create_font(h, "Arial", WHLP_FONTFAM_SERIF, 30, |
107 | WHLP_FONT_BOLD|WHLP_FONT_ITALIC, 0, 0, 0); |
108 | whlp_create_font(h, "Courier New", WHLP_FONTFAM_FIXED, 30, |
109 | WHLP_FONT_BOLD, 0, 0, 0); |
110 | whlp_create_font(h, "Courier New", WHLP_FONTFAM_SANS, 18, |
111 | WHLP_FONT_STRIKEOUT, 0, 0, 0); |
112 | |
113 | /* |
114 | * Loop over the source form finding out whether the user has |
50d6b4bd |
115 | * specified particular help topic names for anything. Also |
116 | * pick out the output file name at this stage. |
d7482997 |
117 | */ |
50d6b4bd |
118 | filename = dupstr("output.hlp"); |
d7482997 |
119 | for (p = sourceform; p; p = p->next) { |
120 | p->private_data = NULL; |
121 | if (p->type == para_Config && p->parent) { |
122 | if (!ustricmp(p->keyword, L"winhelp-topic")) { |
123 | char *topicname; |
4b3c5afb |
124 | whlp_convert(uadv(p->keyword), 0, &topicname, 0); |
d7482997 |
125 | /* Store the topic name in the private_data field of the |
126 | * containing section. */ |
127 | p->parent->private_data = topicname; |
50d6b4bd |
128 | } else if (!ustricmp(p->keyword, L"winhelp-filename")) { |
129 | sfree(filename); |
130 | filename = utoa_dup(uadv(p->keyword)); |
d7482997 |
131 | } |
132 | } |
133 | } |
134 | |
135 | /* |
50d6b4bd |
136 | * Ensure the output file name has a .hlp extension. This is |
137 | * required since we must create the .cnt file in parallel with |
138 | * it. |
139 | */ |
140 | { |
141 | int len = strlen(filename); |
142 | if (len < 4 || filename[len-4] != '.' || |
143 | tolower(filename[len-3] != 'h') || |
144 | tolower(filename[len-2] != 'l') || |
145 | tolower(filename[len-1] != 'p')) { |
146 | char *newf; |
147 | newf = mknewa(char, len + 5); |
148 | sprintf(newf, "%s.hlp", filename); |
149 | sfree(filename); |
150 | filename = newf; |
151 | len = strlen(newf); |
152 | } |
153 | cntname = mknewa(char, len); |
154 | sprintf(cntname, "%.*s.cnt", len-4, filename); |
155 | } |
156 | |
157 | state.cntfp = fopen(cntname, "wb"); |
158 | state.cnt_last_level = -1; state.cnt_workaround = 0; |
159 | |
160 | /* |
d7482997 |
161 | * Loop over the source form registering WHLP_TOPICs for |
162 | * everything. |
163 | */ |
164 | |
165 | contents_topic = whlp_register_topic(h, "Top", NULL); |
166 | whlp_primary_topic(h, contents_topic); |
167 | for (p = sourceform; p; p = p->next) { |
168 | if (p->type == para_Chapter || |
169 | p->type == para_Appendix || |
170 | p->type == para_UnnumberedChapter || |
171 | p->type == para_Heading || |
172 | p->type == para_Subsect) { |
173 | char *topicid = p->private_data; |
174 | char *errstr; |
175 | |
176 | p->private_data = whlp_register_topic(h, topicid, &errstr); |
177 | if (!p->private_data) { |
178 | p->private_data = whlp_register_topic(h, NULL, NULL); |
179 | error(err_winhelp_ctxclash, &p->fpos, topicid, errstr); |
180 | } |
181 | sfree(topicid); |
182 | } |
183 | } |
184 | |
185 | /* |
186 | * Loop over the index entries, preparing final text forms for |
187 | * each one. |
188 | */ |
189 | for (i = 0; (ie = index234(idx->entries, i)) != NULL; i++) { |
190 | rdstringc rs = {0, 0, NULL}; |
191 | whlp_rdaddwc(&rs, ie->text); |
192 | ie->backend_data = rs.text; |
193 | } |
194 | |
195 | whlp_prepare(h); |
196 | |
197 | /* ------------------------------------------------------------------ |
8902e0ed |
198 | * Begin the contents page. |
d7482997 |
199 | */ |
200 | |
201 | whlp_begin_topic(h, contents_topic, "Contents", "DB(\"btn_up\")", NULL); |
202 | |
203 | /* |
204 | * The manual title goes in the non-scroll region, and also |
205 | * goes into the system title slot. |
206 | */ |
207 | { |
208 | rdstringc rs = {0, 0, NULL}; |
209 | for (p = sourceform; p; p = p->next) { |
210 | if (p->type == para_Title) { |
211 | whlp_begin_para(h, WHLP_PARA_NONSCROLL); |
212 | whlp_mkparagraph(&state, FONT_TITLE, p->words, FALSE); |
213 | whlp_rdaddwc(&rs, p->words); |
214 | whlp_end_para(h); |
215 | } |
216 | } |
217 | if (rs.text) { |
218 | whlp_title(h, rs.text); |
219 | fprintf(state.cntfp, ":Title %s\r\n", rs.text); |
220 | sfree(rs.text); |
221 | } |
222 | whlp_contents_write(&state, 1, "Title page", contents_topic); |
223 | /* FIXME: configurability in that string */ |
224 | } |
225 | |
9057a0a8 |
226 | /* |
227 | * Put the copyright into the system section. |
228 | */ |
229 | { |
230 | rdstringc rs = {0, 0, NULL}; |
231 | for (p = sourceform; p; p = p->next) { |
232 | if (p->type == para_Copyright) |
233 | whlp_rdaddwc(&rs, p->words); |
234 | } |
235 | if (rs.text) { |
236 | whlp_copyright(h, rs.text); |
237 | sfree(rs.text); |
238 | } |
239 | } |
240 | |
d7482997 |
241 | lastsect = NULL; |
242 | |
243 | /* ------------------------------------------------------------------ |
244 | * Now we've done the contents page, we're ready to go through |
245 | * and do the main manual text. Ooh. |
246 | */ |
7136a6c7 |
247 | nesting = 0; |
d7482997 |
248 | for (p = sourceform; p; p = p->next) switch (p->type) { |
249 | /* |
250 | * Things we ignore because we've already processed them or |
251 | * aren't going to touch them in this pass. |
252 | */ |
253 | case para_IM: |
254 | case para_BR: |
255 | case para_Biblio: /* only touch BiblioCited */ |
256 | case para_VersionID: |
d7482997 |
257 | case para_NoCite: |
258 | case para_Title: |
259 | break; |
260 | |
7136a6c7 |
261 | case para_LcontPush: |
2614b01d |
262 | case para_QuotePush: |
7136a6c7 |
263 | nesting++; |
264 | break; |
265 | case para_LcontPop: |
2614b01d |
266 | case para_QuotePop: |
7136a6c7 |
267 | assert(nesting > 0); |
268 | nesting--; |
269 | break; |
270 | |
d7482997 |
271 | /* |
272 | * Chapter and section titles: start a new Help topic. |
273 | */ |
274 | case para_Chapter: |
275 | case para_Appendix: |
276 | case para_UnnumberedChapter: |
277 | case para_Heading: |
278 | case para_Subsect: |
8902e0ed |
279 | |
280 | if (!done_contents_topic) { |
281 | paragraph *p; |
282 | |
283 | /* |
284 | * If this is the first section title we've seen, then |
285 | * we're currently still in the contents topic. We |
286 | * should therefore finish up the contents page by |
9057a0a8 |
287 | * writing a nav menu. |
8902e0ed |
288 | */ |
289 | for (p = sourceform; p; p = p->next) { |
290 | if (p->type == para_Chapter || |
291 | p->type == para_Appendix || |
292 | p->type == para_UnnumberedChapter) |
293 | whlp_navmenu(&state, p); |
294 | } |
295 | |
296 | state.curr_topic = contents_topic; |
297 | |
298 | done_contents_topic = TRUE; |
299 | } |
300 | |
d7482997 |
301 | if (lastsect && lastsect->child) { |
302 | paragraph *q; |
303 | /* |
304 | * Do a navigation menu for the previous section we |
305 | * were in. |
306 | */ |
307 | for (q = lastsect->child; q; q = q->sibling) |
308 | whlp_navmenu(&state, q); |
309 | } |
310 | { |
311 | rdstringc rs = {0, 0, NULL}; |
312 | WHLP_TOPIC new_topic, parent_topic; |
313 | char *macro, *topicid; |
314 | |
315 | new_topic = p->private_data; |
316 | whlp_browse_link(h, state.curr_topic, new_topic); |
317 | state.curr_topic = new_topic; |
318 | |
319 | if (p->kwtext) { |
320 | whlp_rdaddwc(&rs, p->kwtext); |
321 | rdaddsc(&rs, ": "); /* FIXME: configurability */ |
322 | } |
323 | whlp_rdaddwc(&rs, p->words); |
324 | if (p->parent == NULL) |
325 | parent_topic = contents_topic; |
326 | else |
327 | parent_topic = (WHLP_TOPIC)p->parent->private_data; |
328 | topicid = whlp_topic_id(parent_topic); |
329 | macro = smalloc(100+strlen(topicid)); |
330 | sprintf(macro, |
331 | "CBB(\"btn_up\",\"JI(`',`%s')\");EB(\"btn_up\")", |
332 | topicid); |
333 | whlp_begin_topic(h, new_topic, |
334 | rs.text ? rs.text : "", |
335 | macro, NULL); |
336 | sfree(macro); |
337 | |
338 | { |
339 | /* |
340 | * Output the .cnt entry. |
341 | * |
342 | * WinHelp has a bug involving having an internal |
343 | * node followed by a leaf at the same level: the |
344 | * leaf is output at the wrong level. We can mostly |
345 | * work around this by modifying the leaf level |
346 | * itself (see whlp_contents_write), but this |
347 | * doesn't work for top-level sections since we |
348 | * can't turn a level-1 leaf into a level-0 one. So |
349 | * for top-level leaf sections (Bibliography |
350 | * springs to mind), we output an internal node |
351 | * containing only the leaf for that section. |
352 | */ |
353 | int i; |
354 | paragraph *q; |
355 | |
356 | /* Count up the level. */ |
357 | i = 1; |
358 | for (q = p; q->parent; q = q->parent) i++; |
359 | |
360 | if (p->child || !p->parent) { |
361 | /* |
362 | * If p has children then it needs to be a |
363 | * folder; if it has no parent then it needs to |
364 | * be a folder to work around the bug. |
365 | */ |
366 | whlp_contents_write(&state, i, rs.text, NULL); |
367 | i++; |
368 | } |
369 | whlp_contents_write(&state, i, rs.text, new_topic); |
370 | } |
371 | |
372 | sfree(rs.text); |
373 | |
374 | whlp_begin_para(h, WHLP_PARA_NONSCROLL); |
375 | if (p->kwtext) { |
376 | whlp_mkparagraph(&state, FONT_TITLE, p->kwtext, FALSE); |
377 | whlp_set_font(h, FONT_TITLE); |
378 | whlp_text(h, ": "); /* FIXME: configurability */ |
379 | } |
380 | whlp_mkparagraph(&state, FONT_TITLE, p->words, FALSE); |
381 | whlp_end_para(h); |
382 | |
383 | lastsect = p; |
384 | } |
385 | break; |
386 | |
387 | case para_Rule: |
388 | whlp_para_attr(h, WHLP_PARA_SPACEBELOW, 12); |
389 | whlp_para_attr(h, WHLP_PARA_ALIGNMENT, WHLP_ALIGN_CENTRE); |
390 | whlp_begin_para(h, WHLP_PARA_SCROLL); |
391 | whlp_set_font(h, FONT_RULE); |
392 | #define TEN "\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0" |
393 | #define TWENTY TEN TEN |
394 | #define FORTY TWENTY TWENTY |
395 | #define EIGHTY FORTY FORTY |
396 | whlp_text(h, EIGHTY); |
397 | #undef TEN |
398 | #undef TWENTY |
399 | #undef FORTY |
400 | #undef EIGHTY |
401 | whlp_end_para(h); |
402 | break; |
403 | |
404 | case para_Normal: |
9057a0a8 |
405 | case para_Copyright: |
7136a6c7 |
406 | case para_DescribedThing: |
407 | case para_Description: |
d7482997 |
408 | case para_BiblioCited: |
409 | case para_Bullet: |
410 | case para_NumberedList: |
411 | whlp_para_attr(h, WHLP_PARA_SPACEBELOW, 12); |
412 | if (p->type == para_Bullet || p->type == para_NumberedList) { |
7136a6c7 |
413 | whlp_para_attr(h, WHLP_PARA_LEFTINDENT, 72*nesting + 72); |
d7482997 |
414 | whlp_para_attr(h, WHLP_PARA_FIRSTLINEINDENT, -36); |
415 | whlp_set_tabstop(h, 72, WHLP_ALIGN_LEFT); |
416 | whlp_begin_para(h, WHLP_PARA_SCROLL); |
417 | whlp_set_font(h, FONT_NORMAL); |
418 | if (p->type == para_Bullet) { |
419 | whlp_text(h, "\x95"); |
420 | } else { |
421 | whlp_mkparagraph(&state, FONT_NORMAL, p->kwtext, FALSE); |
422 | whlp_text(h, "."); |
423 | } |
424 | whlp_tab(h); |
425 | } else { |
7136a6c7 |
426 | whlp_para_attr(h, WHLP_PARA_LEFTINDENT, |
427 | 72*nesting + (p->type==para_Description ? 72 : 0)); |
d7482997 |
428 | whlp_begin_para(h, WHLP_PARA_SCROLL); |
429 | } |
430 | |
431 | if (p->type == para_BiblioCited) { |
432 | whlp_mkparagraph(&state, FONT_NORMAL, p->kwtext, FALSE); |
433 | whlp_text(h, " "); |
434 | } |
435 | |
436 | whlp_mkparagraph(&state, FONT_NORMAL, p->words, FALSE); |
437 | whlp_end_para(h); |
438 | break; |
439 | |
440 | case para_Code: |
441 | /* |
442 | * In a code paragraph, each individual word is a line. For |
443 | * Help files, we will have to output this as a set of |
444 | * paragraphs, all but the last of which don't set |
445 | * SPACEBELOW. |
446 | */ |
447 | { |
448 | word *w; |
4b3c5afb |
449 | wchar_t *t, *e; |
d7482997 |
450 | char *c; |
4b3c5afb |
451 | |
452 | for (w = p->words; w; w = w->next) if (w->type == word_WeakCode) { |
453 | t = w->text; |
454 | if (w->next && w->next->type == word_Emph) { |
455 | w = w->next; |
456 | e = w->text; |
457 | } else |
458 | e = NULL; |
459 | |
d7482997 |
460 | if (!w->next) |
461 | whlp_para_attr(h, WHLP_PARA_SPACEBELOW, 12); |
4b3c5afb |
462 | |
7136a6c7 |
463 | whlp_para_attr(h, WHLP_PARA_LEFTINDENT, 72*nesting); |
d7482997 |
464 | whlp_begin_para(h, WHLP_PARA_SCROLL); |
4b3c5afb |
465 | while (e && *e && *t) { |
466 | int n; |
467 | int ec = *e; |
468 | |
469 | for (n = 0; t[n] && e[n] && e[n] == ec; n++); |
470 | if (ec == 'i') |
471 | whlp_set_font(h, FONT_ITAL_CODE); |
472 | else if (ec == 'b') |
473 | whlp_set_font(h, FONT_BOLD_CODE); |
474 | else |
475 | whlp_set_font(h, FONT_CODE); |
476 | whlp_convert(t, n, &c, FALSE); |
477 | whlp_text(h, c); |
478 | sfree(c); |
479 | t += n; |
480 | e += n; |
481 | } |
d7482997 |
482 | whlp_set_font(h, FONT_CODE); |
4b3c5afb |
483 | whlp_convert(t, 0, &c, FALSE); |
d7482997 |
484 | whlp_text(h, c); |
485 | sfree(c); |
486 | whlp_end_para(h); |
487 | } |
488 | } |
489 | break; |
490 | } |
491 | |
492 | fclose(state.cntfp); |
493 | whlp_close(h, filename); |
494 | |
495 | /* |
496 | * Loop over the index entries, cleaning up our final text |
497 | * forms. |
498 | */ |
499 | for (i = 0; (ie = index234(idx->entries, i)) != NULL; i++) { |
500 | sfree(ie->backend_data); |
501 | } |
50d6b4bd |
502 | |
503 | sfree(filename); |
504 | sfree(cntname); |
d7482997 |
505 | } |
506 | |
507 | static void whlp_contents_write(struct bk_whlp_state *state, |
508 | int level, char *text, WHLP_TOPIC topic) { |
509 | /* |
510 | * Horrifying bug in WinHelp. When dropping a section level or |
511 | * more without using a folder-type entry, WinHelp accidentally |
512 | * adds one to the section level. So we correct for that here. |
513 | */ |
514 | if (state->cnt_last_level > level && topic) |
515 | state->cnt_workaround = -1; |
516 | else if (!topic) |
517 | state->cnt_workaround = 0; |
518 | state->cnt_last_level = level; |
519 | |
520 | fprintf(state->cntfp, "%d ", level + state->cnt_workaround); |
521 | while (*text) { |
522 | if (*text == '=') |
523 | fputc('\\', state->cntfp); |
524 | fputc(*text, state->cntfp); |
525 | text++; |
526 | } |
527 | if (topic) |
528 | fprintf(state->cntfp, "=%s", whlp_topic_id(topic)); |
529 | fputc('\n', state->cntfp); |
530 | } |
531 | |
532 | static void whlp_navmenu(struct bk_whlp_state *state, paragraph *p) { |
533 | whlp_begin_para(state->h, WHLP_PARA_NONSCROLL); |
534 | whlp_start_hyperlink(state->h, (WHLP_TOPIC)p->private_data); |
535 | if (p->kwtext) { |
536 | whlp_mkparagraph(state, FONT_NORMAL, p->kwtext, TRUE); |
537 | whlp_set_font(state->h, FONT_NORMAL); |
538 | whlp_text(state->h, ": "); /* FIXME: configurability */ |
539 | } |
540 | whlp_mkparagraph(state, FONT_NORMAL, p->words, TRUE); |
541 | whlp_end_hyperlink(state->h); |
542 | whlp_end_para(state->h); |
543 | |
544 | } |
545 | |
546 | static void whlp_mkparagraph(struct bk_whlp_state *state, |
547 | int font, word *text, int subsidiary) { |
548 | keyword *kwl; |
549 | int deffont = font; |
550 | int currfont = -1; |
551 | int newfont; |
552 | char *c; |
553 | paragraph *xref_target = NULL; |
554 | |
555 | for (; text; text = text->next) switch (text->type) { |
556 | case word_HyperLink: |
557 | case word_HyperEnd: |
558 | break; |
559 | |
560 | case word_IndexRef: |
561 | if (subsidiary) break; /* disabled in subsidiary bits */ |
562 | { |
563 | indextag *tag = index_findtag(state->idx, text->text); |
564 | int i; |
565 | if (!tag) |
566 | break; |
567 | for (i = 0; i < tag->nrefs; i++) |
568 | whlp_index_term(state->h, tag->refs[i]->backend_data, |
569 | state->curr_topic); |
570 | } |
571 | break; |
572 | |
573 | case word_UpperXref: |
574 | case word_LowerXref: |
575 | if (subsidiary) break; /* disabled in subsidiary bits */ |
576 | kwl = kw_lookup(state->keywords, text->text); |
7cf305b7 |
577 | assert(xref_target == NULL); |
578 | if (kwl) { |
579 | if (kwl->para->type == para_NumberedList) { |
580 | break; /* don't xref to numbered list items */ |
581 | } else if (kwl->para->type == para_BiblioCited) { |
582 | /* |
583 | * An xref to a bibliography item jumps to the section |
584 | * containing it. |
585 | */ |
586 | if (kwl->para->parent) |
587 | xref_target = kwl->para->parent; |
588 | else |
589 | break; |
590 | } else { |
591 | xref_target = kwl->para; |
592 | } |
593 | whlp_start_hyperlink(state->h, |
594 | (WHLP_TOPIC)xref_target->private_data); |
595 | } |
d7482997 |
596 | break; |
597 | |
598 | case word_XrefEnd: |
599 | if (subsidiary) break; /* disabled in subsidiary bits */ |
600 | if (xref_target) |
601 | whlp_end_hyperlink(state->h); |
602 | xref_target = NULL; |
603 | break; |
604 | |
605 | case word_Normal: |
606 | case word_Emph: |
607 | case word_Code: |
608 | case word_WeakCode: |
609 | case word_WhiteSpace: |
610 | case word_EmphSpace: |
611 | case word_CodeSpace: |
612 | case word_WkCodeSpace: |
613 | case word_Quote: |
614 | case word_EmphQuote: |
615 | case word_CodeQuote: |
616 | case word_WkCodeQuote: |
617 | if (towordstyle(text->type) == word_Emph) |
618 | newfont = deffont + FONT_EMPH; |
619 | else if (towordstyle(text->type) == word_Code || |
620 | towordstyle(text->type) == word_WeakCode) |
621 | newfont = deffont + FONT_CODE; |
622 | else |
623 | newfont = deffont; |
624 | if (newfont != currfont) { |
625 | currfont = newfont; |
626 | whlp_set_font(state->h, newfont); |
627 | } |
628 | if (removeattr(text->type) == word_Normal) { |
4b3c5afb |
629 | if (whlp_convert(text->text, 0, &c, TRUE)) |
d7482997 |
630 | whlp_text(state->h, c); |
631 | else |
632 | whlp_mkparagraph(state, deffont, text->alt, FALSE); |
633 | sfree(c); |
634 | } else if (removeattr(text->type) == word_WhiteSpace) { |
635 | whlp_text(state->h, " "); |
636 | } else if (removeattr(text->type) == word_Quote) { |
637 | whlp_text(state->h, |
638 | quoteaux(text->aux) == quote_Open ? "\x91" : "\x92"); |
639 | /* FIXME: configurability */ |
640 | } |
641 | break; |
642 | } |
643 | } |
644 | |
645 | static void whlp_rdaddwc(rdstringc *rs, word *text) { |
646 | char *c; |
647 | |
648 | for (; text; text = text->next) switch (text->type) { |
649 | case word_HyperLink: |
650 | case word_HyperEnd: |
651 | case word_UpperXref: |
652 | case word_LowerXref: |
653 | case word_XrefEnd: |
654 | case word_IndexRef: |
655 | break; |
656 | |
657 | case word_Normal: |
658 | case word_Emph: |
659 | case word_Code: |
660 | case word_WeakCode: |
661 | case word_WhiteSpace: |
662 | case word_EmphSpace: |
663 | case word_CodeSpace: |
664 | case word_WkCodeSpace: |
665 | case word_Quote: |
666 | case word_EmphQuote: |
667 | case word_CodeQuote: |
668 | case word_WkCodeQuote: |
669 | assert(text->type != word_CodeQuote && |
670 | text->type != word_WkCodeQuote); |
671 | if (removeattr(text->type) == word_Normal) { |
4b3c5afb |
672 | if (whlp_convert(text->text, 0, &c, FALSE)) |
d7482997 |
673 | rdaddsc(rs, c); |
674 | else |
675 | whlp_rdaddwc(rs, text->alt); |
676 | sfree(c); |
677 | } else if (removeattr(text->type) == word_WhiteSpace) { |
678 | rdaddc(rs, ' '); |
679 | } else if (removeattr(text->type) == word_Quote) { |
680 | rdaddc(rs, quoteaux(text->aux) == quote_Open ? '\x91' : '\x92'); |
681 | /* FIXME: configurability */ |
682 | } |
683 | break; |
684 | } |
685 | } |
686 | |
687 | /* |
688 | * Convert a wide string into a string of chars. If `result' is |
689 | * non-NULL, mallocs the resulting string and stores a pointer to |
690 | * it in `*result'. If `result' is NULL, merely checks whether all |
691 | * characters in the string are feasible for the output character |
692 | * set. |
693 | * |
694 | * Return is nonzero if all characters are OK. If not all |
695 | * characters are OK but `result' is non-NULL, a result _will_ |
696 | * still be generated! |
697 | */ |
4b3c5afb |
698 | static int whlp_convert(wchar_t *s, int maxlen, |
699 | char **result, int hard_spaces) { |
d7482997 |
700 | /* |
701 | * FIXME. Currently this is ISO8859-1 only. |
702 | */ |
703 | int doing = (result != 0); |
704 | int ok = TRUE; |
705 | char *p = NULL; |
706 | int plen = 0, psize = 0; |
707 | |
4b3c5afb |
708 | if (maxlen <= 0) |
709 | maxlen = -1; |
710 | |
711 | for (; *s && maxlen != 0; s++, maxlen--) { |
d7482997 |
712 | wchar_t c = *s; |
713 | char outc; |
714 | |
715 | if ((c >= 32 && c <= 126) || |
716 | (c >= 160 && c <= 255)) { |
717 | /* Char is OK. */ |
718 | if (c == 32 && hard_spaces) |
719 | outc = '\240'; |
720 | else |
721 | outc = (char)c; |
722 | } else { |
723 | /* Char is not OK. */ |
724 | ok = FALSE; |
725 | outc = 0xBF; /* approximate the good old DEC `uh?' */ |
726 | } |
727 | if (doing) { |
728 | if (plen >= psize) { |
729 | psize = plen + 256; |
730 | p = resize(p, psize); |
731 | } |
732 | p[plen++] = outc; |
733 | } |
734 | } |
735 | if (doing) { |
736 | p = resize(p, plen+1); |
737 | p[plen] = '\0'; |
738 | *result = p; |
739 | } |
740 | return ok; |
741 | } |