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