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