1 /* $Id: man_term.c,v 1.211 2018/06/10 15:12:35 schwarze Exp $ */
3 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010-2015, 2017, 2018 Ingo Schwarze <schwarze@openbsd.org>
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.
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.
20 #include <sys/types.h>
29 #include "mandoc_aux.h"
37 #define MAXMARGINS 64 /* maximum number of indented scopes */
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] */
49 #define DECL_ARGS struct termp *p, \
51 struct roff_node *n, \
52 const struct roff_meta *meta
55 int (*pre)(DECL_ARGS);
56 void (*post)(DECL_ARGS);
58 #define MAN_NOTEXT (1 << 0) /* Never has text children. */
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);
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);
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);
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 */
134 static const struct termact *termacts = __termacts - MAN_TH;
138 terminal_man(void *arg, const struct roff_man *man)
143 size_t save_defindent;
145 p = (struct termp *)arg;
146 save_defindent = p->defindent;
147 if (p->synopsisonly == 0 && p->defindent == 0)
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");
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);
159 n = man->first->child;
160 if (p->synopsisonly) {
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,
175 term_begin(p, print_man_head, print_man_foot, &man->meta);
176 p->flags |= TERMP_NOSPACE;
178 print_man_nodelist(p, &mt, n, &man->meta);
181 p->defindent = save_defindent;
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
193 print_bvspace(struct termp *p, const struct roff_node *n, int pardist)
199 if (n->body && n->body->child)
200 if (n->body->child->type == ROFFT_TBL)
203 if (n->parent->type == ROFFT_ROOT || n->parent->tok != MAN_RS)
207 for (i = 0; i < pardist; i++)
223 term_fontrepl(p, TERMFONT_UNDER);
228 pre_literal(DECL_ARGS)
233 if (n->tok == MAN_nf || n->tok == MAN_EX)
234 mt->fl |= MANT_LITERAL;
236 mt->fl &= ~MANT_LITERAL;
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.
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;
247 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
248 p->flags |= TERMP_NOSPACE;
264 assert(n->type == ROFFT_TEXT);
265 if (a2roffsu(n->string, &su, SCALE_VS) != NULL)
266 mt->pardist = term_vspan(p, &su);
271 pre_alternate(DECL_ARGS)
273 enum termfont font[2];
274 struct roff_node *nn;
279 font[0] = TERMFONT_NONE;
280 font[1] = TERMFONT_BOLD;
283 font[0] = TERMFONT_NONE;
284 font[1] = TERMFONT_UNDER;
287 font[0] = TERMFONT_BOLD;
288 font[1] = TERMFONT_NONE;
291 font[0] = TERMFONT_BOLD;
292 font[1] = TERMFONT_UNDER;
295 font[0] = TERMFONT_UNDER;
296 font[1] = TERMFONT_NONE;
299 font[0] = TERMFONT_UNDER;
300 font[1] = TERMFONT_BOLD;
306 savelit = MANT_LITERAL & mt->fl;
307 mt->fl &= ~MANT_LITERAL;
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;
318 p->flags |= TERMP_NOSPACE;
328 term_fontrepl(p, TERMFONT_BOLD);
337 p->flags |= TERMP_NOSPACE;
339 if (NULL != (n = n->child)) {
340 term_fontrepl(p, TERMFONT_BOLD);
341 term_word(p, n->string);
343 if (NULL != n && NULL != n->next) {
344 term_fontrepl(p, TERMFONT_UNDER);
345 term_word(p, n->next->string);
348 term_fontrepl(p, TERMFONT_NONE);
349 p->flags |= TERMP_NOSPACE;
364 if (n->child == NULL) {
365 p->tcol->offset = mt->offset;
369 cp = n->child->string;
379 if (a2roffsu(++cp, &su, SCALE_EN) == NULL)
382 v = term_hen(p, &su);
385 p->tcol->offset -= p->tcol->offset > v ? v : p->tcol->offset;
387 p->tcol->offset += v;
390 if (p->tcol->offset > SHRT_MAX)
391 p->tcol->offset = term_len(p, p->defindent);
399 term_tab_set(p, NULL);
400 term_tab_set(p, "T");
401 term_tab_set(p, ".5i");
409 const struct roff_node *nn;
414 print_bvspace(p, n, mt->pardist);
422 if ( ! (MANT_LITERAL & mt->fl)) {
423 p->flags |= TERMP_NOBREAK | TERMP_BRIND;
427 /* Calculate offset. */
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)
434 else if (len > SHRT_MAX)
435 len = term_len(p, p->defindent);
436 mt->lmargin[mt->lmargincur] = len;
438 len = mt->lmargin[mt->lmargincur];
440 p->tcol->offset = mt->offset;
441 p->tcol->rmargin = mt->offset + len;
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.
460 if (n->child == NULL)
463 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
465 p->tcol->offset = mt->offset;
466 p->tcol->rmargin = p->maxrmargin;
479 mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
480 print_bvspace(p, n, mt->pardist);
483 p->tcol->offset = mt->offset;
487 return n->type != ROFFT_HEAD;
494 const struct roff_node *nn;
499 p->flags |= TERMP_NOSPACE;
502 p->flags |= TERMP_NOBREAK;
506 print_bvspace(p, n, mt->pardist);
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)
519 else if (len > SHRT_MAX)
520 len = term_len(p, p->defindent);
521 mt->lmargin[mt->lmargincur] = len;
523 len = mt->lmargin[mt->lmargincur];
527 p->tcol->offset = mt->offset;
528 p->tcol->rmargin = mt->offset + len;
530 savelit = MANT_LITERAL & mt->fl;
531 mt->fl &= ~MANT_LITERAL;
534 print_man_node(p, mt, n->child, meta);
537 mt->fl |= MANT_LITERAL;
541 p->tcol->offset = mt->offset + len;
542 p->tcol->rmargin = p->maxrmargin;
558 p->flags &= ~TERMP_NOBREAK;
560 p->tcol->rmargin = p->maxrmargin;
564 p->tcol->offset = mt->offset;
575 struct roff_node *nn;
580 p->flags |= TERMP_NOBREAK | TERMP_BRTRSP;
584 p->flags |= TERMP_NOSPACE;
587 print_bvspace(p, n, mt->pardist);
593 /* Calculate offset. */
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)
601 else if (len > SHRT_MAX)
602 len = term_len(p, p->defindent);
603 mt->lmargin[mt->lmargincur] = len;
605 len = mt->lmargin[mt->lmargincur];
609 p->tcol->offset = mt->offset;
610 p->tcol->rmargin = mt->offset + len;
612 savelit = MANT_LITERAL & mt->fl;
613 mt->fl &= ~MANT_LITERAL;
615 /* Don't print same-line elements. */
617 while (NULL != nn && 0 == (NODE_LINE & nn->flags))
621 print_man_node(p, mt, nn, meta);
626 mt->fl |= MANT_LITERAL;
629 p->tcol->offset = mt->offset + len;
630 p->tcol->rmargin = p->maxrmargin;
632 p->flags &= ~(TERMP_NOBREAK | TERMP_BRTRSP);
651 p->tcol->offset = mt->offset;
665 mt->fl &= ~MANT_LITERAL;
666 mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
667 mt->offset = term_len(p, p->defindent);
670 * No vertical space before the first subsection
671 * and after an empty subsection.
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))
682 for (i = 0; i < mt->pardist; i++)
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;
693 p->tcol->offset = mt->offset;
694 p->tcol->rmargin = p->maxrmargin;
696 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
728 mt->fl &= ~MANT_LITERAL;
729 mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
730 mt->offset = term_len(p, p->defindent);
733 * No vertical space before the first section
734 * and after an empty section.
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))
745 for (i = 0; i < mt->pardist; i++)
749 term_fontrepl(p, TERMFONT_BOLD);
751 p->tcol->rmargin = mt->offset;
752 p->trailspace = mt->offset;
753 p->flags |= TERMP_NOBREAK | TERMP_BRIND;
756 p->tcol->offset = mt->offset;
757 p->tcol->rmargin = p->maxrmargin;
759 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
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);
810 mt->offset += n->aux;
811 p->tcol->offset = mt->offset;
812 p->tcol->rmargin = p->maxrmargin;
814 if (++mt->lmarginsz < MAXMARGINS)
815 mt->lmargincur = mt->lmarginsz;
817 mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
835 mt->offset -= n->parent->head->aux;
836 p->tcol->offset = mt->offset;
838 if (--mt->lmarginsz < MAXMARGINS)
839 mt->lmargincur = mt->lmarginsz;
846 return n->type != ROFFT_HEAD;
853 if (n->type != ROFFT_BLOCK)
857 p->flags |= TERMP_NOSPACE;
859 if (NULL != n->child->child)
860 print_man_node(p, mt, n->child->child, meta);
862 p->flags |= TERMP_NOSPACE;
867 print_man_node(DECL_ARGS)
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.
878 if (*n->string == '\0') {
879 if (p->flags & TERMP_NONEWLINE)
884 } else if (*n->string == ' ' && n->flags & NODE_LINE &&
885 (p->flags & TERMP_NONEWLINE) == 0)
888 term_word(p, n->string);
893 if ( ! (n->flags & NODE_LINE))
894 p->flags |= TERMP_NOSPACE;
896 if (n->next != NULL && ! (n->next->flags & NODE_LINE))
897 p->flags |= TERMP_NOSPACE;
900 if (p->tbl.cols == NULL)
902 term_tbl(p, n->span);
908 if (n->tok < ROFF_MAX) {
913 assert(n->tok >= MAN_TH && n->tok <= MAN_MAX);
914 if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
915 term_fontrepl(p, TERMFONT_NONE);
918 if (termacts[n->tok].pre)
919 c = (*termacts[n->tok].pre)(p, mt, n, meta);
922 print_man_nodelist(p, mt, n->child, meta);
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);
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.
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')
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;
952 if (NODE_EOS & n->flags)
953 p->flags |= TERMP_SENTENCE;
958 print_man_nodelist(DECL_ARGS)
962 print_man_node(p, mt, n, meta);
968 print_man_foot(struct termp *p, const struct roff_meta *meta)
971 size_t datelen, titlen;
977 term_fontrepl(p, TERMFONT_NONE);
983 * Temporary, undocumented option to imitate mdoc(7) output.
984 * In the bottom right corner, use the operating system
985 * instead of the title.
988 if ( ! p->mdocstyle) {
993 mandoc_asprintf(&title, "%s(%s)",
994 meta->title, meta->msec);
995 } else if (meta->os) {
996 title = mandoc_strdup(meta->os);
998 title = mandoc_strdup("");
1000 datelen = term_strlen(p, meta->date);
1002 /* Bottom left corner: operating system. */
1004 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
1006 p->tcol->offset = 0;
1007 p->tcol->rmargin = p->maxrmargin > datelen ?
1008 (p->maxrmargin + term_len(p, 1) - datelen) / 2 : 0;
1011 term_word(p, meta->os);
1014 /* At the bottom in the middle: manual date. */
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;
1022 term_word(p, meta->date);
1025 /* Bottom right corner: manual title and section. */
1027 p->flags &= ~TERMP_NOBREAK;
1028 p->flags |= TERMP_NOSPACE;
1030 p->tcol->offset = p->tcol->rmargin;
1031 p->tcol->rmargin = p->maxrmargin;
1033 term_word(p, title);
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
1044 p->tcol->offset = 0;
1051 print_man_head(struct termp *p, const struct roff_meta *meta)
1055 size_t vollen, titlen;
1057 assert(meta->title);
1060 volume = NULL == meta->vol ? "" : meta->vol;
1061 vollen = term_strlen(p, volume);
1063 /* Top left corner: manual title and section. */
1065 mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec);
1066 titlen = term_strlen(p, title);
1068 p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
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;
1075 term_word(p, title);
1078 /* At the top in the middle: manual volume. */
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;
1085 term_word(p, volume);
1088 /* Top right corner: title and section, again. */
1090 p->flags &= ~TERMP_NOBREAK;
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);
1100 p->flags &= ~TERMP_NOSPACE;
1101 p->tcol->offset = 0;
1102 p->tcol->rmargin = p->maxrmargin;
1105 * Groff prints three blank lines before the content.
1106 * Do the same, except in the temporary, undocumented
1107 * mode imitating mdoc(7) output.
1111 if ( ! p->mdocstyle) {