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