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