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