Initial work on PS and PDF output. Because these two backends share
[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, void *unused) {
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 IGNORE(unused);
87
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);
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);
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
117 * specified particular help topic names for anything. Also
118 * pick out the output file name at this stage.
119 */
120 filename = dupstr("output.hlp");
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;
126 whlp_convert(uadv(p->keyword), 0, &topicname, 0);
127 /* Store the topic name in the private_data field of the
128 * containing section. */
129 p->parent->private_data = topicname;
130 } else if (!ustricmp(p->keyword, L"winhelp-filename")) {
131 sfree(filename);
132 filename = utoa_dup(uadv(p->keyword));
133 }
134 }
135 }
136
137 /*
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 /*
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 /* ------------------------------------------------------------------
200 * Begin the contents page.
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
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
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 */
249 nesting = 0;
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:
259 case para_NoCite:
260 case para_Title:
261 break;
262
263 case para_LcontPush:
264 case para_QuotePush:
265 nesting++;
266 break;
267 case para_LcontPop:
268 case para_QuotePop:
269 assert(nesting > 0);
270 nesting--;
271 break;
272
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:
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
289 * writing a nav menu.
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
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:
407 case para_Copyright:
408 case para_DescribedThing:
409 case para_Description:
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) {
415 whlp_para_attr(h, WHLP_PARA_LEFTINDENT, 72*nesting + 72);
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 {
428 whlp_para_attr(h, WHLP_PARA_LEFTINDENT,
429 72*nesting + (p->type==para_Description ? 72 : 0));
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;
451 wchar_t *t, *e;
452 char *c;
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
462 if (!w->next)
463 whlp_para_attr(h, WHLP_PARA_SPACEBELOW, 12);
464
465 whlp_para_attr(h, WHLP_PARA_LEFTINDENT, 72*nesting);
466 whlp_begin_para(h, WHLP_PARA_SCROLL);
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 }
484 whlp_set_font(h, FONT_CODE);
485 whlp_convert(t, 0, &c, FALSE);
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 }
504
505 sfree(filename);
506 sfree(cntname);
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);
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 }
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) {
631 if (whlp_convert(text->text, 0, &c, TRUE))
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) {
674 if (whlp_convert(text->text, 0, &c, FALSE))
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 */
700 static int whlp_convert(wchar_t *s, int maxlen,
701 char **result, int hard_spaces) {
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
710 if (maxlen <= 0)
711 maxlen = -1;
712
713 for (; *s && maxlen != 0; s++, maxlen--) {
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 }