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