2 * text backend for Halibut
10 typedef enum { LEFT
, LEFTPLUS
, CENTRE
} alignment
;
15 wchar_t *number_suffix
;
19 int indent
, indent_code
;
20 int listindentbefore
, listindentafter
;
22 alignstruct atitle
, achapter
, *asect
;
24 int include_version_id
;
37 static void text_heading(textfile
*, word
*, word
*, word
*, alignstruct
,
39 static void text_rule(textfile
*, int, int);
40 static void text_para(textfile
*, word
*, wchar_t *, word
*, int, int, int);
41 static void text_codepara(textfile
*, word
*, int, int);
42 static void text_versionid(textfile
*, word
*);
44 static void text_output(textfile
*, const wchar_t *);
45 static void text_output_many(textfile
*, int, wchar_t);
47 static alignment
utoalign(wchar_t *p
) {
48 if (!ustricmp(p
, L
"centre") || !ustricmp(p
, L
"center"))
50 if (!ustricmp(p
, L
"leftplus"))
55 static textconfig
text_configure(paragraph
*source
) {
61 ret
.bullet
.next
= NULL
;
62 ret
.bullet
.alt
= NULL
;
63 ret
.bullet
.type
= word_Normal
;
64 ret
.atitle
.just_numbers
= FALSE
; /* ignored */
71 ret
.listindentbefore
= 1;
72 ret
.listindentafter
= 3;
74 ret
.atitle
.align
= CENTRE
;
75 ret
.atitle
.underline
= L
'=';
76 ret
.achapter
.align
= LEFT
;
77 ret
.achapter
.just_numbers
= FALSE
;
78 ret
.achapter
.number_suffix
= L
": ";
79 ret
.achapter
.underline
= L
'-';
81 ret
.asect
= mknewa(alignstruct
, ret
.nasect
);
82 ret
.asect
[0].align
= LEFTPLUS
;
83 ret
.asect
[0].just_numbers
= TRUE
;
84 ret
.asect
[0].number_suffix
= L
" ";
85 ret
.asect
[0].underline
= L
'\0';
86 ret
.include_version_id
= TRUE
;
87 ret
.indent_preambles
= FALSE
;
88 ret
.bullet
.text
= L
"-";
89 ret
.filename
= dupstr("output.txt");
90 ret
.charset
= CS_ASCII
;
92 for (; source
; source
= source
->next
) {
93 if (source
->type
== para_Config
) {
94 if (!ustricmp(source
->keyword
, L
"text-indent")) {
95 ret
.indent
= utoi(uadv(source
->keyword
));
96 } else if (!ustricmp(source
->keyword
, L
"text-charset")) {
97 char *csname
= utoa_dup(uadv(source
->keyword
), CS_ASCII
);
98 ret
.charset
= charset_from_localenc(csname
);
100 } else if (!ustricmp(source
->keyword
, L
"text-filename")) {
102 ret
.filename
= dupstr(adv(source
->origkeyword
));
103 } else if (!ustricmp(source
->keyword
, L
"text-indent-code")) {
104 ret
.indent_code
= utoi(uadv(source
->keyword
));
105 } else if (!ustricmp(source
->keyword
, L
"text-width")) {
106 ret
.width
= utoi(uadv(source
->keyword
));
107 } else if (!ustricmp(source
->keyword
, L
"text-list-indent")) {
108 ret
.listindentbefore
= utoi(uadv(source
->keyword
));
109 } else if (!ustricmp(source
->keyword
, L
"text-listitem-indent")) {
110 ret
.listindentafter
= utoi(uadv(source
->keyword
));
111 } else if (!ustricmp(source
->keyword
, L
"text-chapter-align")) {
112 ret
.achapter
.align
= utoalign(uadv(source
->keyword
));
113 } else if (!ustricmp(source
->keyword
, L
"text-chapter-underline")) {
114 ret
.achapter
.underline
= *uadv(source
->keyword
);
115 } else if (!ustricmp(source
->keyword
, L
"text-chapter-numeric")) {
116 ret
.achapter
.just_numbers
= utob(uadv(source
->keyword
));
117 } else if (!ustricmp(source
->keyword
, L
"text-chapter-suffix")) {
118 ret
.achapter
.number_suffix
= uadv(source
->keyword
);
119 } else if (!ustricmp(source
->keyword
, L
"text-section-align")) {
120 wchar_t *p
= uadv(source
->keyword
);
126 if (n
>= ret
.nasect
) {
128 ret
.asect
= resize(ret
.asect
, n
+1);
129 for (i
= ret
.nasect
; i
<= n
; i
++)
130 ret
.asect
[i
] = ret
.asect
[ret
.nasect
-1];
133 ret
.asect
[n
].align
= utoalign(p
);
134 } else if (!ustricmp(source
->keyword
, L
"text-section-underline")) {
135 wchar_t *p
= uadv(source
->keyword
);
141 if (n
>= ret
.nasect
) {
143 ret
.asect
= resize(ret
.asect
, n
+1);
144 for (i
= ret
.nasect
; i
<= n
; i
++)
145 ret
.asect
[i
] = ret
.asect
[ret
.nasect
-1];
148 ret
.asect
[n
].underline
= *p
;
149 } else if (!ustricmp(source
->keyword
, L
"text-section-numeric")) {
150 wchar_t *p
= uadv(source
->keyword
);
156 if (n
>= ret
.nasect
) {
158 ret
.asect
= resize(ret
.asect
, n
+1);
159 for (i
= ret
.nasect
; i
<= n
; i
++)
160 ret
.asect
[i
] = ret
.asect
[ret
.nasect
-1];
163 ret
.asect
[n
].just_numbers
= utob(p
);
164 } else if (!ustricmp(source
->keyword
, L
"text-section-suffix")) {
165 wchar_t *p
= uadv(source
->keyword
);
171 if (n
>= ret
.nasect
) {
173 ret
.asect
= resize(ret
.asect
, n
+1);
174 for (i
= ret
.nasect
; i
<= n
; i
++) {
175 ret
.asect
[i
] = ret
.asect
[ret
.nasect
-1];
179 ret
.asect
[n
].number_suffix
= p
;
180 } else if (!ustricmp(source
->keyword
, L
"text-title-align")) {
181 ret
.atitle
.align
= utoalign(uadv(source
->keyword
));
182 } else if (!ustricmp(source
->keyword
, L
"text-title-underline")) {
183 ret
.atitle
.underline
= *uadv(source
->keyword
);
184 } else if (!ustricmp(source
->keyword
, L
"text-versionid")) {
185 ret
.include_version_id
= utob(uadv(source
->keyword
));
186 } else if (!ustricmp(source
->keyword
, L
"text-indent-preamble")) {
187 ret
.indent_preambles
= utob(uadv(source
->keyword
));
188 } else if (!ustricmp(source
->keyword
, L
"text-bullet")) {
189 ret
.bullet
.text
= uadv(source
->keyword
);
197 paragraph
*text_config_filename(char *filename
)
199 return cmdline_cfg_simple("text-filename", filename
, NULL
);
202 void text_backend(paragraph
*sourceform
, keywordlist
*keywords
,
203 indexdata
*idx
, void *unused
) {
206 word
*prefix
, *body
, *wp
;
209 wchar_t *prefixextra
;
210 int nesting
, nestindent
;
211 int indentb
, indenta
;
214 IGNORE(keywords
); /* we don't happen to need this */
215 IGNORE(idx
); /* or this */
217 conf
= text_configure(sourceform
);
220 * Open the output file.
222 tf
.fp
= fopen(conf
.filename
, "w");
224 error(err_cantopenw
, conf
.filename
);
227 tf
.charset
= conf
.charset
;
228 tf
.state
= charset_init_state
;
231 for (p
= sourceform
; p
; p
= p
->next
)
232 if (p
->type
== para_Title
)
233 text_heading(&tf
, NULL
, NULL
, p
->words
,
234 conf
.atitle
, conf
.indent
, conf
.width
);
236 nestindent
= conf
.listindentbefore
+ conf
.listindentafter
;
237 nesting
= (conf
.indent_preambles ?
0 : -conf
.indent
);
239 /* Do the main document */
240 for (p
= sourceform
; p
; p
= p
->next
) switch (p
->type
) {
247 assert(nesting
>= 0);
251 nesting
+= nestindent
;
254 nesting
-= nestindent
;
255 assert(nesting
>= 0);
259 * Things we ignore because we've already processed them or
260 * aren't going to touch them in this pass.
264 case para_Biblio
: /* only touch BiblioCited */
275 case para_UnnumberedChapter
:
276 text_heading(&tf
, p
->kwtext
, p
->kwtext2
, p
->words
,
277 conf
.achapter
, conf
.indent
, conf
.width
);
283 text_heading(&tf
, p
->kwtext
, p
->kwtext2
, p
->words
,
284 conf
.asect
[p
->aux
>=conf
.nasect ? conf
.nasect
-1 : p
->aux
],
285 conf
.indent
, conf
.width
);
289 text_rule(&tf
, conf
.indent
+ nesting
, conf
.width
- nesting
);
294 case para_DescribedThing
:
295 case para_Description
:
296 case para_BiblioCited
:
298 case para_NumberedList
:
299 if (p
->type
== para_Bullet
) {
300 prefix
= &conf
.bullet
;
302 indentb
= conf
.listindentbefore
;
303 indenta
= conf
.listindentafter
;
304 } else if (p
->type
== para_NumberedList
) {
306 prefixextra
= L
"."; /* FIXME: configurability */
307 indentb
= conf
.listindentbefore
;
308 indenta
= conf
.listindentafter
;
309 } else if (p
->type
== para_Description
) {
312 indentb
= conf
.listindentbefore
;
313 indenta
= conf
.listindentafter
;
317 indentb
= indenta
= 0;
319 if (p
->type
== para_BiblioCited
) {
320 body
= dup_word_list(p
->kwtext
);
321 for (wp
= body
; wp
->next
; wp
= wp
->next
);
322 wp
->next
= &spaceword
;
323 spaceword
.next
= p
->words
;
324 spaceword
.alt
= NULL
;
325 spaceword
.type
= word_WhiteSpace
;
326 spaceword
.text
= NULL
;
331 text_para(&tf
, prefix
, prefixextra
, body
,
332 conf
.indent
+ nesting
+ indentb
, indenta
,
333 conf
.width
- nesting
- indentb
- indenta
);
336 free_word_list(body
);
341 text_codepara(&tf
, p
->words
,
342 conf
.indent
+ nesting
+ conf
.indent_code
,
343 conf
.width
- nesting
- 2 * conf
.indent_code
);
347 /* Do the version ID */
348 if (conf
.include_version_id
) {
349 for (p
= sourceform
; p
; p
= p
->next
)
350 if (p
->type
== para_VersionID
)
351 text_versionid(&tf
, p
->words
);
357 text_output(&tf
, NULL
); /* end charset conversion */
360 sfree(conf
.filename
);
363 static void text_output(textfile
*tf
, const wchar_t *s
)
378 ret
= charset_from_unicode(sp
, &len
, buf
, lenof(buf
),
379 tf
->charset
, &tf
->state
, NULL
);
382 fwrite(buf
, 1, ret
, tf
->fp
);
386 static void text_output_many(textfile
*tf
, int n
, wchar_t c
)
395 static void text_rdaddw(int charset
, rdstring
*rs
, word
*text
, word
*end
) {
396 for (; text
&& text
!= end
; text
= text
->next
) switch (text
->type
) {
409 case word_WhiteSpace
:
412 case word_WkCodeSpace
:
416 case word_WkCodeQuote
:
417 assert(text
->type
!= word_CodeQuote
&&
418 text
->type
!= word_WkCodeQuote
);
419 if (towordstyle(text
->type
) == word_Emph
&&
420 (attraux(text
->aux
) == attr_First
||
421 attraux(text
->aux
) == attr_Only
))
422 rdadd(rs
, L
'_'); /* FIXME: configurability */
423 else if (towordstyle(text
->type
) == word_Code
&&
424 (attraux(text
->aux
) == attr_First
||
425 attraux(text
->aux
) == attr_Only
))
426 rdadd(rs
, L
'`'); /* FIXME: configurability */
427 if (removeattr(text
->type
) == word_Normal
) {
428 if (cvt_ok(charset
, text
->text
) || !text
->alt
)
429 rdadds(rs
, text
->text
);
431 text_rdaddw(charset
, rs
, text
->alt
, NULL
);
432 } else if (removeattr(text
->type
) == word_WhiteSpace
) {
434 } else if (removeattr(text
->type
) == word_Quote
) {
435 rdadd(rs
, quoteaux(text
->aux
) == quote_Open ? L
'`' : L
'\'');
436 /* FIXME: configurability */
438 if (towordstyle(text
->type
) == word_Emph
&&
439 (attraux(text
->aux
) == attr_Last
||
440 attraux(text
->aux
) == attr_Only
))
441 rdadd(rs
, L
'_'); /* FIXME: configurability */
442 else if (towordstyle(text
->type
) == word_Code
&&
443 (attraux(text
->aux
) == attr_Last
||
444 attraux(text
->aux
) == attr_Only
))
445 rdadd(rs
, L
'\''); /* FIXME: configurability */
450 static int text_width(void *, word
*);
452 static int text_width_list(void *ctx
, word
*text
) {
455 w
+= text_width(ctx
, text
);
461 static int text_width(void *ctx
, word
*text
) {
462 int charset
= * (int *) ctx
;
464 switch (text
->type
) {
477 return (((text
->type
== word_Emph
||
478 text
->type
== word_Code
)
479 ?
(attraux(text
->aux
) == attr_Only ?
2 :
480 attraux(text
->aux
) == attr_Always ?
0 : 1)
482 (cvt_ok(charset
, text
->text
) || !text
->alt ?
483 ustrlen(text
->text
) :
484 text_width_list(ctx
, text
->alt
)));
486 case word_WhiteSpace
:
489 case word_WkCodeSpace
:
493 case word_WkCodeQuote
:
494 assert(text
->type
!= word_CodeQuote
&&
495 text
->type
!= word_WkCodeQuote
);
496 return (((towordstyle(text
->type
) == word_Emph
||
497 towordstyle(text
->type
) == word_Code
)
498 ?
(attraux(text
->aux
) == attr_Only ?
2 :
499 attraux(text
->aux
) == attr_Always ?
0 : 1)
502 return 0; /* should never happen */
505 static void text_heading(textfile
*tf
, word
*tprefix
, word
*nprefix
,
506 word
*text
, alignstruct align
,
507 int indent
, int width
) {
508 rdstring t
= { 0, 0, NULL
};
510 int firstlinewidth
, wrapwidth
;
511 wrappedline
*wrapping
, *p
;
513 if (align
.just_numbers
&& nprefix
) {
514 text_rdaddw(tf
->charset
, &t
, nprefix
, NULL
);
515 rdadds(&t
, align
.number_suffix
);
516 } else if (!align
.just_numbers
&& tprefix
) {
517 text_rdaddw(tf
->charset
, &t
, tprefix
, NULL
);
518 rdadds(&t
, align
.number_suffix
);
520 margin
= length
= t
.pos
;
522 if (align
.align
== LEFTPLUS
) {
523 margin
= indent
- margin
;
524 if (margin
< 0) margin
= 0;
525 firstlinewidth
= indent
+ width
- margin
- length
;
527 } else if (align
.align
== LEFT
|| align
.align
== CENTRE
) {
529 firstlinewidth
= indent
+ width
- length
;
530 wrapwidth
= indent
+ width
;
533 wrapping
= wrap_para(text
, firstlinewidth
, wrapwidth
,
534 text_width
, &tf
->charset
, 0);
535 for (p
= wrapping
; p
; p
= p
->next
) {
536 text_rdaddw(tf
->charset
, &t
, p
->begin
, p
->end
);
538 if (align
.align
== CENTRE
) {
539 margin
= (indent
+ width
- length
)/2;
540 if (margin
< 0) margin
= 0;
542 text_output_many(tf
, margin
, L
' ');
543 text_output(tf
, t
.text
);
544 text_output(tf
, L
"\n");
545 if (align
.underline
!= L
'\0') {
546 text_output_many(tf
, margin
, L
' ');
547 text_output_many(tf
, length
, align
.underline
);
548 text_output(tf
, L
"\n");
550 if (align
.align
== LEFTPLUS
)
558 text_output(tf
, L
"\n");
563 static void text_rule(textfile
*tf
, int indent
, int width
) {
564 text_output_many(tf
, indent
, L
' ');
565 text_output_many(tf
, width
, L
'-'); /* FIXME: configurability! */
566 text_output_many(tf
, 2, L
'\n');
569 static void text_para(textfile
*tf
, word
*prefix
, wchar_t *prefixextra
,
570 word
*text
, int indent
, int extraindent
, int width
) {
571 wrappedline
*wrapping
, *p
;
572 rdstring pfx
= { 0, 0, NULL
};
574 int firstlinewidth
= width
;
577 text_rdaddw(tf
->charset
, &pfx
, prefix
, NULL
);
579 rdadds(&pfx
, prefixextra
);
580 text_output_many(tf
, indent
, L
' ');
581 text_output(tf
, pfx
.text
);
582 /* If the prefix is too long, shorten the first line to fit. */
583 e
= extraindent
- pfx
.pos
;
585 firstlinewidth
+= e
; /* this decreases it, since e < 0 */
586 if (firstlinewidth
< 0) {
587 e
= indent
+ extraindent
;
588 firstlinewidth
= width
;
589 text_output(tf
, L
"\n");
595 e
= indent
+ extraindent
;
597 wrapping
= wrap_para(text
, firstlinewidth
, width
,
598 text_width
, &tf
->charset
, 0);
599 for (p
= wrapping
; p
; p
= p
->next
) {
600 rdstring t
= { 0, 0, NULL
};
601 text_rdaddw(tf
->charset
, &t
, p
->begin
, p
->end
);
602 text_output_many(tf
, e
, L
' ');
603 text_output(tf
, t
.text
);
604 text_output(tf
, L
"\n");
605 e
= indent
+ extraindent
;
609 text_output(tf
, L
"\n");
612 static void text_codepara(textfile
*tf
, word
*text
, int indent
, int width
) {
613 for (; text
; text
= text
->next
) if (text
->type
== word_WeakCode
) {
614 if (ustrlen(text
->text
) > width
) {
617 text_output_many(tf
, indent
, L
' ');
618 text_output(tf
, text
->text
);
619 text_output(tf
, L
"\n");
622 text_output(tf
, L
"\n");
625 static void text_versionid(textfile
*tf
, word
*text
) {
626 rdstring t
= { 0, 0, NULL
};
628 rdadd(&t
, L
'['); /* FIXME: configurability */
629 text_rdaddw(tf
->charset
, &t
, text
, NULL
);
630 rdadd(&t
, L
']'); /* FIXME: configurability */
633 text_output(tf
, t
.text
);