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