]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/mandoc/man_term.c
Merge llvm, clang, compiler-rt, libc++, lld, and lldb release_80 branch
[FreeBSD/FreeBSD.git] / contrib / mandoc / man_term.c
1 /*      $Id: man_term.c,v 1.211 2018/06/10 15:12:35 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2010-2015, 2017, 2018 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->type == ROFFT_COMMENT ||
679                     (n->tok == MAN_SS && n->body->child == NULL))
680                         break;
681
682                 for (i = 0; i < mt->pardist; i++)
683                         term_vspace(p);
684                 break;
685         case ROFFT_HEAD:
686                 term_fontrepl(p, TERMFONT_BOLD);
687                 p->tcol->offset = term_len(p, 3);
688                 p->tcol->rmargin = mt->offset;
689                 p->trailspace = mt->offset;
690                 p->flags |= TERMP_NOBREAK | TERMP_BRIND;
691                 break;
692         case ROFFT_BODY:
693                 p->tcol->offset = mt->offset;
694                 p->tcol->rmargin = p->maxrmargin;
695                 p->trailspace = 0;
696                 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
697                 break;
698         default:
699                 break;
700         }
701
702         return 1;
703 }
704
705 static void
706 post_SS(DECL_ARGS)
707 {
708
709         switch (n->type) {
710         case ROFFT_HEAD:
711                 term_newln(p);
712                 break;
713         case ROFFT_BODY:
714                 term_newln(p);
715                 break;
716         default:
717                 break;
718         }
719 }
720
721 static int
722 pre_SH(DECL_ARGS)
723 {
724         int      i;
725
726         switch (n->type) {
727         case ROFFT_BLOCK:
728                 mt->fl &= ~MANT_LITERAL;
729                 mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
730                 mt->offset = term_len(p, p->defindent);
731
732                 /*
733                  * No vertical space before the first section
734                  * and after an empty section.
735                  */
736
737                 do {
738                         n = n->prev;
739                 } while (n != NULL && n->tok >= MAN_TH &&
740                     termacts[n->tok].flags & MAN_NOTEXT);
741                 if (n == NULL || n->type == ROFFT_COMMENT ||
742                     (n->tok == MAN_SH && n->body->child == NULL))
743                         break;
744
745                 for (i = 0; i < mt->pardist; i++)
746                         term_vspace(p);
747                 break;
748         case ROFFT_HEAD:
749                 term_fontrepl(p, TERMFONT_BOLD);
750                 p->tcol->offset = 0;
751                 p->tcol->rmargin = mt->offset;
752                 p->trailspace = mt->offset;
753                 p->flags |= TERMP_NOBREAK | TERMP_BRIND;
754                 break;
755         case ROFFT_BODY:
756                 p->tcol->offset = mt->offset;
757                 p->tcol->rmargin = p->maxrmargin;
758                 p->trailspace = 0;
759                 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
760                 break;
761         default:
762                 break;
763         }
764
765         return 1;
766 }
767
768 static void
769 post_SH(DECL_ARGS)
770 {
771
772         switch (n->type) {
773         case ROFFT_HEAD:
774                 term_newln(p);
775                 break;
776         case ROFFT_BODY:
777                 term_newln(p);
778                 break;
779         default:
780                 break;
781         }
782 }
783
784 static int
785 pre_RS(DECL_ARGS)
786 {
787         struct roffsu    su;
788
789         switch (n->type) {
790         case ROFFT_BLOCK:
791                 term_newln(p);
792                 return 1;
793         case ROFFT_HEAD:
794                 return 0;
795         default:
796                 break;
797         }
798
799         n = n->parent->head;
800         n->aux = SHRT_MAX + 1;
801         if (n->child == NULL)
802                 n->aux = mt->lmargin[mt->lmargincur];
803         else if (a2roffsu(n->child->string, &su, SCALE_EN) != NULL)
804                 n->aux = term_hen(p, &su);
805         if (n->aux < 0 && (size_t)(-n->aux) > mt->offset)
806                 n->aux = -mt->offset;
807         else if (n->aux > SHRT_MAX)
808                 n->aux = term_len(p, p->defindent);
809
810         mt->offset += n->aux;
811         p->tcol->offset = mt->offset;
812         p->tcol->rmargin = p->maxrmargin;
813
814         if (++mt->lmarginsz < MAXMARGINS)
815                 mt->lmargincur = mt->lmarginsz;
816
817         mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
818         return 1;
819 }
820
821 static void
822 post_RS(DECL_ARGS)
823 {
824
825         switch (n->type) {
826         case ROFFT_BLOCK:
827                 return;
828         case ROFFT_HEAD:
829                 return;
830         default:
831                 term_newln(p);
832                 break;
833         }
834
835         mt->offset -= n->parent->head->aux;
836         p->tcol->offset = mt->offset;
837
838         if (--mt->lmarginsz < MAXMARGINS)
839                 mt->lmargincur = mt->lmarginsz;
840 }
841
842 static int
843 pre_UR(DECL_ARGS)
844 {
845
846         return n->type != ROFFT_HEAD;
847 }
848
849 static void
850 post_UR(DECL_ARGS)
851 {
852
853         if (n->type != ROFFT_BLOCK)
854                 return;
855
856         term_word(p, "<");
857         p->flags |= TERMP_NOSPACE;
858
859         if (NULL != n->child->child)
860                 print_man_node(p, mt, n->child->child, meta);
861
862         p->flags |= TERMP_NOSPACE;
863         term_word(p, ">");
864 }
865
866 static void
867 print_man_node(DECL_ARGS)
868 {
869         int              c;
870
871         switch (n->type) {
872         case ROFFT_TEXT:
873                 /*
874                  * If we have a blank line, output a vertical space.
875                  * If we have a space as the first character, break
876                  * before printing the line's data.
877                  */
878                 if (*n->string == '\0') {
879                         if (p->flags & TERMP_NONEWLINE)
880                                 term_newln(p);
881                         else
882                                 term_vspace(p);
883                         return;
884                 } else if (*n->string == ' ' && n->flags & NODE_LINE &&
885                     (p->flags & TERMP_NONEWLINE) == 0)
886                         term_newln(p);
887
888                 term_word(p, n->string);
889                 goto out;
890         case ROFFT_COMMENT:
891                 return;
892         case ROFFT_EQN:
893                 if ( ! (n->flags & NODE_LINE))
894                         p->flags |= TERMP_NOSPACE;
895                 term_eqn(p, n->eqn);
896                 if (n->next != NULL && ! (n->next->flags & NODE_LINE))
897                         p->flags |= TERMP_NOSPACE;
898                 return;
899         case ROFFT_TBL:
900                 if (p->tbl.cols == NULL)
901                         term_vspace(p);
902                 term_tbl(p, n->span);
903                 return;
904         default:
905                 break;
906         }
907
908         if (n->tok < ROFF_MAX) {
909                 roff_term_pre(p, n);
910                 return;
911         }
912
913         assert(n->tok >= MAN_TH && n->tok <= MAN_MAX);
914         if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
915                 term_fontrepl(p, TERMFONT_NONE);
916
917         c = 1;
918         if (termacts[n->tok].pre)
919                 c = (*termacts[n->tok].pre)(p, mt, n, meta);
920
921         if (c && n->child)
922                 print_man_nodelist(p, mt, n->child, meta);
923
924         if (termacts[n->tok].post)
925                 (*termacts[n->tok].post)(p, mt, n, meta);
926         if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
927                 term_fontrepl(p, TERMFONT_NONE);
928
929 out:
930         /*
931          * If we're in a literal context, make sure that words
932          * together on the same line stay together.  This is a
933          * POST-printing call, so we check the NEXT word.  Since
934          * -man doesn't have nested macros, we don't need to be
935          * more specific than this.
936          */
937         if (mt->fl & MANT_LITERAL &&
938             ! (p->flags & (TERMP_NOBREAK | TERMP_NONEWLINE)) &&
939             (n->next == NULL || n->next->flags & NODE_LINE)) {
940                 p->flags |= TERMP_BRNEVER | TERMP_NOSPACE;
941                 if (n->string != NULL && *n->string != '\0')
942                         term_flushln(p);
943                 else
944                         term_newln(p);
945                 p->flags &= ~TERMP_BRNEVER;
946                 if (p->tcol->rmargin < p->maxrmargin &&
947                     n->parent->tok == MAN_HP) {
948                         p->tcol->offset = p->tcol->rmargin;
949                         p->tcol->rmargin = p->maxrmargin;
950                 }
951         }
952         if (NODE_EOS & n->flags)
953                 p->flags |= TERMP_SENTENCE;
954 }
955
956
957 static void
958 print_man_nodelist(DECL_ARGS)
959 {
960
961         while (n != NULL) {
962                 print_man_node(p, mt, n, meta);
963                 n = n->next;
964         }
965 }
966
967 static void
968 print_man_foot(struct termp *p, const struct roff_meta *meta)
969 {
970         char                    *title;
971         size_t                   datelen, titlen;
972
973         assert(meta->title);
974         assert(meta->msec);
975         assert(meta->date);
976
977         term_fontrepl(p, TERMFONT_NONE);
978
979         if (meta->hasbody)
980                 term_vspace(p);
981
982         /*
983          * Temporary, undocumented option to imitate mdoc(7) output.
984          * In the bottom right corner, use the operating system
985          * instead of the title.
986          */
987
988         if ( ! p->mdocstyle) {
989                 if (meta->hasbody) {
990                         term_vspace(p);
991                         term_vspace(p);
992                 }
993                 mandoc_asprintf(&title, "%s(%s)",
994                     meta->title, meta->msec);
995         } else if (meta->os) {
996                 title = mandoc_strdup(meta->os);
997         } else {
998                 title = mandoc_strdup("");
999         }
1000         datelen = term_strlen(p, meta->date);
1001
1002         /* Bottom left corner: operating system. */
1003
1004         p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
1005         p->trailspace = 1;
1006         p->tcol->offset = 0;
1007         p->tcol->rmargin = p->maxrmargin > datelen ?
1008             (p->maxrmargin + term_len(p, 1) - datelen) / 2 : 0;
1009
1010         if (meta->os)
1011                 term_word(p, meta->os);
1012         term_flushln(p);
1013
1014         /* At the bottom in the middle: manual date. */
1015
1016         p->tcol->offset = p->tcol->rmargin;
1017         titlen = term_strlen(p, title);
1018         p->tcol->rmargin = p->maxrmargin > titlen ?
1019             p->maxrmargin - titlen : 0;
1020         p->flags |= TERMP_NOSPACE;
1021
1022         term_word(p, meta->date);
1023         term_flushln(p);
1024
1025         /* Bottom right corner: manual title and section. */
1026
1027         p->flags &= ~TERMP_NOBREAK;
1028         p->flags |= TERMP_NOSPACE;
1029         p->trailspace = 0;
1030         p->tcol->offset = p->tcol->rmargin;
1031         p->tcol->rmargin = p->maxrmargin;
1032
1033         term_word(p, title);
1034         term_flushln(p);
1035
1036         /*
1037          * Reset the terminal state for more output after the footer:
1038          * Some output modes, in particular PostScript and PDF, print
1039          * the header and the footer into a buffer such that it can be
1040          * reused for multiple output pages, then go on to format the
1041          * main text.
1042          */
1043
1044         p->tcol->offset = 0;
1045         p->flags = 0;
1046
1047         free(title);
1048 }
1049
1050 static void
1051 print_man_head(struct termp *p, const struct roff_meta *meta)
1052 {
1053         const char              *volume;
1054         char                    *title;
1055         size_t                   vollen, titlen;
1056
1057         assert(meta->title);
1058         assert(meta->msec);
1059
1060         volume = NULL == meta->vol ? "" : meta->vol;
1061         vollen = term_strlen(p, volume);
1062
1063         /* Top left corner: manual title and section. */
1064
1065         mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec);
1066         titlen = term_strlen(p, title);
1067
1068         p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
1069         p->trailspace = 1;
1070         p->tcol->offset = 0;
1071         p->tcol->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ?
1072             (p->maxrmargin - vollen + term_len(p, 1)) / 2 :
1073             vollen < p->maxrmargin ? p->maxrmargin - vollen : 0;
1074
1075         term_word(p, title);
1076         term_flushln(p);
1077
1078         /* At the top in the middle: manual volume. */
1079
1080         p->flags |= TERMP_NOSPACE;
1081         p->tcol->offset = p->tcol->rmargin;
1082         p->tcol->rmargin = p->tcol->offset + vollen + titlen <
1083             p->maxrmargin ?  p->maxrmargin - titlen : p->maxrmargin;
1084
1085         term_word(p, volume);
1086         term_flushln(p);
1087
1088         /* Top right corner: title and section, again. */
1089
1090         p->flags &= ~TERMP_NOBREAK;
1091         p->trailspace = 0;
1092         if (p->tcol->rmargin + titlen <= p->maxrmargin) {
1093                 p->flags |= TERMP_NOSPACE;
1094                 p->tcol->offset = p->tcol->rmargin;
1095                 p->tcol->rmargin = p->maxrmargin;
1096                 term_word(p, title);
1097                 term_flushln(p);
1098         }
1099
1100         p->flags &= ~TERMP_NOSPACE;
1101         p->tcol->offset = 0;
1102         p->tcol->rmargin = p->maxrmargin;
1103
1104         /*
1105          * Groff prints three blank lines before the content.
1106          * Do the same, except in the temporary, undocumented
1107          * mode imitating mdoc(7) output.
1108          */
1109
1110         term_vspace(p);
1111         if ( ! p->mdocstyle) {
1112                 term_vspace(p);
1113                 term_vspace(p);
1114         }
1115         free(title);
1116 }