]> git.cameronkatri.com Git - mandoc.git/blob - man_term.c
Added `PD' to -man (doesn't do anything, yet).
[mandoc.git] / man_term.c
1 /* $Id: man_term.c,v 1.44 2009/10/24 05:45:05 kristaps Exp $ */
2 /*
3 * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17 #include <sys/types.h>
18
19 #include <assert.h>
20 #include <ctype.h>
21 #include <err.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 #include "out.h"
27 #include "man.h"
28 #include "term.h"
29 #include "chars.h"
30 #include "main.h"
31
32 #define INDENT 7
33 #define HALFINDENT 3
34
35 /* FIXME: have PD set the default vspace width. */
36
37 struct mtermp {
38 int fl;
39 #define MANT_LITERAL (1 << 0)
40 /*
41 * Default amount to indent the left margin after leading text
42 * has been printed (e.g., `HP' left-indent, `TP' and `IP' body
43 * indent). This needs to be saved because `HP' and so on, if
44 * not having a specified value, must default.
45 *
46 * Note that this is the indentation AFTER the left offset, so
47 * the total offset is usually offset + lmargin.
48 */
49 size_t lmargin;
50 /*
51 * The default offset, i.e., the amount between any text and the
52 * page boundary.
53 */
54 size_t offset;
55 };
56
57 #define DECL_ARGS struct termp *p, \
58 struct mtermp *mt, \
59 const struct man_node *n, \
60 const struct man_meta *m
61
62 struct termact {
63 int (*pre)(DECL_ARGS);
64 void (*post)(DECL_ARGS);
65 };
66
67 #ifdef __linux__
68 extern size_t strlcpy(char *, const char *, size_t);
69 extern size_t strlcat(char *, const char *, size_t);
70 #endif
71
72 static int arg2width(const struct man_node *);
73 static int arg2height(const struct man_node *);
74
75 static void print_head(struct termp *,
76 const struct man_meta *);
77 static void print_body(DECL_ARGS);
78 static void print_node(DECL_ARGS);
79 static void print_foot(struct termp *,
80 const struct man_meta *);
81 static void print_bvspace(struct termp *,
82 const struct man_node *);
83
84 static int pre_B(DECL_ARGS);
85 static int pre_BI(DECL_ARGS);
86 static int pre_HP(DECL_ARGS);
87 static int pre_I(DECL_ARGS);
88 static int pre_IP(DECL_ARGS);
89 static int pre_IR(DECL_ARGS);
90 static int pre_PP(DECL_ARGS);
91 static int pre_RB(DECL_ARGS);
92 static int pre_RI(DECL_ARGS);
93 static int pre_RS(DECL_ARGS);
94 static int pre_SH(DECL_ARGS);
95 static int pre_SS(DECL_ARGS);
96 static int pre_TP(DECL_ARGS);
97 static int pre_br(DECL_ARGS);
98 static int pre_fi(DECL_ARGS);
99 static int pre_ign(DECL_ARGS);
100 static int pre_nf(DECL_ARGS);
101 static int pre_r(DECL_ARGS);
102 static int pre_sp(DECL_ARGS);
103
104 static void post_B(DECL_ARGS);
105 static void post_I(DECL_ARGS);
106 static void post_IP(DECL_ARGS);
107 static void post_HP(DECL_ARGS);
108 static void post_RS(DECL_ARGS);
109 static void post_SH(DECL_ARGS);
110 static void post_SS(DECL_ARGS);
111 static void post_TP(DECL_ARGS);
112 static void post_i(DECL_ARGS);
113
114 static const struct termact termacts[MAN_MAX] = {
115 { pre_br, NULL }, /* br */
116 { NULL, NULL }, /* TH */
117 { pre_SH, post_SH }, /* SH */
118 { pre_SS, post_SS }, /* SS */
119 { pre_TP, post_TP }, /* TP */
120 { pre_PP, NULL }, /* LP */
121 { pre_PP, NULL }, /* PP */
122 { pre_PP, NULL }, /* P */
123 { pre_IP, post_IP }, /* IP */
124 { pre_HP, post_HP }, /* HP */
125 { NULL, NULL }, /* SM */
126 { pre_B, post_B }, /* SB */
127 { pre_BI, NULL }, /* BI */
128 { pre_BI, NULL }, /* IB */
129 { pre_RB, NULL }, /* BR */
130 { pre_RB, NULL }, /* RB */
131 { NULL, NULL }, /* R */
132 { pre_B, post_B }, /* B */
133 { pre_I, post_I }, /* I */
134 { pre_IR, NULL }, /* IR */
135 { pre_RI, NULL }, /* RI */
136 { NULL, NULL }, /* na */
137 { pre_I, post_i }, /* i */
138 { pre_sp, NULL }, /* sp */
139 { pre_nf, NULL }, /* nf */
140 { pre_fi, NULL }, /* fi */
141 { pre_r, NULL }, /* r */
142 { NULL, NULL }, /* RE */
143 { pre_RS, post_RS }, /* RS */
144 { pre_ign, NULL }, /* DT */
145 { pre_ign, NULL }, /* UC */
146 { pre_ign, NULL }, /* PD */
147 };
148
149
150
151 void
152 terminal_man(void *arg, const struct man *man)
153 {
154 struct termp *p;
155 const struct man_node *n;
156 const struct man_meta *m;
157 struct mtermp mt;
158
159 p = (struct termp *)arg;
160
161 if (NULL == p->symtab)
162 switch (p->enc) {
163 case (TERMENC_ASCII):
164 p->symtab = chars_init(CHARS_ASCII);
165 break;
166 default:
167 abort();
168 /* NOTREACHED */
169 }
170
171 n = man_node(man);
172 m = man_meta(man);
173
174 print_head(p, m);
175 p->flags |= TERMP_NOSPACE;
176
177 mt.fl = 0;
178 mt.lmargin = INDENT;
179 mt.offset = INDENT;
180
181 if (n->child)
182 print_body(p, &mt, n->child, m);
183 print_foot(p, m);
184 }
185
186
187 static int
188 arg2height(const struct man_node *n)
189 {
190 struct roffsu su;
191
192 assert(MAN_TEXT == n->type);
193 assert(n->string);
194 if ( ! a2roffsu(n->string, &su, SCALE_VS))
195 SCALE_VS_INIT(&su, strlen(n->string));
196
197 return((int)term_vspan(&su));
198 }
199
200
201 static int
202 arg2width(const struct man_node *n)
203 {
204 struct roffsu su;
205
206 assert(MAN_TEXT == n->type);
207 assert(n->string);
208 if ( ! a2roffsu(n->string, &su, SCALE_BU))
209 return(-1);
210
211 return((int)term_hspan(&su));
212 }
213
214
215 static void
216 print_bvspace(struct termp *p, const struct man_node *n)
217 {
218 term_newln(p);
219
220 if (NULL == n->prev)
221 return;
222
223 if (MAN_SS == n->prev->tok)
224 return;
225 if (MAN_SH == n->prev->tok)
226 return;
227
228 term_vspace(p);
229 }
230
231
232 /* ARGSUSED */
233 static int
234 pre_ign(DECL_ARGS)
235 {
236
237 return(0);
238 }
239
240
241 /* ARGSUSED */
242 static int
243 pre_I(DECL_ARGS)
244 {
245
246 p->under++;
247 return(1);
248 }
249
250
251 /* ARGSUSED */
252 static int
253 pre_r(DECL_ARGS)
254 {
255
256 p->bold = p->under = 0;
257 return(1);
258 }
259
260
261 /* ARGSUSED */
262 static void
263 post_i(DECL_ARGS)
264 {
265
266 if (n->nchild)
267 p->under--;
268 }
269
270
271 /* ARGSUSED */
272 static void
273 post_I(DECL_ARGS)
274 {
275
276 p->under--;
277 }
278
279
280 /* ARGSUSED */
281 static int
282 pre_fi(DECL_ARGS)
283 {
284
285 mt->fl &= ~MANT_LITERAL;
286 return(1);
287 }
288
289
290 /* ARGSUSED */
291 static int
292 pre_nf(DECL_ARGS)
293 {
294
295 term_newln(p);
296 mt->fl |= MANT_LITERAL;
297 return(1);
298 }
299
300
301 /* ARGSUSED */
302 static int
303 pre_IR(DECL_ARGS)
304 {
305 const struct man_node *nn;
306 int i;
307
308 for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
309 if ( ! (i % 2))
310 p->under++;
311 if (i > 0)
312 p->flags |= TERMP_NOSPACE;
313 print_node(p, mt, nn, m);
314 if ( ! (i % 2))
315 p->under--;
316 }
317 return(0);
318 }
319
320
321 /* ARGSUSED */
322 static int
323 pre_RB(DECL_ARGS)
324 {
325 const struct man_node *nn;
326 int i;
327
328 for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
329 if (i % 2 && MAN_RB == n->tok)
330 p->bold++;
331 else if ( ! (i % 2) && MAN_RB != n->tok)
332 p->bold++;
333
334 if (i > 0)
335 p->flags |= TERMP_NOSPACE;
336
337 print_node(p, mt, nn, m);
338
339 if (i % 2 && MAN_RB == n->tok)
340 p->bold--;
341 else if ( ! (i % 2) && MAN_RB != n->tok)
342 p->bold--;
343 }
344 return(0);
345 }
346
347
348 /* ARGSUSED */
349 static int
350 pre_RI(DECL_ARGS)
351 {
352 const struct man_node *nn;
353 int i;
354
355 for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
356 if ( ! (i % 2))
357 p->under++;
358 if (i > 0)
359 p->flags |= TERMP_NOSPACE;
360 print_node(p, mt, nn, m);
361 if ( ! (i % 2))
362 p->under--;
363 }
364 return(0);
365 }
366
367
368 /* ARGSUSED */
369 static int
370 pre_BI(DECL_ARGS)
371 {
372 const struct man_node *nn;
373 int i;
374
375 for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
376 if (i % 2 && MAN_BI == n->tok)
377 p->under++;
378 else if (i % 2)
379 p->bold++;
380 else if (MAN_BI == n->tok)
381 p->bold++;
382 else
383 p->under++;
384
385 if (i)
386 p->flags |= TERMP_NOSPACE;
387 print_node(p, mt, nn, m);
388
389 if (i % 2 && MAN_BI == n->tok)
390 p->under--;
391 else if (i % 2)
392 p->bold--;
393 else if (MAN_BI == n->tok)
394 p->bold--;
395 else
396 p->under--;
397 }
398 return(0);
399 }
400
401
402 /* ARGSUSED */
403 static int
404 pre_B(DECL_ARGS)
405 {
406
407 p->bold++;
408 return(1);
409 }
410
411
412 /* ARGSUSED */
413 static void
414 post_B(DECL_ARGS)
415 {
416
417 p->bold--;
418 }
419
420
421 /* ARGSUSED */
422 static int
423 pre_sp(DECL_ARGS)
424 {
425 int i, len;
426
427 len = n->child ? arg2height(n->child) : 1;
428
429 if (0 == len)
430 term_newln(p);
431 for (i = 0; i < len; i++)
432 term_vspace(p);
433
434 return(0);
435 }
436
437
438 /* ARGSUSED */
439 static int
440 pre_br(DECL_ARGS)
441 {
442
443 term_newln(p);
444 return(0);
445 }
446
447
448 /* ARGSUSED */
449 static int
450 pre_HP(DECL_ARGS)
451 {
452 size_t len;
453 int ival;
454 const struct man_node *nn;
455
456 switch (n->type) {
457 case (MAN_BLOCK):
458 print_bvspace(p, n);
459 return(1);
460 case (MAN_BODY):
461 p->flags |= TERMP_NOBREAK;
462 p->flags |= TERMP_TWOSPACE;
463 break;
464 default:
465 return(0);
466 }
467
468 len = mt->lmargin;
469 ival = -1;
470
471 /* Calculate offset. */
472
473 if (NULL != (nn = n->parent->head->child))
474 if ((ival = arg2width(nn)) >= 0)
475 len = (size_t)ival;
476
477 if (0 == len)
478 len = 1;
479
480 p->offset = mt->offset;
481 p->rmargin = mt->offset + len;
482
483 if (ival >= 0)
484 mt->lmargin = (size_t)ival;
485
486 return(1);
487 }
488
489
490 /* ARGSUSED */
491 static void
492 post_HP(DECL_ARGS)
493 {
494
495 switch (n->type) {
496 case (MAN_BLOCK):
497 term_flushln(p);
498 break;
499 case (MAN_BODY):
500 term_flushln(p);
501 p->flags &= ~TERMP_NOBREAK;
502 p->flags &= ~TERMP_TWOSPACE;
503 p->offset = mt->offset;
504 p->rmargin = p->maxrmargin;
505 break;
506 default:
507 break;
508 }
509 }
510
511
512 /* ARGSUSED */
513 static int
514 pre_PP(DECL_ARGS)
515 {
516
517 switch (n->type) {
518 case (MAN_BLOCK):
519 mt->lmargin = INDENT;
520 print_bvspace(p, n);
521 break;
522 default:
523 p->offset = mt->offset;
524 break;
525 }
526
527 return(1);
528 }
529
530
531 /* ARGSUSED */
532 static int
533 pre_IP(DECL_ARGS)
534 {
535 const struct man_node *nn;
536 size_t len;
537 int ival;
538
539 switch (n->type) {
540 case (MAN_BODY):
541 p->flags |= TERMP_NOLPAD;
542 p->flags |= TERMP_NOSPACE;
543 break;
544 case (MAN_HEAD):
545 p->flags |= TERMP_NOBREAK;
546 p->flags |= TERMP_TWOSPACE;
547 break;
548 case (MAN_BLOCK):
549 print_bvspace(p, n);
550 /* FALLTHROUGH */
551 default:
552 return(1);
553 }
554
555 len = mt->lmargin;
556 ival = -1;
557
558 /* Calculate offset. */
559
560 if (NULL != (nn = n->parent->head->child))
561 if (NULL != (nn = nn->next)) {
562 for ( ; nn->next; nn = nn->next)
563 /* Do nothing. */ ;
564 if ((ival = arg2width(nn)) >= 0)
565 len = (size_t)ival;
566 }
567
568 switch (n->type) {
569 case (MAN_HEAD):
570 /* Handle zero-width lengths. */
571 if (0 == len)
572 len = 1;
573
574 p->offset = mt->offset;
575 p->rmargin = mt->offset + len;
576 if (ival < 0)
577 break;
578
579 /* Set the saved left-margin. */
580 mt->lmargin = (size_t)ival;
581
582 /* Don't print the length value. */
583 for (nn = n->child; nn->next; nn = nn->next)
584 print_node(p, mt, nn, m);
585 return(0);
586 case (MAN_BODY):
587 p->offset = mt->offset + len;
588 p->rmargin = p->maxrmargin;
589 break;
590 default:
591 break;
592 }
593
594 return(1);
595 }
596
597
598 /* ARGSUSED */
599 static void
600 post_IP(DECL_ARGS)
601 {
602
603 switch (n->type) {
604 case (MAN_HEAD):
605 term_flushln(p);
606 p->flags &= ~TERMP_NOBREAK;
607 p->flags &= ~TERMP_TWOSPACE;
608 p->rmargin = p->maxrmargin;
609 break;
610 case (MAN_BODY):
611 term_flushln(p);
612 p->flags &= ~TERMP_NOLPAD;
613 break;
614 default:
615 break;
616 }
617 }
618
619
620 /* ARGSUSED */
621 static int
622 pre_TP(DECL_ARGS)
623 {
624 const struct man_node *nn;
625 size_t len;
626 int ival;
627
628 switch (n->type) {
629 case (MAN_HEAD):
630 p->flags |= TERMP_NOBREAK;
631 p->flags |= TERMP_TWOSPACE;
632 break;
633 case (MAN_BODY):
634 p->flags |= TERMP_NOLPAD;
635 p->flags |= TERMP_NOSPACE;
636 break;
637 case (MAN_BLOCK):
638 print_bvspace(p, n);
639 /* FALLTHROUGH */
640 default:
641 return(1);
642 }
643
644 len = (size_t)mt->lmargin;
645 ival = -1;
646
647 /* Calculate offset. */
648
649 if (NULL != (nn = n->parent->head->child))
650 if (NULL != nn->next)
651 if ((ival = arg2width(nn)) >= 0)
652 len = (size_t)ival;
653
654 switch (n->type) {
655 case (MAN_HEAD):
656 /* Handle zero-length properly. */
657 if (0 == len)
658 len = 1;
659
660 p->offset = mt->offset;
661 p->rmargin = mt->offset + len;
662
663 /* Don't print same-line elements. */
664 for (nn = n->child; nn; nn = nn->next)
665 if (nn->line > n->line)
666 print_node(p, mt, nn, m);
667
668 if (ival >= 0)
669 mt->lmargin = (size_t)ival;
670
671 return(0);
672 case (MAN_BODY):
673 p->offset = mt->offset + len;
674 p->rmargin = p->maxrmargin;
675 break;
676 default:
677 break;
678 }
679
680 return(1);
681 }
682
683
684 /* ARGSUSED */
685 static void
686 post_TP(DECL_ARGS)
687 {
688
689 switch (n->type) {
690 case (MAN_HEAD):
691 term_flushln(p);
692 p->flags &= ~TERMP_NOBREAK;
693 p->flags &= ~TERMP_TWOSPACE;
694 p->rmargin = p->maxrmargin;
695 break;
696 case (MAN_BODY):
697 term_flushln(p);
698 p->flags &= ~TERMP_NOLPAD;
699 break;
700 default:
701 break;
702 }
703 }
704
705
706 /* ARGSUSED */
707 static int
708 pre_SS(DECL_ARGS)
709 {
710
711 switch (n->type) {
712 case (MAN_BLOCK):
713 mt->lmargin = INDENT;
714 mt->offset = INDENT;
715 /* If following a prior empty `SS', no vspace. */
716 if (n->prev && MAN_SS == n->prev->tok)
717 if (NULL == n->prev->body->child)
718 break;
719 if (NULL == n->prev)
720 break;
721 term_vspace(p);
722 break;
723 case (MAN_HEAD):
724 p->bold++;
725 p->offset = HALFINDENT;
726 break;
727 case (MAN_BODY):
728 p->offset = mt->offset;
729 break;
730 default:
731 break;
732 }
733
734 return(1);
735 }
736
737
738 /* ARGSUSED */
739 static void
740 post_SS(DECL_ARGS)
741 {
742
743 switch (n->type) {
744 case (MAN_HEAD):
745 term_newln(p);
746 p->bold--;
747 break;
748 case (MAN_BODY):
749 term_newln(p);
750 break;
751 default:
752 break;
753 }
754 }
755
756
757 /* ARGSUSED */
758 static int
759 pre_SH(DECL_ARGS)
760 {
761
762 switch (n->type) {
763 case (MAN_BLOCK):
764 mt->lmargin = INDENT;
765 mt->offset = INDENT;
766 /* If following a prior empty `SH', no vspace. */
767 if (n->prev && MAN_SH == n->prev->tok)
768 if (NULL == n->prev->body->child)
769 break;
770 term_vspace(p);
771 break;
772 case (MAN_HEAD):
773 p->bold++;
774 p->offset = 0;
775 break;
776 case (MAN_BODY):
777 p->offset = mt->offset;
778 break;
779 default:
780 break;
781 }
782
783 return(1);
784 }
785
786
787 /* ARGSUSED */
788 static void
789 post_SH(DECL_ARGS)
790 {
791
792 switch (n->type) {
793 case (MAN_HEAD):
794 term_newln(p);
795 p->bold--;
796 break;
797 case (MAN_BODY):
798 term_newln(p);
799 break;
800 default:
801 break;
802 }
803 }
804
805
806 /* ARGSUSED */
807 static int
808 pre_RS(DECL_ARGS)
809 {
810 const struct man_node *nn;
811 int ival;
812
813 switch (n->type) {
814 case (MAN_BLOCK):
815 term_newln(p);
816 return(1);
817 case (MAN_HEAD):
818 return(0);
819 default:
820 break;
821 }
822
823 if (NULL == (nn = n->parent->head->child)) {
824 mt->offset = mt->lmargin + INDENT;
825 p->offset = mt->offset;
826 return(1);
827 }
828
829 if ((ival = arg2width(nn)) < 0)
830 return(1);
831
832 mt->offset = INDENT + (size_t)ival;
833 p->offset = mt->offset;
834
835 return(1);
836 }
837
838
839 /* ARGSUSED */
840 static void
841 post_RS(DECL_ARGS)
842 {
843
844 switch (n->type) {
845 case (MAN_BLOCK):
846 mt->offset = mt->lmargin = INDENT;
847 break;
848 default:
849 term_newln(p);
850 p->offset = INDENT;
851 break;
852 }
853 }
854
855
856 static void
857 print_node(DECL_ARGS)
858 {
859 int c, sz;
860
861 c = 1;
862
863 switch (n->type) {
864 case(MAN_TEXT):
865 if (0 == *n->string) {
866 term_vspace(p);
867 break;
868 }
869 /*
870 * Note! This is hacky. Here, we recognise the `\c'
871 * escape embedded in so many -man pages. It's supposed
872 * to remove the subsequent space, so we mark NOSPACE if
873 * it's encountered in the string.
874 */
875 sz = (int)strlen(n->string);
876 term_word(p, n->string);
877 if (sz >= 2 && n->string[sz - 1] == 'c' &&
878 n->string[sz - 2] == '\\')
879 p->flags |= TERMP_NOSPACE;
880 /* FIXME: this means that macro lines are munged! */
881 if (MANT_LITERAL & mt->fl) {
882 p->flags |= TERMP_NOSPACE;
883 term_flushln(p);
884 }
885 break;
886 default:
887 if (termacts[n->tok].pre)
888 c = (*termacts[n->tok].pre)(p, mt, n, m);
889 break;
890 }
891
892 if (c && n->child)
893 print_body(p, mt, n->child, m);
894
895 if (MAN_TEXT != n->type)
896 if (termacts[n->tok].post)
897 (*termacts[n->tok].post)(p, mt, n, m);
898 }
899
900
901 static void
902 print_body(DECL_ARGS)
903 {
904
905 print_node(p, mt, n, m);
906 if ( ! n->next)
907 return;
908 print_body(p, mt, n->next, m);
909 }
910
911
912 static void
913 print_foot(struct termp *p, const struct man_meta *meta)
914 {
915 char buf[DATESIZ];
916
917 time2a(meta->date, buf, DATESIZ);
918
919 term_vspace(p);
920
921 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
922 p->rmargin = p->maxrmargin - strlen(buf);
923 p->offset = 0;
924
925 if (meta->source)
926 term_word(p, meta->source);
927 if (meta->source)
928 term_word(p, "");
929 term_flushln(p);
930
931 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
932 p->offset = p->rmargin;
933 p->rmargin = p->maxrmargin;
934 p->flags &= ~TERMP_NOBREAK;
935
936 term_word(p, buf);
937 term_flushln(p);
938 }
939
940
941 static void
942 print_head(struct termp *p, const struct man_meta *meta)
943 {
944 char *buf, *title;
945
946 p->rmargin = p->maxrmargin;
947 p->offset = 0;
948
949 if (NULL == (buf = malloc(p->rmargin)))
950 err(EXIT_FAILURE, "malloc");
951 if (NULL == (title = malloc(p->rmargin)))
952 err(EXIT_FAILURE, "malloc");
953
954 if (meta->vol)
955 (void)strlcpy(buf, meta->vol, p->rmargin);
956 else
957 *buf = 0;
958
959 (void)snprintf(title, p->rmargin, "%s(%d)",
960 meta->title, meta->msec);
961
962 p->offset = 0;
963 p->rmargin = (p->maxrmargin - strlen(buf) + 1) / 2;
964 p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
965
966 term_word(p, title);
967 term_flushln(p);
968
969 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
970 p->offset = p->rmargin;
971 p->rmargin = p->maxrmargin - strlen(title);
972
973 term_word(p, buf);
974 term_flushln(p);
975
976 p->offset = p->rmargin;
977 p->rmargin = p->maxrmargin;
978 p->flags &= ~TERMP_NOBREAK;
979 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
980
981 term_word(p, title);
982 term_flushln(p);
983
984 p->rmargin = p->maxrmargin;
985 p->offset = 0;
986 p->flags &= ~TERMP_NOSPACE;
987
988 free(title);
989 free(buf);
990 }
991