Cope with TrueType fonts with slightly broken cmaps, just ignoring code points
[sgt/halibut] / bk_text.c
CommitLineData
d7482997 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
10typedef enum { LEFT, LEFTPLUS, CENTRE } alignment;
11typedef struct {
12 alignment align;
13 int just_numbers;
db662ca1 14 wchar_t *underline;
63223c78 15 wchar_t *number_suffix;
d7482997 16} alignstruct;
17
18typedef 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;
2ac8ceac 26 int charset;
d7482997 27 word bullet;
db662ca1 28 wchar_t *lquote, *rquote, *rule;
50d6b4bd 29 char *filename;
db662ca1 30 wchar_t *listsuffix, *startemph, *endemph;
d7482997 31} textconfig;
32
2ac8ceac 33typedef struct {
34 FILE *fp;
35 int charset;
36 charset_state state;
37} textfile;
38
39static void text_heading(textfile *, word *, word *, word *, alignstruct,
db662ca1 40 int, int, textconfig *);
41static void text_rule(textfile *, int, int, textconfig *);
42static void text_para(textfile *, word *, wchar_t *, word *, int, int, int,
43 textconfig *);
2ac8ceac 44static void text_codepara(textfile *, word *, int, int);
db662ca1 45static void text_versionid(textfile *, word *, textconfig *);
d7482997 46
2ac8ceac 47static void text_output(textfile *, const wchar_t *);
48static void text_output_many(textfile *, int, wchar_t);
d7482997 49
50static 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
58static textconfig text_configure(paragraph *source) {
59 textconfig ret;
db662ca1 60 paragraph *p;
61 int n;
d7482997 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;
db662ca1 80 ret.atitle.underline = L"\x2550\0=\0\0";
d7482997 81 ret.achapter.align = LEFT;
82 ret.achapter.just_numbers = FALSE;
e5e6bf9d 83 ret.achapter.number_suffix = L": ";
db662ca1 84 ret.achapter.underline = L"\x203E\0-\0\0";
d7482997 85 ret.nasect = 1;
f1530049 86 ret.asect = snewn(ret.nasect, alignstruct);
d7482997 87 ret.asect[0].align = LEFTPLUS;
88 ret.asect[0].just_numbers = TRUE;
e5e6bf9d 89 ret.asect[0].number_suffix = L" ";
db662ca1 90 ret.asect[0].underline = L"\0";
d7482997 91 ret.include_version_id = TRUE;
92 ret.indent_preambles = FALSE;
db662ca1 93 ret.bullet.text = L"\x2022\0-\0\0";
94 ret.rule = L"\x2500\0-\0\0";
50d6b4bd 95 ret.filename = dupstr("output.txt");
3e030063 96 ret.startemph = L"_\0_\0\0";
97 ret.endemph = uadv(ret.startemph);
db662ca1 98 ret.listsuffix = L".";
2ac8ceac 99 ret.charset = CS_ASCII;
db662ca1 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);
d7482997 106
db662ca1 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")) {
0960a3d8 129 ret.charset = charset_from_ustr(&p->fpos, uadv(p->keyword));
db662ca1 130 } else if (!ustricmp(p->keyword, L"text-filename")) {
50d6b4bd 131 sfree(ret.filename);
db662ca1 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);
d7482997 151 int n = 0;
db662ca1 152 if (uisdigit(*q)) {
153 n = utoi(q);
154 q = uadv(q);
d7482997 155 }
156 if (n >= ret.nasect) {
157 int i;
f1530049 158 ret.asect = sresize(ret.asect, n+1, alignstruct);
d7482997 159 for (i = ret.nasect; i <= n; i++)
160 ret.asect[i] = ret.asect[ret.nasect-1];
161 ret.nasect = n+1;
162 }
db662ca1 163 ret.asect[n].align = utoalign(q);
164 } else if (!ustricmp(p->keyword, L"text-section-underline")) {
165 wchar_t *q = uadv(p->keyword);
d7482997 166 int n = 0;
db662ca1 167 if (uisdigit(*q)) {
168 n = utoi(q);
169 q = uadv(q);
d7482997 170 }
171 if (n >= ret.nasect) {
172 int i;
f1530049 173 ret.asect = sresize(ret.asect, n+1, alignstruct);
d7482997 174 for (i = ret.nasect; i <= n; i++)
175 ret.asect[i] = ret.asect[ret.nasect-1];
176 ret.nasect = n+1;
177 }
db662ca1 178 ret.asect[n].underline = q;
179 } else if (!ustricmp(p->keyword, L"text-section-numeric")) {
180 wchar_t *q = uadv(p->keyword);
d7482997 181 int n = 0;
db662ca1 182 if (uisdigit(*q)) {
183 n = utoi(q);
184 q = uadv(q);
d7482997 185 }
186 if (n >= ret.nasect) {
187 int i;
f1530049 188 ret.asect = sresize(ret.asect, n+1, alignstruct);
d7482997 189 for (i = ret.nasect; i <= n; i++)
190 ret.asect[i] = ret.asect[ret.nasect-1];
191 ret.nasect = n+1;
192 }
db662ca1 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);
63223c78 196 int n = 0;
db662ca1 197 if (uisdigit(*q)) {
198 n = utoi(q);
199 q = uadv(q);
63223c78 200 }
201 if (n >= ret.nasect) {
202 int i;
f1530049 203 ret.asect = sresize(ret.asect, n+1, alignstruct);
e5e6bf9d 204 for (i = ret.nasect; i <= n; i++) {
63223c78 205 ret.asect[i] = ret.asect[ret.nasect-1];
e5e6bf9d 206 }
63223c78 207 ret.nasect = n+1;
208 }
db662ca1 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 }
d7482997 234 }
235 }
236 }
237
db662ca1 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
d7482997 278 return ret;
279}
280
ba9c1487 281paragraph *text_config_filename(char *filename)
282{
e4ea58f8 283 return cmdline_cfg_simple("text-filename", filename, NULL);
ba9c1487 284}
285
d7482997 286void text_backend(paragraph *sourceform, keywordlist *keywords,
43341922 287 indexdata *idx, void *unused) {
d7482997 288 paragraph *p;
289 textconfig conf;
290 word *prefix, *body, *wp;
291 word spaceword;
2ac8ceac 292 textfile tf;
293 wchar_t *prefixextra;
7136a6c7 294 int nesting, nestindent;
d7482997 295 int indentb, indenta;
296
43341922 297 IGNORE(unused);
d7482997 298 IGNORE(keywords); /* we don't happen to need this */
299 IGNORE(idx); /* or this */
300
301 conf = text_configure(sourceform);
302
303 /*
50d6b4bd 304 * Open the output file.
d7482997 305 */
2ac8ceac 306 tf.fp = fopen(conf.filename, "w");
307 if (!tf.fp) {
50d6b4bd 308 error(err_cantopenw, conf.filename);
d7482997 309 return;
310 }
2ac8ceac 311 tf.charset = conf.charset;
312 tf.state = charset_init_state;
d7482997 313
314 /* Do the title */
315 for (p = sourceform; p; p = p->next)
316 if (p->type == para_Title)
2ac8ceac 317 text_heading(&tf, NULL, NULL, p->words,
db662ca1 318 conf.atitle, conf.indent, conf.width, &conf);
d7482997 319
7136a6c7 320 nestindent = conf.listindentbefore + conf.listindentafter;
8902e0ed 321 nesting = (conf.indent_preambles ? 0 : -conf.indent);
7136a6c7 322
d7482997 323 /* Do the main document */
324 for (p = sourceform; p; p = p->next) switch (p->type) {
325
2614b01d 326 case para_QuotePush:
327 nesting += 2;
328 break;
329 case para_QuotePop:
330 nesting -= 2;
331 assert(nesting >= 0);
332 break;
333
7136a6c7 334 case para_LcontPush:
2614b01d 335 nesting += nestindent;
7136a6c7 336 break;
337 case para_LcontPop:
2614b01d 338 nesting -= nestindent;
339 assert(nesting >= 0);
7136a6c7 340 break;
341
d7482997 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:
d7482997 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:
2ac8ceac 360 text_heading(&tf, p->kwtext, p->kwtext2, p->words,
db662ca1 361 conf.achapter, conf.indent, conf.width, &conf);
8902e0ed 362 nesting = 0;
d7482997 363 break;
364
365 case para_Heading:
366 case para_Subsect:
2ac8ceac 367 text_heading(&tf, p->kwtext, p->kwtext2, p->words,
d7482997 368 conf.asect[p->aux>=conf.nasect ? conf.nasect-1 : p->aux],
db662ca1 369 conf.indent, conf.width, &conf);
d7482997 370 break;
371
372 case para_Rule:
db662ca1 373 text_rule(&tf, conf.indent + nesting, conf.width - nesting, &conf);
d7482997 374 break;
375
376 case para_Normal:
9057a0a8 377 case para_Copyright:
7136a6c7 378 case para_DescribedThing:
379 case para_Description:
d7482997 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;
db662ca1 390 prefixextra = conf.listsuffix;
d7482997 391 indentb = conf.listindentbefore;
392 indenta = conf.listindentafter;
7136a6c7 393 } else if (p->type == para_Description) {
394 prefix = NULL;
395 prefixextra = NULL;
396 indentb = conf.listindentbefore;
397 indenta = conf.listindentafter;
d7482997 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 }
2ac8ceac 415 text_para(&tf, prefix, prefixextra, body,
2614b01d 416 conf.indent + nesting + indentb, indenta,
db662ca1 417 conf.width - nesting - indentb - indenta, &conf);
d7482997 418 if (wp) {
419 wp->next = NULL;
420 free_word_list(body);
421 }
422 break;
423
424 case para_Code:
2ac8ceac 425 text_codepara(&tf, p->words,
2614b01d 426 conf.indent + nesting + conf.indent_code,
427 conf.width - nesting - 2 * conf.indent_code);
d7482997 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)
db662ca1 435 text_versionid(&tf, p->words, &conf);
d7482997 436 }
437
438 /*
439 * Tidy up
440 */
2ac8ceac 441 text_output(&tf, NULL); /* end charset conversion */
442 fclose(tf.fp);
e5e6bf9d 443 sfree(conf.asect);
50d6b4bd 444 sfree(conf.filename);
d7482997 445}
446
2ac8ceac 447static 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);
d7482997 467 }
d7482997 468}
469
2ac8ceac 470static 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}
d7482997 478
db662ca1 479static void text_rdaddw(rdstring *rs, word *text, word *end, textconfig *cfg) {
d7482997 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))
db662ca1 506 rdadds(rs, cfg->startemph);
d7482997 507 else if (towordstyle(text->type) == word_Code &&
508 (attraux(text->aux) == attr_First ||
509 attraux(text->aux) == attr_Only))
db662ca1 510 rdadds(rs, cfg->lquote);
d7482997 511 if (removeattr(text->type) == word_Normal) {
db662ca1 512 if (cvt_ok(cfg->charset, text->text) || !text->alt)
2ac8ceac 513 rdadds(rs, text->text);
d7482997 514 else
db662ca1 515 text_rdaddw(rs, text->alt, NULL, cfg);
d7482997 516 } else if (removeattr(text->type) == word_WhiteSpace) {
2ac8ceac 517 rdadd(rs, L' ');
d7482997 518 } else if (removeattr(text->type) == word_Quote) {
db662ca1 519 rdadds(rs, quoteaux(text->aux) == quote_Open ?
520 cfg->lquote : cfg->rquote);
d7482997 521 }
522 if (towordstyle(text->type) == word_Emph &&
523 (attraux(text->aux) == attr_Last ||
524 attraux(text->aux) == attr_Only))
db662ca1 525 rdadds(rs, cfg->endemph);
d7482997 526 else if (towordstyle(text->type) == word_Code &&
527 (attraux(text->aux) == attr_Last ||
528 attraux(text->aux) == attr_Only))
db662ca1 529 rdadds(rs, cfg->rquote);
d7482997 530 break;
531 }
532}
533
43341922 534static int text_width(void *, word *);
d7482997 535
43341922 536static int text_width_list(void *ctx, word *text) {
d7482997 537 int w = 0;
538 while (text) {
43341922 539 w += text_width(ctx, text);
d7482997 540 text = text->next;
541 }
542 return w;
543}
544
43341922 545static int text_width(void *ctx, word *text) {
db662ca1 546 textconfig *cfg = (textconfig *)ctx;
547 int wid;
548 int attr;
43341922 549
d7482997 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;
db662ca1 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 }
d7482997 576
db662ca1 577 switch (text->type) {
d7482997 578 case word_Normal:
579 case word_Emph:
580 case word_Code:
581 case word_WeakCode:
db662ca1 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;
d7482997 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);
db662ca1 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 */
d7482997 605 }
db662ca1 606
607 return wid;
d7482997 608}
609
2ac8ceac 610static void text_heading(textfile *tf, word *tprefix, word *nprefix,
611 word *text, alignstruct align,
db662ca1 612 int indent, int width, textconfig *cfg) {
2ac8ceac 613 rdstring t = { 0, 0, NULL };
d7482997 614 int margin, length;
615 int firstlinewidth, wrapwidth;
616 wrappedline *wrapping, *p;
617
618 if (align.just_numbers && nprefix) {
db662ca1 619 text_rdaddw(&t, nprefix, NULL, cfg);
2ac8ceac 620 rdadds(&t, align.number_suffix);
d7482997 621 } else if (!align.just_numbers && tprefix) {
db662ca1 622 text_rdaddw(&t, tprefix, NULL, cfg);
2ac8ceac 623 rdadds(&t, align.number_suffix);
d7482997 624 }
db662ca1 625 margin = length = ustrwid(t.text ? t.text : L"", cfg->charset);
d7482997 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
2ac8ceac 638 wrapping = wrap_para(text, firstlinewidth, wrapwidth,
db662ca1 639 text_width, cfg, 0);
d7482997 640 for (p = wrapping; p; p = p->next) {
db662ca1 641 text_rdaddw(&t, p->begin, p->end, cfg);
642 length = ustrwid(t.text ? t.text : L"", cfg->charset);
d7482997 643 if (align.align == CENTRE) {
644 margin = (indent + width - length)/2;
645 if (margin < 0) margin = 0;
646 }
2ac8ceac 647 text_output_many(tf, margin, L' ');
648 text_output(tf, t.text);
649 text_output(tf, L"\n");
db662ca1 650 if (*align.underline) {
2ac8ceac 651 text_output_many(tf, margin, L' ');
db662ca1 652 while (length > 0) {
653 text_output(tf, align.underline);
654 length -= ustrwid(align.underline, cfg->charset);
655 }
2ac8ceac 656 text_output(tf, L"\n");
d7482997 657 }
658 if (align.align == LEFTPLUS)
659 margin = indent;
660 else
661 margin = 0;
662 sfree(t.text);
2ac8ceac 663 t = empty_rdstring;
d7482997 664 }
665 wrap_free(wrapping);
2ac8ceac 666 text_output(tf, L"\n");
d7482997 667
668 sfree(t.text);
669}
670
db662ca1 671static void text_rule(textfile *tf, int indent, int width, textconfig *cfg) {
2ac8ceac 672 text_output_many(tf, indent, L' ');
db662ca1 673 while (width > 0) {
674 text_output(tf, cfg->rule);
675 width -= ustrwid(cfg->rule, cfg->charset);
676 }
2ac8ceac 677 text_output_many(tf, 2, L'\n');
d7482997 678}
679
2ac8ceac 680static void text_para(textfile *tf, word *prefix, wchar_t *prefixextra,
db662ca1 681 word *text, int indent, int extraindent, int width,
682 textconfig *cfg) {
d7482997 683 wrappedline *wrapping, *p;
2ac8ceac 684 rdstring pfx = { 0, 0, NULL };
d7482997 685 int e;
686 int firstlinewidth = width;
687
688 if (prefix) {
db662ca1 689 text_rdaddw(&pfx, prefix, NULL, cfg);
d7482997 690 if (prefixextra)
2ac8ceac 691 rdadds(&pfx, prefixextra);
692 text_output_many(tf, indent, L' ');
693 text_output(tf, pfx.text);
c83c6495 694 /* If the prefix is too long, shorten the first line to fit. */
db662ca1 695 e = extraindent - ustrwid(pfx.text ? pfx.text : L"", cfg->charset);
d7482997 696 if (e < 0) {
c83c6495 697 firstlinewidth += e; /* this decreases it, since e < 0 */
d7482997 698 if (firstlinewidth < 0) {
699 e = indent + extraindent;
700 firstlinewidth = width;
2ac8ceac 701 text_output(tf, L"\n");
c83c6495 702 } else
703 e = 0;
d7482997 704 }
705 sfree(pfx.text);
706 } else
707 e = indent + extraindent;
708
2ac8ceac 709 wrapping = wrap_para(text, firstlinewidth, width,
db662ca1 710 text_width, cfg, 0);
d7482997 711 for (p = wrapping; p; p = p->next) {
2ac8ceac 712 rdstring t = { 0, 0, NULL };
db662ca1 713 text_rdaddw(&t, p->begin, p->end, cfg);
2ac8ceac 714 text_output_many(tf, e, L' ');
715 text_output(tf, t.text);
716 text_output(tf, L"\n");
d7482997 717 e = indent + extraindent;
718 sfree(t.text);
719 }
720 wrap_free(wrapping);
2ac8ceac 721 text_output(tf, L"\n");
d7482997 722}
723
2ac8ceac 724static void text_codepara(textfile *tf, word *text, int indent, int width) {
d7482997 725 for (; text; text = text->next) if (text->type == word_WeakCode) {
db662ca1 726 int wid = ustrwid(text->text, tf->charset);
727 if (wid > width)
728 error(err_text_codeline, &text->fpos, wid, width);
2ac8ceac 729 text_output_many(tf, indent, L' ');
730 text_output(tf, text->text);
731 text_output(tf, L"\n");
d7482997 732 }
733
2ac8ceac 734 text_output(tf, L"\n");
d7482997 735}
736
db662ca1 737static void text_versionid(textfile *tf, word *text, textconfig *cfg) {
2ac8ceac 738 rdstring t = { 0, 0, NULL };
d7482997 739
db662ca1 740 rdadd(&t, L'[');
741 text_rdaddw(&t, text, NULL, cfg);
742 rdadd(&t, L']');
2ac8ceac 743 rdadd(&t, L'\n');
d7482997 744
2ac8ceac 745 text_output(tf, t.text);
d7482997 746 sfree(t.text);
747}