The length parameters in mbstowcs and wcstombs are limits on the
[sgt/halibut] / bk_text.c
1 /*
2 * text backend for Halibut
3 */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <assert.h>
8 #include "halibut.h"
9
10 typedef enum { LEFT, LEFTPLUS, CENTRE } alignment;
11 typedef struct {
12 alignment align;
13 int just_numbers;
14 wchar_t *underline;
15 wchar_t *number_suffix;
16 } alignstruct;
17
18 typedef struct {
19 int indent, indent_code;
20 int listindentbefore, listindentafter;
21 int width;
22 alignstruct atitle, achapter, *asect;
23 int nasect;
24 int include_version_id;
25 int indent_preambles;
26 int charset;
27 word bullet;
28 wchar_t *lquote, *rquote, *rule;
29 char *filename;
30 wchar_t *listsuffix, *startemph, *endemph;
31 } textconfig;
32
33 typedef struct {
34 FILE *fp;
35 int charset;
36 charset_state state;
37 } textfile;
38
39 static void text_heading(textfile *, word *, word *, word *, alignstruct,
40 int, int, textconfig *);
41 static void text_rule(textfile *, int, int, textconfig *);
42 static void text_para(textfile *, word *, wchar_t *, word *, int, int, int,
43 textconfig *);
44 static void text_codepara(textfile *, word *, int, int);
45 static void text_versionid(textfile *, word *, textconfig *);
46
47 static void text_output(textfile *, const wchar_t *);
48 static void text_output_many(textfile *, int, wchar_t);
49
50 static alignment utoalign(wchar_t *p) {
51 if (!ustricmp(p, L"centre") || !ustricmp(p, L"center"))
52 return CENTRE;
53 if (!ustricmp(p, L"leftplus"))
54 return LEFTPLUS;
55 return LEFT;
56 }
57
58 static textconfig text_configure(paragraph *source) {
59 textconfig ret;
60 paragraph *p;
61 int n;
62
63 /*
64 * Non-negotiables.
65 */
66 ret.bullet.next = NULL;
67 ret.bullet.alt = NULL;
68 ret.bullet.type = word_Normal;
69 ret.atitle.just_numbers = FALSE; /* ignored */
70
71 /*
72 * Defaults.
73 */
74 ret.indent = 7;
75 ret.indent_code = 2;
76 ret.listindentbefore = 1;
77 ret.listindentafter = 3;
78 ret.width = 68;
79 ret.atitle.align = CENTRE;
80 ret.atitle.underline = L"\x2550\0=\0\0";
81 ret.achapter.align = LEFT;
82 ret.achapter.just_numbers = FALSE;
83 ret.achapter.number_suffix = L": ";
84 ret.achapter.underline = L"\x203E\0-\0\0";
85 ret.nasect = 1;
86 ret.asect = snewn(ret.nasect, alignstruct);
87 ret.asect[0].align = LEFTPLUS;
88 ret.asect[0].just_numbers = TRUE;
89 ret.asect[0].number_suffix = L" ";
90 ret.asect[0].underline = L"\0";
91 ret.include_version_id = TRUE;
92 ret.indent_preambles = FALSE;
93 ret.bullet.text = L"\x2022\0-\0\0";
94 ret.rule = L"\x2500\0-\0\0";
95 ret.filename = dupstr("output.txt");
96 ret.startemph = L"_\0_\0\0";
97 ret.endemph = uadv(ret.startemph);
98 ret.listsuffix = L".";
99 ret.charset = CS_ASCII;
100 /*
101 * Default quote characters are Unicode matched single quotes,
102 * falling back to the TeXlike `'.
103 */
104 ret.lquote = L"\x2018\0\x2019\0`\0'\0\0";
105 ret.rquote = uadv(ret.lquote);
106
107 /*
108 * Two-pass configuration so that we can pick up global config
109 * (e.g. `quotes') before having it overridden by specific
110 * config (`text-quotes'), irrespective of the order in which
111 * they occur.
112 */
113 for (p = source; p; p = p->next) {
114 if (p->type == para_Config) {
115 if (!ustricmp(p->keyword, L"quotes")) {
116 if (*uadv(p->keyword) && *uadv(uadv(p->keyword))) {
117 ret.lquote = uadv(p->keyword);
118 ret.rquote = uadv(ret.lquote);
119 }
120 }
121 }
122 }
123
124 for (p = source; p; p = p->next) {
125 if (p->type == para_Config) {
126 if (!ustricmp(p->keyword, L"text-indent")) {
127 ret.indent = utoi(uadv(p->keyword));
128 } else if (!ustricmp(p->keyword, L"text-charset")) {
129 ret.charset = charset_from_ustr(&p->fpos, uadv(p->keyword));
130 } else if (!ustricmp(p->keyword, L"text-filename")) {
131 sfree(ret.filename);
132 ret.filename = dupstr(adv(p->origkeyword));
133 } else if (!ustricmp(p->keyword, L"text-indent-code")) {
134 ret.indent_code = utoi(uadv(p->keyword));
135 } else if (!ustricmp(p->keyword, L"text-width")) {
136 ret.width = utoi(uadv(p->keyword));
137 } else if (!ustricmp(p->keyword, L"text-list-indent")) {
138 ret.listindentbefore = utoi(uadv(p->keyword));
139 } else if (!ustricmp(p->keyword, L"text-listitem-indent")) {
140 ret.listindentafter = utoi(uadv(p->keyword));
141 } else if (!ustricmp(p->keyword, L"text-chapter-align")) {
142 ret.achapter.align = utoalign(uadv(p->keyword));
143 } else if (!ustricmp(p->keyword, L"text-chapter-underline")) {
144 ret.achapter.underline = uadv(p->keyword);
145 } else if (!ustricmp(p->keyword, L"text-chapter-numeric")) {
146 ret.achapter.just_numbers = utob(uadv(p->keyword));
147 } else if (!ustricmp(p->keyword, L"text-chapter-suffix")) {
148 ret.achapter.number_suffix = uadv(p->keyword);
149 } else if (!ustricmp(p->keyword, L"text-section-align")) {
150 wchar_t *q = uadv(p->keyword);
151 int n = 0;
152 if (uisdigit(*q)) {
153 n = utoi(q);
154 q = uadv(q);
155 }
156 if (n >= ret.nasect) {
157 int i;
158 ret.asect = sresize(ret.asect, n+1, alignstruct);
159 for (i = ret.nasect; i <= n; i++)
160 ret.asect[i] = ret.asect[ret.nasect-1];
161 ret.nasect = n+1;
162 }
163 ret.asect[n].align = utoalign(q);
164 } else if (!ustricmp(p->keyword, L"text-section-underline")) {
165 wchar_t *q = uadv(p->keyword);
166 int n = 0;
167 if (uisdigit(*q)) {
168 n = utoi(q);
169 q = uadv(q);
170 }
171 if (n >= ret.nasect) {
172 int i;
173 ret.asect = sresize(ret.asect, n+1, alignstruct);
174 for (i = ret.nasect; i <= n; i++)
175 ret.asect[i] = ret.asect[ret.nasect-1];
176 ret.nasect = n+1;
177 }
178 ret.asect[n].underline = q;
179 } else if (!ustricmp(p->keyword, L"text-section-numeric")) {
180 wchar_t *q = uadv(p->keyword);
181 int n = 0;
182 if (uisdigit(*q)) {
183 n = utoi(q);
184 q = uadv(q);
185 }
186 if (n >= ret.nasect) {
187 int i;
188 ret.asect = sresize(ret.asect, n+1, alignstruct);
189 for (i = ret.nasect; i <= n; i++)
190 ret.asect[i] = ret.asect[ret.nasect-1];
191 ret.nasect = n+1;
192 }
193 ret.asect[n].just_numbers = utob(q);
194 } else if (!ustricmp(p->keyword, L"text-section-suffix")) {
195 wchar_t *q = uadv(p->keyword);
196 int n = 0;
197 if (uisdigit(*q)) {
198 n = utoi(q);
199 q = uadv(q);
200 }
201 if (n >= ret.nasect) {
202 int i;
203 ret.asect = sresize(ret.asect, n+1, alignstruct);
204 for (i = ret.nasect; i <= n; i++) {
205 ret.asect[i] = ret.asect[ret.nasect-1];
206 }
207 ret.nasect = n+1;
208 }
209 ret.asect[n].number_suffix = q;
210 } else if (!ustricmp(p->keyword, L"text-title-align")) {
211 ret.atitle.align = utoalign(uadv(p->keyword));
212 } else if (!ustricmp(p->keyword, L"text-title-underline")) {
213 ret.atitle.underline = uadv(p->keyword);
214 } else if (!ustricmp(p->keyword, L"text-versionid")) {
215 ret.include_version_id = utob(uadv(p->keyword));
216 } else if (!ustricmp(p->keyword, L"text-indent-preamble")) {
217 ret.indent_preambles = utob(uadv(p->keyword));
218 } else if (!ustricmp(p->keyword, L"text-bullet")) {
219 ret.bullet.text = uadv(p->keyword);
220 } else if (!ustricmp(p->keyword, L"text-rule")) {
221 ret.rule = uadv(p->keyword);
222 } else if (!ustricmp(p->keyword, L"text-list-suffix")) {
223 ret.listsuffix = uadv(p->keyword);
224 } else if (!ustricmp(p->keyword, L"text-emphasis")) {
225 if (*uadv(p->keyword) && *uadv(uadv(p->keyword))) {
226 ret.startemph = uadv(p->keyword);
227 ret.endemph = uadv(ret.startemph);
228 }
229 } else if (!ustricmp(p->keyword, L"text-quotes")) {
230 if (*uadv(p->keyword) && *uadv(uadv(p->keyword))) {
231 ret.lquote = uadv(p->keyword);
232 ret.rquote = uadv(ret.lquote);
233 }
234 }
235 }
236 }
237
238 /*
239 * Now process fallbacks on quote characters, underlines, the
240 * rule character, the emphasis characters, and bullets.
241 */
242 while (*uadv(ret.rquote) && *uadv(uadv(ret.rquote)) &&
243 (!cvt_ok(ret.charset, ret.lquote) ||
244 !cvt_ok(ret.charset, ret.rquote))) {
245 ret.lquote = uadv(ret.rquote);
246 ret.rquote = uadv(ret.lquote);
247 }
248
249 while (*uadv(ret.endemph) && *uadv(uadv(ret.endemph)) &&
250 (!cvt_ok(ret.charset, ret.startemph) ||
251 !cvt_ok(ret.charset, ret.endemph))) {
252 ret.startemph = uadv(ret.endemph);
253 ret.endemph = uadv(ret.startemph);
254 }
255
256 while (*ret.atitle.underline && *uadv(ret.atitle.underline) &&
257 !cvt_ok(ret.charset, ret.atitle.underline))
258 ret.atitle.underline = uadv(ret.atitle.underline);
259
260 while (*ret.achapter.underline && *uadv(ret.achapter.underline) &&
261 !cvt_ok(ret.charset, ret.achapter.underline))
262 ret.achapter.underline = uadv(ret.achapter.underline);
263
264 for (n = 0; n < ret.nasect; n++) {
265 while (*ret.asect[n].underline && *uadv(ret.asect[n].underline) &&
266 !cvt_ok(ret.charset, ret.asect[n].underline))
267 ret.asect[n].underline = uadv(ret.asect[n].underline);
268 }
269
270 while (*ret.bullet.text && *uadv(ret.bullet.text) &&
271 !cvt_ok(ret.charset, ret.bullet.text))
272 ret.bullet.text = uadv(ret.bullet.text);
273
274 while (*ret.rule && *uadv(ret.rule) &&
275 !cvt_ok(ret.charset, ret.rule))
276 ret.rule = uadv(ret.rule);
277
278 return ret;
279 }
280
281 paragraph *text_config_filename(char *filename)
282 {
283 return cmdline_cfg_simple("text-filename", filename, NULL);
284 }
285
286 void text_backend(paragraph *sourceform, keywordlist *keywords,
287 indexdata *idx, void *unused) {
288 paragraph *p;
289 textconfig conf;
290 word *prefix, *body, *wp;
291 word spaceword;
292 textfile tf;
293 wchar_t *prefixextra;
294 int nesting, nestindent;
295 int indentb, indenta;
296
297 IGNORE(unused);
298 IGNORE(keywords); /* we don't happen to need this */
299 IGNORE(idx); /* or this */
300
301 conf = text_configure(sourceform);
302
303 /*
304 * Open the output file.
305 */
306 tf.fp = fopen(conf.filename, "w");
307 if (!tf.fp) {
308 error(err_cantopenw, conf.filename);
309 return;
310 }
311 tf.charset = conf.charset;
312 tf.state = charset_init_state;
313
314 /* Do the title */
315 for (p = sourceform; p; p = p->next)
316 if (p->type == para_Title)
317 text_heading(&tf, NULL, NULL, p->words,
318 conf.atitle, conf.indent, conf.width, &conf);
319
320 nestindent = conf.listindentbefore + conf.listindentafter;
321 nesting = (conf.indent_preambles ? 0 : -conf.indent);
322
323 /* Do the main document */
324 for (p = sourceform; p; p = p->next) switch (p->type) {
325
326 case para_QuotePush:
327 nesting += 2;
328 break;
329 case para_QuotePop:
330 nesting -= 2;
331 assert(nesting >= 0);
332 break;
333
334 case para_LcontPush:
335 nesting += nestindent;
336 break;
337 case para_LcontPop:
338 nesting -= nestindent;
339 assert(nesting >= 0);
340 break;
341
342 /*
343 * Things we ignore because we've already processed them or
344 * aren't going to touch them in this pass.
345 */
346 case para_IM:
347 case para_BR:
348 case para_Biblio: /* only touch BiblioCited */
349 case para_VersionID:
350 case para_NoCite:
351 case para_Title:
352 break;
353
354 /*
355 * Chapter titles.
356 */
357 case para_Chapter:
358 case para_Appendix:
359 case para_UnnumberedChapter:
360 text_heading(&tf, p->kwtext, p->kwtext2, p->words,
361 conf.achapter, conf.indent, conf.width, &conf);
362 nesting = 0;
363 break;
364
365 case para_Heading:
366 case para_Subsect:
367 text_heading(&tf, p->kwtext, p->kwtext2, p->words,
368 conf.asect[p->aux>=conf.nasect ? conf.nasect-1 : p->aux],
369 conf.indent, conf.width, &conf);
370 break;
371
372 case para_Rule:
373 text_rule(&tf, conf.indent + nesting, conf.width - nesting, &conf);
374 break;
375
376 case para_Normal:
377 case para_Copyright:
378 case para_DescribedThing:
379 case para_Description:
380 case para_BiblioCited:
381 case para_Bullet:
382 case para_NumberedList:
383 if (p->type == para_Bullet) {
384 prefix = &conf.bullet;
385 prefixextra = NULL;
386 indentb = conf.listindentbefore;
387 indenta = conf.listindentafter;
388 } else if (p->type == para_NumberedList) {
389 prefix = p->kwtext;
390 prefixextra = conf.listsuffix;
391 indentb = conf.listindentbefore;
392 indenta = conf.listindentafter;
393 } else if (p->type == para_Description) {
394 prefix = NULL;
395 prefixextra = NULL;
396 indentb = conf.listindentbefore;
397 indenta = conf.listindentafter;
398 } else {
399 prefix = NULL;
400 prefixextra = NULL;
401 indentb = indenta = 0;
402 }
403 if (p->type == para_BiblioCited) {
404 body = dup_word_list(p->kwtext);
405 for (wp = body; wp->next; wp = wp->next);
406 wp->next = &spaceword;
407 spaceword.next = p->words;
408 spaceword.alt = NULL;
409 spaceword.type = word_WhiteSpace;
410 spaceword.text = NULL;
411 } else {
412 wp = NULL;
413 body = p->words;
414 }
415 text_para(&tf, prefix, prefixextra, body,
416 conf.indent + nesting + indentb, indenta,
417 conf.width - nesting - indentb - indenta, &conf);
418 if (wp) {
419 wp->next = NULL;
420 free_word_list(body);
421 }
422 break;
423
424 case para_Code:
425 text_codepara(&tf, p->words,
426 conf.indent + nesting + conf.indent_code,
427 conf.width - nesting - 2 * conf.indent_code);
428 break;
429 }
430
431 /* Do the version ID */
432 if (conf.include_version_id) {
433 for (p = sourceform; p; p = p->next)
434 if (p->type == para_VersionID)
435 text_versionid(&tf, p->words, &conf);
436 }
437
438 /*
439 * Tidy up
440 */
441 text_output(&tf, NULL); /* end charset conversion */
442 fclose(tf.fp);
443 sfree(conf.asect);
444 sfree(conf.filename);
445 }
446
447 static void text_output(textfile *tf, const wchar_t *s)
448 {
449 char buf[256];
450 int ret, len;
451 const wchar_t **sp;
452
453 if (!s) {
454 sp = NULL;
455 len = 1;
456 } else {
457 sp = &s;
458 len = ustrlen(s);
459 }
460
461 while (len > 0) {
462 ret = charset_from_unicode(sp, &len, buf, lenof(buf),
463 tf->charset, &tf->state, NULL);
464 if (!sp)
465 len = 0;
466 fwrite(buf, 1, ret, tf->fp);
467 }
468 }
469
470 static void text_output_many(textfile *tf, int n, wchar_t c)
471 {
472 wchar_t s[2];
473 s[0] = c;
474 s[1] = L'\0';
475 while (n--)
476 text_output(tf, s);
477 }
478
479 static void text_rdaddw(rdstring *rs, word *text, word *end, textconfig *cfg) {
480 for (; text && text != end; text = text->next) switch (text->type) {
481 case word_HyperLink:
482 case word_HyperEnd:
483 case word_UpperXref:
484 case word_LowerXref:
485 case word_XrefEnd:
486 case word_IndexRef:
487 break;
488
489 case word_Normal:
490 case word_Emph:
491 case word_Code:
492 case word_WeakCode:
493 case word_WhiteSpace:
494 case word_EmphSpace:
495 case word_CodeSpace:
496 case word_WkCodeSpace:
497 case word_Quote:
498 case word_EmphQuote:
499 case word_CodeQuote:
500 case word_WkCodeQuote:
501 assert(text->type != word_CodeQuote &&
502 text->type != word_WkCodeQuote);
503 if (towordstyle(text->type) == word_Emph &&
504 (attraux(text->aux) == attr_First ||
505 attraux(text->aux) == attr_Only))
506 rdadds(rs, cfg->startemph);
507 else if (towordstyle(text->type) == word_Code &&
508 (attraux(text->aux) == attr_First ||
509 attraux(text->aux) == attr_Only))
510 rdadds(rs, cfg->lquote);
511 if (removeattr(text->type) == word_Normal) {
512 if (cvt_ok(cfg->charset, text->text) || !text->alt)
513 rdadds(rs, text->text);
514 else
515 text_rdaddw(rs, text->alt, NULL, cfg);
516 } else if (removeattr(text->type) == word_WhiteSpace) {
517 rdadd(rs, L' ');
518 } else if (removeattr(text->type) == word_Quote) {
519 rdadds(rs, quoteaux(text->aux) == quote_Open ?
520 cfg->lquote : cfg->rquote);
521 }
522 if (towordstyle(text->type) == word_Emph &&
523 (attraux(text->aux) == attr_Last ||
524 attraux(text->aux) == attr_Only))
525 rdadds(rs, cfg->endemph);
526 else if (towordstyle(text->type) == word_Code &&
527 (attraux(text->aux) == attr_Last ||
528 attraux(text->aux) == attr_Only))
529 rdadds(rs, cfg->rquote);
530 break;
531 }
532 }
533
534 static int text_width(void *, word *);
535
536 static int text_width_list(void *ctx, word *text) {
537 int w = 0;
538 while (text) {
539 w += text_width(ctx, text);
540 text = text->next;
541 }
542 return w;
543 }
544
545 static int text_width(void *ctx, word *text) {
546 textconfig *cfg = (textconfig *)ctx;
547 int wid;
548 int attr;
549
550 switch (text->type) {
551 case word_HyperLink:
552 case word_HyperEnd:
553 case word_UpperXref:
554 case word_LowerXref:
555 case word_XrefEnd:
556 case word_IndexRef:
557 return 0;
558 }
559
560 assert(text->type < word_internal_endattrs);
561
562 wid = 0;
563 attr = towordstyle(text->type);
564 if (attr == word_Emph || attr == word_Code) {
565 if (attraux(text->aux) == attr_Only ||
566 attraux(text->aux) == attr_First)
567 wid += ustrwid(attr == word_Emph ? cfg->startemph : cfg->lquote,
568 cfg->charset);
569 }
570 if (attr == word_Emph || attr == word_Code) {
571 if (attraux(text->aux) == attr_Only ||
572 attraux(text->aux) == attr_Last)
573 wid += ustrwid(attr == word_Emph ? cfg->startemph : cfg->lquote,
574 cfg->charset);
575 }
576
577 switch (text->type) {
578 case word_Normal:
579 case word_Emph:
580 case word_Code:
581 case word_WeakCode:
582 if (cvt_ok(cfg->charset, text->text) || !text->alt)
583 wid += ustrwid(text->text, cfg->charset);
584 else
585 wid += text_width_list(ctx, text->alt);
586 return wid;
587
588 case word_WhiteSpace:
589 case word_EmphSpace:
590 case word_CodeSpace:
591 case word_WkCodeSpace:
592 case word_Quote:
593 case word_EmphQuote:
594 case word_CodeQuote:
595 case word_WkCodeQuote:
596 assert(text->type != word_CodeQuote &&
597 text->type != word_WkCodeQuote);
598 if (removeattr(text->type) == word_Quote) {
599 if (quoteaux(text->aux) == quote_Open)
600 wid += ustrwid(cfg->lquote, cfg->charset);
601 else
602 wid += ustrwid(cfg->rquote, cfg->charset);
603 } else
604 wid++; /* space */
605 }
606
607 return wid;
608 }
609
610 static void text_heading(textfile *tf, word *tprefix, word *nprefix,
611 word *text, alignstruct align,
612 int indent, int width, textconfig *cfg) {
613 rdstring t = { 0, 0, NULL };
614 int margin, length;
615 int firstlinewidth, wrapwidth;
616 wrappedline *wrapping, *p;
617
618 if (align.just_numbers && nprefix) {
619 text_rdaddw(&t, nprefix, NULL, cfg);
620 rdadds(&t, align.number_suffix);
621 } else if (!align.just_numbers && tprefix) {
622 text_rdaddw(&t, tprefix, NULL, cfg);
623 rdadds(&t, align.number_suffix);
624 }
625 margin = length = ustrwid(t.text ? t.text : L"", cfg->charset);
626
627 if (align.align == LEFTPLUS) {
628 margin = indent - margin;
629 if (margin < 0) margin = 0;
630 firstlinewidth = indent + width - margin - length;
631 wrapwidth = width;
632 } else if (align.align == LEFT || align.align == CENTRE) {
633 margin = 0;
634 firstlinewidth = indent + width - length;
635 wrapwidth = indent + width;
636 }
637
638 wrapping = wrap_para(text, firstlinewidth, wrapwidth,
639 text_width, cfg, 0);
640 for (p = wrapping; p; p = p->next) {
641 text_rdaddw(&t, p->begin, p->end, cfg);
642 length = ustrwid(t.text ? t.text : L"", cfg->charset);
643 if (align.align == CENTRE) {
644 margin = (indent + width - length)/2;
645 if (margin < 0) margin = 0;
646 }
647 text_output_many(tf, margin, L' ');
648 text_output(tf, t.text);
649 text_output(tf, L"\n");
650 if (*align.underline) {
651 text_output_many(tf, margin, L' ');
652 while (length > 0) {
653 text_output(tf, align.underline);
654 length -= ustrwid(align.underline, cfg->charset);
655 }
656 text_output(tf, L"\n");
657 }
658 if (align.align == LEFTPLUS)
659 margin = indent;
660 else
661 margin = 0;
662 sfree(t.text);
663 t = empty_rdstring;
664 }
665 wrap_free(wrapping);
666 text_output(tf, L"\n");
667
668 sfree(t.text);
669 }
670
671 static void text_rule(textfile *tf, int indent, int width, textconfig *cfg) {
672 text_output_many(tf, indent, L' ');
673 while (width > 0) {
674 text_output(tf, cfg->rule);
675 width -= ustrwid(cfg->rule, cfg->charset);
676 }
677 text_output_many(tf, 2, L'\n');
678 }
679
680 static void text_para(textfile *tf, word *prefix, wchar_t *prefixextra,
681 word *text, int indent, int extraindent, int width,
682 textconfig *cfg) {
683 wrappedline *wrapping, *p;
684 rdstring pfx = { 0, 0, NULL };
685 int e;
686 int firstlinewidth = width;
687
688 if (prefix) {
689 text_rdaddw(&pfx, prefix, NULL, cfg);
690 if (prefixextra)
691 rdadds(&pfx, prefixextra);
692 text_output_many(tf, indent, L' ');
693 text_output(tf, pfx.text);
694 /* If the prefix is too long, shorten the first line to fit. */
695 e = extraindent - ustrwid(pfx.text ? pfx.text : L"", cfg->charset);
696 if (e < 0) {
697 firstlinewidth += e; /* this decreases it, since e < 0 */
698 if (firstlinewidth < 0) {
699 e = indent + extraindent;
700 firstlinewidth = width;
701 text_output(tf, L"\n");
702 } else
703 e = 0;
704 }
705 sfree(pfx.text);
706 } else
707 e = indent + extraindent;
708
709 wrapping = wrap_para(text, firstlinewidth, width,
710 text_width, cfg, 0);
711 for (p = wrapping; p; p = p->next) {
712 rdstring t = { 0, 0, NULL };
713 text_rdaddw(&t, p->begin, p->end, cfg);
714 text_output_many(tf, e, L' ');
715 text_output(tf, t.text);
716 text_output(tf, L"\n");
717 e = indent + extraindent;
718 sfree(t.text);
719 }
720 wrap_free(wrapping);
721 text_output(tf, L"\n");
722 }
723
724 static void text_codepara(textfile *tf, word *text, int indent, int width) {
725 for (; text; text = text->next) if (text->type == word_WeakCode) {
726 int wid = ustrwid(text->text, tf->charset);
727 if (wid > width)
728 error(err_text_codeline, &text->fpos, wid, width);
729 text_output_many(tf, indent, L' ');
730 text_output(tf, text->text);
731 text_output(tf, L"\n");
732 }
733
734 text_output(tf, L"\n");
735 }
736
737 static void text_versionid(textfile *tf, word *text, textconfig *cfg) {
738 rdstring t = { 0, 0, NULL };
739
740 rdadd(&t, L'[');
741 text_rdaddw(&t, text, NULL, cfg);
742 rdadd(&t, L']');
743 rdadd(&t, L'\n');
744
745 text_output(tf, t.text);
746 sfree(t.text);
747 }