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