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