Arrange a mechanism whereby each backend can be passed a filename
[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 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,
29 FONT_ITAL_CODE,
30 FONT_BOLD_CODE,
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);
38 static int whlp_convert(wchar_t *s, int maxlen,
39 char **result, int hard_spaces);
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
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
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;
82 int nesting;
83 indexentry *ie;
84 int done_contents_topic = FALSE;
85
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);
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);
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
115 * specified particular help topic names for anything. Also
116 * pick out the output file name at this stage.
117 */
118 filename = dupstr("output.hlp");
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;
124 whlp_convert(uadv(p->keyword), 0, &topicname, 0);
125 /* Store the topic name in the private_data field of the
126 * containing section. */
127 p->parent->private_data = topicname;
128 } else if (!ustricmp(p->keyword, L"winhelp-filename")) {
129 sfree(filename);
130 filename = utoa_dup(uadv(p->keyword));
131 }
132 }
133 }
134
135 /*
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 /*
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 /* ------------------------------------------------------------------
198 * Begin the contents page.
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
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
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 */
247 nesting = 0;
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:
257 case para_NoCite:
258 case para_Title:
259 break;
260
261 case para_LcontPush:
262 case para_QuotePush:
263 nesting++;
264 break;
265 case para_LcontPop:
266 case para_QuotePop:
267 assert(nesting > 0);
268 nesting--;
269 break;
270
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:
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
287 * writing a nav menu.
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
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:
405 case para_Copyright:
406 case para_DescribedThing:
407 case para_Description:
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) {
413 whlp_para_attr(h, WHLP_PARA_LEFTINDENT, 72*nesting + 72);
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 {
426 whlp_para_attr(h, WHLP_PARA_LEFTINDENT,
427 72*nesting + (p->type==para_Description ? 72 : 0));
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;
449 wchar_t *t, *e;
450 char *c;
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
460 if (!w->next)
461 whlp_para_attr(h, WHLP_PARA_SPACEBELOW, 12);
462
463 whlp_para_attr(h, WHLP_PARA_LEFTINDENT, 72*nesting);
464 whlp_begin_para(h, WHLP_PARA_SCROLL);
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 }
482 whlp_set_font(h, FONT_CODE);
483 whlp_convert(t, 0, &c, FALSE);
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 }
502
503 sfree(filename);
504 sfree(cntname);
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);
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 }
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) {
629 if (whlp_convert(text->text, 0, &c, TRUE))
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) {
672 if (whlp_convert(text->text, 0, &c, FALSE))
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 */
698 static int whlp_convert(wchar_t *s, int maxlen,
699 char **result, int hard_spaces) {
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
708 if (maxlen <= 0)
709 maxlen = -1;
710
711 for (; *s && maxlen != 0; s++, maxlen--) {
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 }