]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/mdocml/man_term.c
Fix ipfw invalid mbuf handling.
[FreeBSD/FreeBSD.git] / contrib / mdocml / man_term.c
1 /*      $Id: man_term.c,v 1.209 2017/07/31 15:19:06 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2010-2015, 2017 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 AUTHORS DISCLAIM ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS 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 #include "config.h"
19
20 #include <sys/types.h>
21
22 #include <assert.h>
23 #include <ctype.h>
24 #include <limits.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28
29 #include "mandoc_aux.h"
30 #include "mandoc.h"
31 #include "roff.h"
32 #include "man.h"
33 #include "out.h"
34 #include "term.h"
35 #include "main.h"
36
37 #define MAXMARGINS        64 /* maximum number of indented scopes */
38
39 struct  mtermp {
40         int               fl;
41 #define MANT_LITERAL     (1 << 0)
42         int               lmargin[MAXMARGINS]; /* margins (incl. vis. page) */
43         int               lmargincur; /* index of current margin */
44         int               lmarginsz; /* actual number of nested margins */
45         size_t            offset; /* default offset to visible page */
46         int               pardist; /* vert. space before par., unit: [v] */
47 };
48
49 #define DECL_ARGS         struct termp *p, \
50                           struct mtermp *mt, \
51                           struct roff_node *n, \
52                           const struct roff_meta *meta
53
54 struct  termact {
55         int             (*pre)(DECL_ARGS);
56         void            (*post)(DECL_ARGS);
57         int               flags;
58 #define MAN_NOTEXT       (1 << 0) /* Never has text children. */
59 };
60
61 static  void              print_man_nodelist(DECL_ARGS);
62 static  void              print_man_node(DECL_ARGS);
63 static  void              print_man_head(struct termp *,
64                                 const struct roff_meta *);
65 static  void              print_man_foot(struct termp *,
66                                 const struct roff_meta *);
67 static  void              print_bvspace(struct termp *,
68                                 const struct roff_node *, int);
69
70 static  int               pre_B(DECL_ARGS);
71 static  int               pre_DT(DECL_ARGS);
72 static  int               pre_HP(DECL_ARGS);
73 static  int               pre_I(DECL_ARGS);
74 static  int               pre_IP(DECL_ARGS);
75 static  int               pre_OP(DECL_ARGS);
76 static  int               pre_PD(DECL_ARGS);
77 static  int               pre_PP(DECL_ARGS);
78 static  int               pre_RS(DECL_ARGS);
79 static  int               pre_SH(DECL_ARGS);
80 static  int               pre_SS(DECL_ARGS);
81 static  int               pre_TP(DECL_ARGS);
82 static  int               pre_UR(DECL_ARGS);
83 static  int               pre_alternate(DECL_ARGS);
84 static  int               pre_ign(DECL_ARGS);
85 static  int               pre_in(DECL_ARGS);
86 static  int               pre_literal(DECL_ARGS);
87
88 static  void              post_IP(DECL_ARGS);
89 static  void              post_HP(DECL_ARGS);
90 static  void              post_RS(DECL_ARGS);
91 static  void              post_SH(DECL_ARGS);
92 static  void              post_SS(DECL_ARGS);
93 static  void              post_TP(DECL_ARGS);
94 static  void              post_UR(DECL_ARGS);
95
96 static  const struct termact __termacts[MAN_MAX - MAN_TH] = {
97         { NULL, NULL, 0 }, /* TH */
98         { pre_SH, post_SH, 0 }, /* SH */
99         { pre_SS, post_SS, 0 }, /* SS */
100         { pre_TP, post_TP, 0 }, /* TP */
101         { pre_PP, NULL, 0 }, /* LP */
102         { pre_PP, NULL, 0 }, /* PP */
103         { pre_PP, NULL, 0 }, /* P */
104         { pre_IP, post_IP, 0 }, /* IP */
105         { pre_HP, post_HP, 0 }, /* HP */
106         { NULL, NULL, 0 }, /* SM */
107         { pre_B, NULL, 0 }, /* SB */
108         { pre_alternate, NULL, 0 }, /* BI */
109         { pre_alternate, NULL, 0 }, /* IB */
110         { pre_alternate, NULL, 0 }, /* BR */
111         { pre_alternate, NULL, 0 }, /* RB */
112         { NULL, NULL, 0 }, /* R */
113         { pre_B, NULL, 0 }, /* B */
114         { pre_I, NULL, 0 }, /* I */
115         { pre_alternate, NULL, 0 }, /* IR */
116         { pre_alternate, NULL, 0 }, /* RI */
117         { pre_literal, NULL, 0 }, /* nf */
118         { pre_literal, NULL, 0 }, /* fi */
119         { NULL, NULL, 0 }, /* RE */
120         { pre_RS, post_RS, 0 }, /* RS */
121         { pre_DT, NULL, 0 }, /* DT */
122         { pre_ign, NULL, MAN_NOTEXT }, /* UC */
123         { pre_PD, NULL, MAN_NOTEXT }, /* PD */
124         { pre_ign, NULL, 0 }, /* AT */
125         { pre_in, NULL, MAN_NOTEXT }, /* in */
126         { pre_OP, NULL, 0 }, /* OP */
127         { pre_literal, NULL, 0 }, /* EX */
128         { pre_literal, NULL, 0 }, /* EE */
129         { pre_UR, post_UR, 0 }, /* UR */
130         { NULL, NULL, 0 }, /* UE */
131         { pre_UR, post_UR, 0 }, /* MT */
132         { NULL, NULL, 0 }, /* ME */
133 };
134 static  const struct termact *termacts = __termacts - MAN_TH;
135
136
137 void
138 terminal_man(void *arg, const struct roff_man *man)
139 {
140         struct termp            *p;
141         struct roff_node        *n;
142         struct mtermp            mt;
143         size_t                   save_defindent;
144
145         p = (struct termp *)arg;
146         save_defindent = p->defindent;
147         if (p->synopsisonly == 0 && p->defindent == 0)
148                 p->defindent = 7;
149         p->tcol->rmargin = p->maxrmargin = p->defrmargin;
150         term_tab_set(p, NULL);
151         term_tab_set(p, "T");
152         term_tab_set(p, ".5i");
153
154         memset(&mt, 0, sizeof(struct mtermp));
155         mt.lmargin[mt.lmargincur] = term_len(p, p->defindent);
156         mt.offset = term_len(p, p->defindent);
157         mt.pardist = 1;
158
159         n = man->first->child;
160         if (p->synopsisonly) {
161                 while (n != NULL) {
162                         if (n->tok == MAN_SH &&
163                             n->child->child->type == ROFFT_TEXT &&
164                             !strcmp(n->child->child->string, "SYNOPSIS")) {
165                                 if (n->child->next->child != NULL)
166                                         print_man_nodelist(p, &mt,
167                                             n->child->next->child,
168                                             &man->meta);
169                                 term_newln(p);
170                                 break;
171                         }
172                         n = n->next;
173                 }
174         } else {
175                 term_begin(p, print_man_head, print_man_foot, &man->meta);
176                 p->flags |= TERMP_NOSPACE;
177                 if (n != NULL)
178                         print_man_nodelist(p, &mt, n, &man->meta);
179                 term_end(p);
180         }
181         p->defindent = save_defindent;
182 }
183
184 /*
185  * Printing leading vertical space before a block.
186  * This is used for the paragraph macros.
187  * The rules are pretty simple, since there's very little nesting going
188  * on here.  Basically, if we're the first within another block (SS/SH),
189  * then don't emit vertical space.  If we are (RS), then do.  If not the
190  * first, print it.
191  */
192 static void
193 print_bvspace(struct termp *p, const struct roff_node *n, int pardist)
194 {
195         int      i;
196
197         term_newln(p);
198
199         if (n->body && n->body->child)
200                 if (n->body->child->type == ROFFT_TBL)
201                         return;
202
203         if (n->parent->type == ROFFT_ROOT || n->parent->tok != MAN_RS)
204                 if (NULL == n->prev)
205                         return;
206
207         for (i = 0; i < pardist; i++)
208                 term_vspace(p);
209 }
210
211
212 static int
213 pre_ign(DECL_ARGS)
214 {
215
216         return 0;
217 }
218
219 static int
220 pre_I(DECL_ARGS)
221 {
222
223         term_fontrepl(p, TERMFONT_UNDER);
224         return 1;
225 }
226
227 static int
228 pre_literal(DECL_ARGS)
229 {
230
231         term_newln(p);
232
233         if (n->tok == MAN_nf || n->tok == MAN_EX)
234                 mt->fl |= MANT_LITERAL;
235         else
236                 mt->fl &= ~MANT_LITERAL;
237
238         /*
239          * Unlike .IP and .TP, .HP does not have a HEAD.
240          * So in case a second call to term_flushln() is needed,
241          * indentation has to be set up explicitly.
242          */
243         if (n->parent->tok == MAN_HP && p->tcol->rmargin < p->maxrmargin) {
244                 p->tcol->offset = p->tcol->rmargin;
245                 p->tcol->rmargin = p->maxrmargin;
246                 p->trailspace = 0;
247                 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
248                 p->flags |= TERMP_NOSPACE;
249         }
250
251         return 0;
252 }
253
254 static int
255 pre_PD(DECL_ARGS)
256 {
257         struct roffsu    su;
258
259         n = n->child;
260         if (n == NULL) {
261                 mt->pardist = 1;
262                 return 0;
263         }
264         assert(n->type == ROFFT_TEXT);
265         if (a2roffsu(n->string, &su, SCALE_VS) != NULL)
266                 mt->pardist = term_vspan(p, &su);
267         return 0;
268 }
269
270 static int
271 pre_alternate(DECL_ARGS)
272 {
273         enum termfont            font[2];
274         struct roff_node        *nn;
275         int                      savelit, i;
276
277         switch (n->tok) {
278         case MAN_RB:
279                 font[0] = TERMFONT_NONE;
280                 font[1] = TERMFONT_BOLD;
281                 break;
282         case MAN_RI:
283                 font[0] = TERMFONT_NONE;
284                 font[1] = TERMFONT_UNDER;
285                 break;
286         case MAN_BR:
287                 font[0] = TERMFONT_BOLD;
288                 font[1] = TERMFONT_NONE;
289                 break;
290         case MAN_BI:
291                 font[0] = TERMFONT_BOLD;
292                 font[1] = TERMFONT_UNDER;
293                 break;
294         case MAN_IR:
295                 font[0] = TERMFONT_UNDER;
296                 font[1] = TERMFONT_NONE;
297                 break;
298         case MAN_IB:
299                 font[0] = TERMFONT_UNDER;
300                 font[1] = TERMFONT_BOLD;
301                 break;
302         default:
303                 abort();
304         }
305
306         savelit = MANT_LITERAL & mt->fl;
307         mt->fl &= ~MANT_LITERAL;
308
309         for (i = 0, nn = n->child; nn; nn = nn->next, i = 1 - i) {
310                 term_fontrepl(p, font[i]);
311                 if (savelit && NULL == nn->next)
312                         mt->fl |= MANT_LITERAL;
313                 assert(nn->type == ROFFT_TEXT);
314                 term_word(p, nn->string);
315                 if (nn->flags & NODE_EOS)
316                         p->flags |= TERMP_SENTENCE;
317                 if (nn->next)
318                         p->flags |= TERMP_NOSPACE;
319         }
320
321         return 0;
322 }
323
324 static int
325 pre_B(DECL_ARGS)
326 {
327
328         term_fontrepl(p, TERMFONT_BOLD);
329         return 1;
330 }
331
332 static int
333 pre_OP(DECL_ARGS)
334 {
335
336         term_word(p, "[");
337         p->flags |= TERMP_NOSPACE;
338
339         if (NULL != (n = n->child)) {
340                 term_fontrepl(p, TERMFONT_BOLD);
341                 term_word(p, n->string);
342         }
343         if (NULL != n && NULL != n->next) {
344                 term_fontrepl(p, TERMFONT_UNDER);
345                 term_word(p, n->next->string);
346         }
347
348         term_fontrepl(p, TERMFONT_NONE);
349         p->flags |= TERMP_NOSPACE;
350         term_word(p, "]");
351         return 0;
352 }
353
354 static int
355 pre_in(DECL_ARGS)
356 {
357         struct roffsu    su;
358         const char      *cp;
359         size_t           v;
360         int              less;
361
362         term_newln(p);
363
364         if (n->child == NULL) {
365                 p->tcol->offset = mt->offset;
366                 return 0;
367         }
368
369         cp = n->child->string;
370         less = 0;
371
372         if ('-' == *cp)
373                 less = -1;
374         else if ('+' == *cp)
375                 less = 1;
376         else
377                 cp--;
378
379         if (a2roffsu(++cp, &su, SCALE_EN) == NULL)
380                 return 0;
381
382         v = term_hen(p, &su);
383
384         if (less < 0)
385                 p->tcol->offset -= p->tcol->offset > v ? v : p->tcol->offset;
386         else if (less > 0)
387                 p->tcol->offset += v;
388         else
389                 p->tcol->offset = v;
390         if (p->tcol->offset > SHRT_MAX)
391                 p->tcol->offset = term_len(p, p->defindent);
392
393         return 0;
394 }
395
396 static int
397 pre_DT(DECL_ARGS)
398 {
399         term_tab_set(p, NULL);
400         term_tab_set(p, "T");
401         term_tab_set(p, ".5i");
402         return 0;
403 }
404
405 static int
406 pre_HP(DECL_ARGS)
407 {
408         struct roffsu            su;
409         const struct roff_node  *nn;
410         int                      len;
411
412         switch (n->type) {
413         case ROFFT_BLOCK:
414                 print_bvspace(p, n, mt->pardist);
415                 return 1;
416         case ROFFT_BODY:
417                 break;
418         default:
419                 return 0;
420         }
421
422         if ( ! (MANT_LITERAL & mt->fl)) {
423                 p->flags |= TERMP_NOBREAK | TERMP_BRIND;
424                 p->trailspace = 2;
425         }
426
427         /* Calculate offset. */
428
429         if ((nn = n->parent->head->child) != NULL &&
430             a2roffsu(nn->string, &su, SCALE_EN) != NULL) {
431                 len = term_hen(p, &su);
432                 if (len < 0 && (size_t)(-len) > mt->offset)
433                         len = -mt->offset;
434                 else if (len > SHRT_MAX)
435                         len = term_len(p, p->defindent);
436                 mt->lmargin[mt->lmargincur] = len;
437         } else
438                 len = mt->lmargin[mt->lmargincur];
439
440         p->tcol->offset = mt->offset;
441         p->tcol->rmargin = mt->offset + len;
442         return 1;
443 }
444
445 static void
446 post_HP(DECL_ARGS)
447 {
448
449         switch (n->type) {
450         case ROFFT_BODY:
451                 term_newln(p);
452
453                 /*
454                  * Compatibility with a groff bug.
455                  * The .HP macro uses the undocumented .tag request
456                  * which causes a line break and cancels no-space
457                  * mode even if there isn't any output.
458                  */
459
460                 if (n->child == NULL)
461                         term_vspace(p);
462
463                 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
464                 p->trailspace = 0;
465                 p->tcol->offset = mt->offset;
466                 p->tcol->rmargin = p->maxrmargin;
467                 break;
468         default:
469                 break;
470         }
471 }
472
473 static int
474 pre_PP(DECL_ARGS)
475 {
476
477         switch (n->type) {
478         case ROFFT_BLOCK:
479                 mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
480                 print_bvspace(p, n, mt->pardist);
481                 break;
482         default:
483                 p->tcol->offset = mt->offset;
484                 break;
485         }
486
487         return n->type != ROFFT_HEAD;
488 }
489
490 static int
491 pre_IP(DECL_ARGS)
492 {
493         struct roffsu            su;
494         const struct roff_node  *nn;
495         int                      len, savelit;
496
497         switch (n->type) {
498         case ROFFT_BODY:
499                 p->flags |= TERMP_NOSPACE;
500                 break;
501         case ROFFT_HEAD:
502                 p->flags |= TERMP_NOBREAK;
503                 p->trailspace = 1;
504                 break;
505         case ROFFT_BLOCK:
506                 print_bvspace(p, n, mt->pardist);
507                 /* FALLTHROUGH */
508         default:
509                 return 1;
510         }
511
512         /* Calculate the offset from the optional second argument. */
513         if ((nn = n->parent->head->child) != NULL &&
514             (nn = nn->next) != NULL &&
515             a2roffsu(nn->string, &su, SCALE_EN) != NULL) {
516                 len = term_hen(p, &su);
517                 if (len < 0 && (size_t)(-len) > mt->offset)
518                         len = -mt->offset;
519                 else if (len > SHRT_MAX)
520                         len = term_len(p, p->defindent);
521                 mt->lmargin[mt->lmargincur] = len;
522         } else
523                 len = mt->lmargin[mt->lmargincur];
524
525         switch (n->type) {
526         case ROFFT_HEAD:
527                 p->tcol->offset = mt->offset;
528                 p->tcol->rmargin = mt->offset + len;
529
530                 savelit = MANT_LITERAL & mt->fl;
531                 mt->fl &= ~MANT_LITERAL;
532
533                 if (n->child)
534                         print_man_node(p, mt, n->child, meta);
535
536                 if (savelit)
537                         mt->fl |= MANT_LITERAL;
538
539                 return 0;
540         case ROFFT_BODY:
541                 p->tcol->offset = mt->offset + len;
542                 p->tcol->rmargin = p->maxrmargin;
543                 break;
544         default:
545                 break;
546         }
547
548         return 1;
549 }
550
551 static void
552 post_IP(DECL_ARGS)
553 {
554
555         switch (n->type) {
556         case ROFFT_HEAD:
557                 term_flushln(p);
558                 p->flags &= ~TERMP_NOBREAK;
559                 p->trailspace = 0;
560                 p->tcol->rmargin = p->maxrmargin;
561                 break;
562         case ROFFT_BODY:
563                 term_newln(p);
564                 p->tcol->offset = mt->offset;
565                 break;
566         default:
567                 break;
568         }
569 }
570
571 static int
572 pre_TP(DECL_ARGS)
573 {
574         struct roffsu            su;
575         struct roff_node        *nn;
576         int                      len, savelit;
577
578         switch (n->type) {
579         case ROFFT_HEAD:
580                 p->flags |= TERMP_NOBREAK | TERMP_BRTRSP;
581                 p->trailspace = 1;
582                 break;
583         case ROFFT_BODY:
584                 p->flags |= TERMP_NOSPACE;
585                 break;
586         case ROFFT_BLOCK:
587                 print_bvspace(p, n, mt->pardist);
588                 /* FALLTHROUGH */
589         default:
590                 return 1;
591         }
592
593         /* Calculate offset. */
594
595         if ((nn = n->parent->head->child) != NULL &&
596             nn->string != NULL && ! (NODE_LINE & nn->flags) &&
597             a2roffsu(nn->string, &su, SCALE_EN) != NULL) {
598                 len = term_hen(p, &su);
599                 if (len < 0 && (size_t)(-len) > mt->offset)
600                         len = -mt->offset;
601                 else if (len > SHRT_MAX)
602                         len = term_len(p, p->defindent);
603                 mt->lmargin[mt->lmargincur] = len;
604         } else
605                 len = mt->lmargin[mt->lmargincur];
606
607         switch (n->type) {
608         case ROFFT_HEAD:
609                 p->tcol->offset = mt->offset;
610                 p->tcol->rmargin = mt->offset + len;
611
612                 savelit = MANT_LITERAL & mt->fl;
613                 mt->fl &= ~MANT_LITERAL;
614
615                 /* Don't print same-line elements. */
616                 nn = n->child;
617                 while (NULL != nn && 0 == (NODE_LINE & nn->flags))
618                         nn = nn->next;
619
620                 while (NULL != nn) {
621                         print_man_node(p, mt, nn, meta);
622                         nn = nn->next;
623                 }
624
625                 if (savelit)
626                         mt->fl |= MANT_LITERAL;
627                 return 0;
628         case ROFFT_BODY:
629                 p->tcol->offset = mt->offset + len;
630                 p->tcol->rmargin = p->maxrmargin;
631                 p->trailspace = 0;
632                 p->flags &= ~(TERMP_NOBREAK | TERMP_BRTRSP);
633                 break;
634         default:
635                 break;
636         }
637
638         return 1;
639 }
640
641 static void
642 post_TP(DECL_ARGS)
643 {
644
645         switch (n->type) {
646         case ROFFT_HEAD:
647                 term_flushln(p);
648                 break;
649         case ROFFT_BODY:
650                 term_newln(p);
651                 p->tcol->offset = mt->offset;
652                 break;
653         default:
654                 break;
655         }
656 }
657
658 static int
659 pre_SS(DECL_ARGS)
660 {
661         int      i;
662
663         switch (n->type) {
664         case ROFFT_BLOCK:
665                 mt->fl &= ~MANT_LITERAL;
666                 mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
667                 mt->offset = term_len(p, p->defindent);
668
669                 /*
670                  * No vertical space before the first subsection
671                  * and after an empty subsection.
672                  */
673
674                 do {
675                         n = n->prev;
676                 } while (n != NULL && n->tok >= MAN_TH &&
677                     termacts[n->tok].flags & MAN_NOTEXT);
678                 if (n == NULL || (n->tok == MAN_SS && n->body->child == NULL))
679                         break;
680
681                 for (i = 0; i < mt->pardist; i++)
682                         term_vspace(p);
683                 break;
684         case ROFFT_HEAD:
685                 term_fontrepl(p, TERMFONT_BOLD);
686                 p->tcol->offset = term_len(p, 3);
687                 p->tcol->rmargin = mt->offset;
688                 p->trailspace = mt->offset;
689                 p->flags |= TERMP_NOBREAK | TERMP_BRIND;
690                 break;
691         case ROFFT_BODY:
692                 p->tcol->offset = mt->offset;
693                 p->tcol->rmargin = p->maxrmargin;
694                 p->trailspace = 0;
695                 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
696                 break;
697         default:
698                 break;
699         }
700
701         return 1;
702 }
703
704 static void
705 post_SS(DECL_ARGS)
706 {
707
708         switch (n->type) {
709         case ROFFT_HEAD:
710                 term_newln(p);
711                 break;
712         case ROFFT_BODY:
713                 term_newln(p);
714                 break;
715         default:
716                 break;
717         }
718 }
719
720 static int
721 pre_SH(DECL_ARGS)
722 {
723         int      i;
724
725         switch (n->type) {
726         case ROFFT_BLOCK:
727                 mt->fl &= ~MANT_LITERAL;
728                 mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
729                 mt->offset = term_len(p, p->defindent);
730
731                 /*
732                  * No vertical space before the first section
733                  * and after an empty section.
734                  */
735
736                 do {
737                         n = n->prev;
738                 } while (n != NULL && n->tok >= MAN_TH &&
739                     termacts[n->tok].flags & MAN_NOTEXT);
740                 if (n == NULL || (n->tok == MAN_SH && n->body->child == NULL))
741                         break;
742
743                 for (i = 0; i < mt->pardist; i++)
744                         term_vspace(p);
745                 break;
746         case ROFFT_HEAD:
747                 term_fontrepl(p, TERMFONT_BOLD);
748                 p->tcol->offset = 0;
749                 p->tcol->rmargin = mt->offset;
750                 p->trailspace = mt->offset;
751                 p->flags |= TERMP_NOBREAK | TERMP_BRIND;
752                 break;
753         case ROFFT_BODY:
754                 p->tcol->offset = mt->offset;
755                 p->tcol->rmargin = p->maxrmargin;
756                 p->trailspace = 0;
757                 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
758                 break;
759         default:
760                 break;
761         }
762
763         return 1;
764 }
765
766 static void
767 post_SH(DECL_ARGS)
768 {
769
770         switch (n->type) {
771         case ROFFT_HEAD:
772                 term_newln(p);
773                 break;
774         case ROFFT_BODY:
775                 term_newln(p);
776                 break;
777         default:
778                 break;
779         }
780 }
781
782 static int
783 pre_RS(DECL_ARGS)
784 {
785         struct roffsu    su;
786
787         switch (n->type) {
788         case ROFFT_BLOCK:
789                 term_newln(p);
790                 return 1;
791         case ROFFT_HEAD:
792                 return 0;
793         default:
794                 break;
795         }
796
797         n = n->parent->head;
798         n->aux = SHRT_MAX + 1;
799         if (n->child == NULL)
800                 n->aux = mt->lmargin[mt->lmargincur];
801         else if (a2roffsu(n->child->string, &su, SCALE_EN) != NULL)
802                 n->aux = term_hen(p, &su);
803         if (n->aux < 0 && (size_t)(-n->aux) > mt->offset)
804                 n->aux = -mt->offset;
805         else if (n->aux > SHRT_MAX)
806                 n->aux = term_len(p, p->defindent);
807
808         mt->offset += n->aux;
809         p->tcol->offset = mt->offset;
810         p->tcol->rmargin = p->maxrmargin;
811
812         if (++mt->lmarginsz < MAXMARGINS)
813                 mt->lmargincur = mt->lmarginsz;
814
815         mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
816         return 1;
817 }
818
819 static void
820 post_RS(DECL_ARGS)
821 {
822
823         switch (n->type) {
824         case ROFFT_BLOCK:
825                 return;
826         case ROFFT_HEAD:
827                 return;
828         default:
829                 term_newln(p);
830                 break;
831         }
832
833         mt->offset -= n->parent->head->aux;
834         p->tcol->offset = mt->offset;
835
836         if (--mt->lmarginsz < MAXMARGINS)
837                 mt->lmargincur = mt->lmarginsz;
838 }
839
840 static int
841 pre_UR(DECL_ARGS)
842 {
843
844         return n->type != ROFFT_HEAD;
845 }
846
847 static void
848 post_UR(DECL_ARGS)
849 {
850
851         if (n->type != ROFFT_BLOCK)
852                 return;
853
854         term_word(p, "<");
855         p->flags |= TERMP_NOSPACE;
856
857         if (NULL != n->child->child)
858                 print_man_node(p, mt, n->child->child, meta);
859
860         p->flags |= TERMP_NOSPACE;
861         term_word(p, ">");
862 }
863
864 static void
865 print_man_node(DECL_ARGS)
866 {
867         int              c;
868
869         switch (n->type) {
870         case ROFFT_TEXT:
871                 /*
872                  * If we have a blank line, output a vertical space.
873                  * If we have a space as the first character, break
874                  * before printing the line's data.
875                  */
876                 if (*n->string == '\0') {
877                         if (p->flags & TERMP_NONEWLINE)
878                                 term_newln(p);
879                         else
880                                 term_vspace(p);
881                         return;
882                 } else if (*n->string == ' ' && n->flags & NODE_LINE &&
883                     (p->flags & TERMP_NONEWLINE) == 0)
884                         term_newln(p);
885
886                 term_word(p, n->string);
887                 goto out;
888
889         case ROFFT_EQN:
890                 if ( ! (n->flags & NODE_LINE))
891                         p->flags |= TERMP_NOSPACE;
892                 term_eqn(p, n->eqn);
893                 if (n->next != NULL && ! (n->next->flags & NODE_LINE))
894                         p->flags |= TERMP_NOSPACE;
895                 return;
896         case ROFFT_TBL:
897                 if (p->tbl.cols == NULL)
898                         term_vspace(p);
899                 term_tbl(p, n->span);
900                 return;
901         default:
902                 break;
903         }
904
905         if (n->tok < ROFF_MAX) {
906                 roff_term_pre(p, n);
907                 return;
908         }
909
910         assert(n->tok >= MAN_TH && n->tok <= MAN_MAX);
911         if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
912                 term_fontrepl(p, TERMFONT_NONE);
913
914         c = 1;
915         if (termacts[n->tok].pre)
916                 c = (*termacts[n->tok].pre)(p, mt, n, meta);
917
918         if (c && n->child)
919                 print_man_nodelist(p, mt, n->child, meta);
920
921         if (termacts[n->tok].post)
922                 (*termacts[n->tok].post)(p, mt, n, meta);
923         if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
924                 term_fontrepl(p, TERMFONT_NONE);
925
926 out:
927         /*
928          * If we're in a literal context, make sure that words
929          * together on the same line stay together.  This is a
930          * POST-printing call, so we check the NEXT word.  Since
931          * -man doesn't have nested macros, we don't need to be
932          * more specific than this.
933          */
934         if (mt->fl & MANT_LITERAL &&
935             ! (p->flags & (TERMP_NOBREAK | TERMP_NONEWLINE)) &&
936             (n->next == NULL || n->next->flags & NODE_LINE)) {
937                 p->flags |= TERMP_BRNEVER | TERMP_NOSPACE;
938                 if (n->string != NULL && *n->string != '\0')
939                         term_flushln(p);
940                 else
941                         term_newln(p);
942                 p->flags &= ~TERMP_BRNEVER;
943                 if (p->tcol->rmargin < p->maxrmargin &&
944                     n->parent->tok == MAN_HP) {
945                         p->tcol->offset = p->tcol->rmargin;
946                         p->tcol->rmargin = p->maxrmargin;
947                 }
948         }
949         if (NODE_EOS & n->flags)
950                 p->flags |= TERMP_SENTENCE;
951 }
952
953
954 static void
955 print_man_nodelist(DECL_ARGS)
956 {
957
958         while (n != NULL) {
959                 print_man_node(p, mt, n, meta);
960                 n = n->next;
961         }
962 }
963
964 static void
965 print_man_foot(struct termp *p, const struct roff_meta *meta)
966 {
967         char                    *title;
968         size_t                   datelen, titlen;
969
970         assert(meta->title);
971         assert(meta->msec);
972         assert(meta->date);
973
974         term_fontrepl(p, TERMFONT_NONE);
975
976         if (meta->hasbody)
977                 term_vspace(p);
978
979         /*
980          * Temporary, undocumented option to imitate mdoc(7) output.
981          * In the bottom right corner, use the operating system
982          * instead of the title.
983          */
984
985         if ( ! p->mdocstyle) {
986                 if (meta->hasbody) {
987                         term_vspace(p);
988                         term_vspace(p);
989                 }
990                 mandoc_asprintf(&title, "%s(%s)",
991                     meta->title, meta->msec);
992         } else if (meta->os) {
993                 title = mandoc_strdup(meta->os);
994         } else {
995                 title = mandoc_strdup("");
996         }
997         datelen = term_strlen(p, meta->date);
998
999         /* Bottom left corner: operating system. */
1000
1001         p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
1002         p->trailspace = 1;
1003         p->tcol->offset = 0;
1004         p->tcol->rmargin = p->maxrmargin > datelen ?
1005             (p->maxrmargin + term_len(p, 1) - datelen) / 2 : 0;
1006
1007         if (meta->os)
1008                 term_word(p, meta->os);
1009         term_flushln(p);
1010
1011         /* At the bottom in the middle: manual date. */
1012
1013         p->tcol->offset = p->tcol->rmargin;
1014         titlen = term_strlen(p, title);
1015         p->tcol->rmargin = p->maxrmargin > titlen ?
1016             p->maxrmargin - titlen : 0;
1017         p->flags |= TERMP_NOSPACE;
1018
1019         term_word(p, meta->date);
1020         term_flushln(p);
1021
1022         /* Bottom right corner: manual title and section. */
1023
1024         p->flags &= ~TERMP_NOBREAK;
1025         p->flags |= TERMP_NOSPACE;
1026         p->trailspace = 0;
1027         p->tcol->offset = p->tcol->rmargin;
1028         p->tcol->rmargin = p->maxrmargin;
1029
1030         term_word(p, title);
1031         term_flushln(p);
1032         free(title);
1033 }
1034
1035 static void
1036 print_man_head(struct termp *p, const struct roff_meta *meta)
1037 {
1038         const char              *volume;
1039         char                    *title;
1040         size_t                   vollen, titlen;
1041
1042         assert(meta->title);
1043         assert(meta->msec);
1044
1045         volume = NULL == meta->vol ? "" : meta->vol;
1046         vollen = term_strlen(p, volume);
1047
1048         /* Top left corner: manual title and section. */
1049
1050         mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec);
1051         titlen = term_strlen(p, title);
1052
1053         p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
1054         p->trailspace = 1;
1055         p->tcol->offset = 0;
1056         p->tcol->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ?
1057             (p->maxrmargin - vollen + term_len(p, 1)) / 2 :
1058             vollen < p->maxrmargin ? p->maxrmargin - vollen : 0;
1059
1060         term_word(p, title);
1061         term_flushln(p);
1062
1063         /* At the top in the middle: manual volume. */
1064
1065         p->flags |= TERMP_NOSPACE;
1066         p->tcol->offset = p->tcol->rmargin;
1067         p->tcol->rmargin = p->tcol->offset + vollen + titlen <
1068             p->maxrmargin ?  p->maxrmargin - titlen : p->maxrmargin;
1069
1070         term_word(p, volume);
1071         term_flushln(p);
1072
1073         /* Top right corner: title and section, again. */
1074
1075         p->flags &= ~TERMP_NOBREAK;
1076         p->trailspace = 0;
1077         if (p->tcol->rmargin + titlen <= p->maxrmargin) {
1078                 p->flags |= TERMP_NOSPACE;
1079                 p->tcol->offset = p->tcol->rmargin;
1080                 p->tcol->rmargin = p->maxrmargin;
1081                 term_word(p, title);
1082                 term_flushln(p);
1083         }
1084
1085         p->flags &= ~TERMP_NOSPACE;
1086         p->tcol->offset = 0;
1087         p->tcol->rmargin = p->maxrmargin;
1088
1089         /*
1090          * Groff prints three blank lines before the content.
1091          * Do the same, except in the temporary, undocumented
1092          * mode imitating mdoc(7) output.
1093          */
1094
1095         term_vspace(p);
1096         if ( ! p->mdocstyle) {
1097                 term_vspace(p);
1098                 term_vspace(p);
1099         }
1100         free(title);
1101 }