9718 update mandoc to 1.14.4
[unleashed.git] / usr / src / cmd / mandoc / man_term.c
blobb5723ccf83833d76d88d74a94c64944233b03f75
1 /* $Id: man_term.c,v 1.211 2018/06/10 15:12:35 schwarze Exp $ */
2 /*
3 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010-2015, 2017, 2018 Ingo Schwarze <schwarze@openbsd.org>
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.
18 #include "config.h"
20 #include <sys/types.h>
22 #include <assert.h>
23 #include <ctype.h>
24 #include <limits.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
29 #include "mandoc_aux.h"
30 #include "mandoc.h"
31 #include "roff.h"
32 #include "man.h"
33 #include "out.h"
34 #include "term.h"
35 #include "main.h"
37 #define MAXMARGINS 64 /* maximum number of indented scopes */
39 struct mtermp {
40 int fl;
41 #define MANT_LITERAL (1 << 0)
42 int lmargin[MAXMARGINS]; /* margins (incl. vis. page) */
43 int lmargincur; /* index of current margin */
44 int lmarginsz; /* actual number of nested margins */
45 size_t offset; /* default offset to visible page */
46 int pardist; /* vert. space before par., unit: [v] */
49 #define DECL_ARGS struct termp *p, \
50 struct mtermp *mt, \
51 struct roff_node *n, \
52 const struct roff_meta *meta
54 struct termact {
55 int (*pre)(DECL_ARGS);
56 void (*post)(DECL_ARGS);
57 int flags;
58 #define MAN_NOTEXT (1 << 0) /* Never has text children. */
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;
137 void
138 terminal_man(void *arg, const struct roff_man *man)
140 struct termp *p;
141 struct roff_node *n;
142 struct mtermp mt;
143 size_t save_defindent;
145 p = (struct termp *)arg;
146 save_defindent = p->defindent;
147 if (p->synopsisonly == 0 && p->defindent == 0)
148 p->defindent = 7;
149 p->tcol->rmargin = p->maxrmargin = p->defrmargin;
150 term_tab_set(p, NULL);
151 term_tab_set(p, "T");
152 term_tab_set(p, ".5i");
154 memset(&mt, 0, sizeof(struct mtermp));
155 mt.lmargin[mt.lmargincur] = term_len(p, p->defindent);
156 mt.offset = term_len(p, p->defindent);
157 mt.pardist = 1;
159 n = man->first->child;
160 if (p->synopsisonly) {
161 while (n != NULL) {
162 if (n->tok == MAN_SH &&
163 n->child->child->type == ROFFT_TEXT &&
164 !strcmp(n->child->child->string, "SYNOPSIS")) {
165 if (n->child->next->child != NULL)
166 print_man_nodelist(p, &mt,
167 n->child->next->child,
168 &man->meta);
169 term_newln(p);
170 break;
172 n = n->next;
174 } else {
175 term_begin(p, print_man_head, print_man_foot, &man->meta);
176 p->flags |= TERMP_NOSPACE;
177 if (n != NULL)
178 print_man_nodelist(p, &mt, n, &man->meta);
179 term_end(p);
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
190 * first, print it.
192 static void
193 print_bvspace(struct termp *p, const struct roff_node *n, int pardist)
195 int i;
197 term_newln(p);
199 if (n->body && n->body->child)
200 if (n->body->child->type == ROFFT_TBL)
201 return;
203 if (n->parent->type == ROFFT_ROOT || n->parent->tok != MAN_RS)
204 if (NULL == n->prev)
205 return;
207 for (i = 0; i < pardist; i++)
208 term_vspace(p);
212 static int
213 pre_ign(DECL_ARGS)
216 return 0;
219 static int
220 pre_I(DECL_ARGS)
223 term_fontrepl(p, TERMFONT_UNDER);
224 return 1;
227 static int
228 pre_literal(DECL_ARGS)
231 term_newln(p);
233 if (n->tok == MAN_nf || n->tok == MAN_EX)
234 mt->fl |= MANT_LITERAL;
235 else
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;
246 p->trailspace = 0;
247 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
248 p->flags |= TERMP_NOSPACE;
251 return 0;
254 static int
255 pre_PD(DECL_ARGS)
257 struct roffsu su;
259 n = n->child;
260 if (n == NULL) {
261 mt->pardist = 1;
262 return 0;
264 assert(n->type == ROFFT_TEXT);
265 if (a2roffsu(n->string, &su, SCALE_VS) != NULL)
266 mt->pardist = term_vspan(p, &su);
267 return 0;
270 static int
271 pre_alternate(DECL_ARGS)
273 enum termfont font[2];
274 struct roff_node *nn;
275 int savelit, i;
277 switch (n->tok) {
278 case MAN_RB:
279 font[0] = TERMFONT_NONE;
280 font[1] = TERMFONT_BOLD;
281 break;
282 case MAN_RI:
283 font[0] = TERMFONT_NONE;
284 font[1] = TERMFONT_UNDER;
285 break;
286 case MAN_BR:
287 font[0] = TERMFONT_BOLD;
288 font[1] = TERMFONT_NONE;
289 break;
290 case MAN_BI:
291 font[0] = TERMFONT_BOLD;
292 font[1] = TERMFONT_UNDER;
293 break;
294 case MAN_IR:
295 font[0] = TERMFONT_UNDER;
296 font[1] = TERMFONT_NONE;
297 break;
298 case MAN_IB:
299 font[0] = TERMFONT_UNDER;
300 font[1] = TERMFONT_BOLD;
301 break;
302 default:
303 abort();
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;
317 if (nn->next)
318 p->flags |= TERMP_NOSPACE;
321 return 0;
324 static int
325 pre_B(DECL_ARGS)
328 term_fontrepl(p, TERMFONT_BOLD);
329 return 1;
332 static int
333 pre_OP(DECL_ARGS)
336 term_word(p, "[");
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;
350 term_word(p, "]");
351 return 0;
354 static int
355 pre_in(DECL_ARGS)
357 struct roffsu su;
358 const char *cp;
359 size_t v;
360 int less;
362 term_newln(p);
364 if (n->child == NULL) {
365 p->tcol->offset = mt->offset;
366 return 0;
369 cp = n->child->string;
370 less = 0;
372 if ('-' == *cp)
373 less = -1;
374 else if ('+' == *cp)
375 less = 1;
376 else
377 cp--;
379 if (a2roffsu(++cp, &su, SCALE_EN) == NULL)
380 return 0;
382 v = term_hen(p, &su);
384 if (less < 0)
385 p->tcol->offset -= p->tcol->offset > v ? v : p->tcol->offset;
386 else if (less > 0)
387 p->tcol->offset += v;
388 else
389 p->tcol->offset = v;
390 if (p->tcol->offset > SHRT_MAX)
391 p->tcol->offset = term_len(p, p->defindent);
393 return 0;
396 static int
397 pre_DT(DECL_ARGS)
399 term_tab_set(p, NULL);
400 term_tab_set(p, "T");
401 term_tab_set(p, ".5i");
402 return 0;
405 static int
406 pre_HP(DECL_ARGS)
408 struct roffsu su;
409 const struct roff_node *nn;
410 int len;
412 switch (n->type) {
413 case ROFFT_BLOCK:
414 print_bvspace(p, n, mt->pardist);
415 return 1;
416 case ROFFT_BODY:
417 break;
418 default:
419 return 0;
422 if ( ! (MANT_LITERAL & mt->fl)) {
423 p->flags |= TERMP_NOBREAK | TERMP_BRIND;
424 p->trailspace = 2;
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)
433 len = -mt->offset;
434 else if (len > SHRT_MAX)
435 len = term_len(p, p->defindent);
436 mt->lmargin[mt->lmargincur] = len;
437 } else
438 len = mt->lmargin[mt->lmargincur];
440 p->tcol->offset = mt->offset;
441 p->tcol->rmargin = mt->offset + len;
442 return 1;
445 static void
446 post_HP(DECL_ARGS)
449 switch (n->type) {
450 case ROFFT_BODY:
451 term_newln(p);
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)
461 term_vspace(p);
463 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
464 p->trailspace = 0;
465 p->tcol->offset = mt->offset;
466 p->tcol->rmargin = p->maxrmargin;
467 break;
468 default:
469 break;
473 static int
474 pre_PP(DECL_ARGS)
477 switch (n->type) {
478 case ROFFT_BLOCK:
479 mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
480 print_bvspace(p, n, mt->pardist);
481 break;
482 default:
483 p->tcol->offset = mt->offset;
484 break;
487 return n->type != ROFFT_HEAD;
490 static int
491 pre_IP(DECL_ARGS)
493 struct roffsu su;
494 const struct roff_node *nn;
495 int len, savelit;
497 switch (n->type) {
498 case ROFFT_BODY:
499 p->flags |= TERMP_NOSPACE;
500 break;
501 case ROFFT_HEAD:
502 p->flags |= TERMP_NOBREAK;
503 p->trailspace = 1;
504 break;
505 case ROFFT_BLOCK:
506 print_bvspace(p, n, mt->pardist);
507 /* FALLTHROUGH */
508 default:
509 return 1;
512 /* Calculate the offset from the optional second argument. */
513 if ((nn = n->parent->head->child) != NULL &&
514 (nn = nn->next) != NULL &&
515 a2roffsu(nn->string, &su, SCALE_EN) != NULL) {
516 len = term_hen(p, &su);
517 if (len < 0 && (size_t)(-len) > mt->offset)
518 len = -mt->offset;
519 else if (len > SHRT_MAX)
520 len = term_len(p, p->defindent);
521 mt->lmargin[mt->lmargincur] = len;
522 } else
523 len = mt->lmargin[mt->lmargincur];
525 switch (n->type) {
526 case ROFFT_HEAD:
527 p->tcol->offset = mt->offset;
528 p->tcol->rmargin = mt->offset + len;
530 savelit = MANT_LITERAL & mt->fl;
531 mt->fl &= ~MANT_LITERAL;
533 if (n->child)
534 print_man_node(p, mt, n->child, meta);
536 if (savelit)
537 mt->fl |= MANT_LITERAL;
539 return 0;
540 case ROFFT_BODY:
541 p->tcol->offset = mt->offset + len;
542 p->tcol->rmargin = p->maxrmargin;
543 break;
544 default:
545 break;
548 return 1;
551 static void
552 post_IP(DECL_ARGS)
555 switch (n->type) {
556 case ROFFT_HEAD:
557 term_flushln(p);
558 p->flags &= ~TERMP_NOBREAK;
559 p->trailspace = 0;
560 p->tcol->rmargin = p->maxrmargin;
561 break;
562 case ROFFT_BODY:
563 term_newln(p);
564 p->tcol->offset = mt->offset;
565 break;
566 default:
567 break;
571 static int
572 pre_TP(DECL_ARGS)
574 struct roffsu su;
575 struct roff_node *nn;
576 int len, savelit;
578 switch (n->type) {
579 case ROFFT_HEAD:
580 p->flags |= TERMP_NOBREAK | TERMP_BRTRSP;
581 p->trailspace = 1;
582 break;
583 case ROFFT_BODY:
584 p->flags |= TERMP_NOSPACE;
585 break;
586 case ROFFT_BLOCK:
587 print_bvspace(p, n, mt->pardist);
588 /* FALLTHROUGH */
589 default:
590 return 1;
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)
600 len = -mt->offset;
601 else if (len > SHRT_MAX)
602 len = term_len(p, p->defindent);
603 mt->lmargin[mt->lmargincur] = len;
604 } else
605 len = mt->lmargin[mt->lmargincur];
607 switch (n->type) {
608 case ROFFT_HEAD:
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. */
616 nn = n->child;
617 while (NULL != nn && 0 == (NODE_LINE & nn->flags))
618 nn = nn->next;
620 while (NULL != nn) {
621 print_man_node(p, mt, nn, meta);
622 nn = nn->next;
625 if (savelit)
626 mt->fl |= MANT_LITERAL;
627 return 0;
628 case ROFFT_BODY:
629 p->tcol->offset = mt->offset + len;
630 p->tcol->rmargin = p->maxrmargin;
631 p->trailspace = 0;
632 p->flags &= ~(TERMP_NOBREAK | TERMP_BRTRSP);
633 break;
634 default:
635 break;
638 return 1;
641 static void
642 post_TP(DECL_ARGS)
645 switch (n->type) {
646 case ROFFT_HEAD:
647 term_flushln(p);
648 break;
649 case ROFFT_BODY:
650 term_newln(p);
651 p->tcol->offset = mt->offset;
652 break;
653 default:
654 break;
658 static int
659 pre_SS(DECL_ARGS)
661 int i;
663 switch (n->type) {
664 case ROFFT_BLOCK:
665 mt->fl &= ~MANT_LITERAL;
666 mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
667 mt->offset = term_len(p, p->defindent);
670 * No vertical space before the first subsection
671 * and after an empty subsection.
674 do {
675 n = n->prev;
676 } while (n != NULL && n->tok >= MAN_TH &&
677 termacts[n->tok].flags & MAN_NOTEXT);
678 if (n == NULL || n->type == ROFFT_COMMENT ||
679 (n->tok == MAN_SS && n->body->child == NULL))
680 break;
682 for (i = 0; i < mt->pardist; i++)
683 term_vspace(p);
684 break;
685 case ROFFT_HEAD:
686 term_fontrepl(p, TERMFONT_BOLD);
687 p->tcol->offset = term_len(p, 3);
688 p->tcol->rmargin = mt->offset;
689 p->trailspace = mt->offset;
690 p->flags |= TERMP_NOBREAK | TERMP_BRIND;
691 break;
692 case ROFFT_BODY:
693 p->tcol->offset = mt->offset;
694 p->tcol->rmargin = p->maxrmargin;
695 p->trailspace = 0;
696 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
697 break;
698 default:
699 break;
702 return 1;
705 static void
706 post_SS(DECL_ARGS)
709 switch (n->type) {
710 case ROFFT_HEAD:
711 term_newln(p);
712 break;
713 case ROFFT_BODY:
714 term_newln(p);
715 break;
716 default:
717 break;
721 static int
722 pre_SH(DECL_ARGS)
724 int i;
726 switch (n->type) {
727 case ROFFT_BLOCK:
728 mt->fl &= ~MANT_LITERAL;
729 mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
730 mt->offset = term_len(p, p->defindent);
733 * No vertical space before the first section
734 * and after an empty section.
737 do {
738 n = n->prev;
739 } while (n != NULL && n->tok >= MAN_TH &&
740 termacts[n->tok].flags & MAN_NOTEXT);
741 if (n == NULL || n->type == ROFFT_COMMENT ||
742 (n->tok == MAN_SH && n->body->child == NULL))
743 break;
745 for (i = 0; i < mt->pardist; i++)
746 term_vspace(p);
747 break;
748 case ROFFT_HEAD:
749 term_fontrepl(p, TERMFONT_BOLD);
750 p->tcol->offset = 0;
751 p->tcol->rmargin = mt->offset;
752 p->trailspace = mt->offset;
753 p->flags |= TERMP_NOBREAK | TERMP_BRIND;
754 break;
755 case ROFFT_BODY:
756 p->tcol->offset = mt->offset;
757 p->tcol->rmargin = p->maxrmargin;
758 p->trailspace = 0;
759 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
760 break;
761 default:
762 break;
765 return 1;
768 static void
769 post_SH(DECL_ARGS)
772 switch (n->type) {
773 case ROFFT_HEAD:
774 term_newln(p);
775 break;
776 case ROFFT_BODY:
777 term_newln(p);
778 break;
779 default:
780 break;
784 static int
785 pre_RS(DECL_ARGS)
787 struct roffsu su;
789 switch (n->type) {
790 case ROFFT_BLOCK:
791 term_newln(p);
792 return 1;
793 case ROFFT_HEAD:
794 return 0;
795 default:
796 break;
799 n = n->parent->head;
800 n->aux = SHRT_MAX + 1;
801 if (n->child == NULL)
802 n->aux = mt->lmargin[mt->lmargincur];
803 else if (a2roffsu(n->child->string, &su, SCALE_EN) != NULL)
804 n->aux = term_hen(p, &su);
805 if (n->aux < 0 && (size_t)(-n->aux) > mt->offset)
806 n->aux = -mt->offset;
807 else if (n->aux > SHRT_MAX)
808 n->aux = term_len(p, p->defindent);
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);
818 return 1;
821 static void
822 post_RS(DECL_ARGS)
825 switch (n->type) {
826 case ROFFT_BLOCK:
827 return;
828 case ROFFT_HEAD:
829 return;
830 default:
831 term_newln(p);
832 break;
835 mt->offset -= n->parent->head->aux;
836 p->tcol->offset = mt->offset;
838 if (--mt->lmarginsz < MAXMARGINS)
839 mt->lmargincur = mt->lmarginsz;
842 static int
843 pre_UR(DECL_ARGS)
846 return n->type != ROFFT_HEAD;
849 static void
850 post_UR(DECL_ARGS)
853 if (n->type != ROFFT_BLOCK)
854 return;
856 term_word(p, "<");
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;
863 term_word(p, ">");
866 static void
867 print_man_node(DECL_ARGS)
869 int c;
871 switch (n->type) {
872 case ROFFT_TEXT:
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)
880 term_newln(p);
881 else
882 term_vspace(p);
883 return;
884 } else if (*n->string == ' ' && n->flags & NODE_LINE &&
885 (p->flags & TERMP_NONEWLINE) == 0)
886 term_newln(p);
888 term_word(p, n->string);
889 goto out;
890 case ROFFT_COMMENT:
891 return;
892 case ROFFT_EQN:
893 if ( ! (n->flags & NODE_LINE))
894 p->flags |= TERMP_NOSPACE;
895 term_eqn(p, n->eqn);
896 if (n->next != NULL && ! (n->next->flags & NODE_LINE))
897 p->flags |= TERMP_NOSPACE;
898 return;
899 case ROFFT_TBL:
900 if (p->tbl.cols == NULL)
901 term_vspace(p);
902 term_tbl(p, n->span);
903 return;
904 default:
905 break;
908 if (n->tok < ROFF_MAX) {
909 roff_term_pre(p, n);
910 return;
913 assert(n->tok >= MAN_TH && n->tok <= MAN_MAX);
914 if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
915 term_fontrepl(p, TERMFONT_NONE);
917 c = 1;
918 if (termacts[n->tok].pre)
919 c = (*termacts[n->tok].pre)(p, mt, n, meta);
921 if (c && n->child)
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);
929 out:
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')
942 term_flushln(p);
943 else
944 term_newln(p);
945 p->flags &= ~TERMP_BRNEVER;
946 if (p->tcol->rmargin < p->maxrmargin &&
947 n->parent->tok == MAN_HP) {
948 p->tcol->offset = p->tcol->rmargin;
949 p->tcol->rmargin = p->maxrmargin;
952 if (NODE_EOS & n->flags)
953 p->flags |= TERMP_SENTENCE;
957 static void
958 print_man_nodelist(DECL_ARGS)
961 while (n != NULL) {
962 print_man_node(p, mt, n, meta);
963 n = n->next;
967 static void
968 print_man_foot(struct termp *p, const struct roff_meta *meta)
970 char *title;
971 size_t datelen, titlen;
973 assert(meta->title);
974 assert(meta->msec);
975 assert(meta->date);
977 term_fontrepl(p, TERMFONT_NONE);
979 if (meta->hasbody)
980 term_vspace(p);
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) {
989 if (meta->hasbody) {
990 term_vspace(p);
991 term_vspace(p);
993 mandoc_asprintf(&title, "%s(%s)",
994 meta->title, meta->msec);
995 } else if (meta->os) {
996 title = mandoc_strdup(meta->os);
997 } else {
998 title = mandoc_strdup("");
1000 datelen = term_strlen(p, meta->date);
1002 /* Bottom left corner: operating system. */
1004 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
1005 p->trailspace = 1;
1006 p->tcol->offset = 0;
1007 p->tcol->rmargin = p->maxrmargin > datelen ?
1008 (p->maxrmargin + term_len(p, 1) - datelen) / 2 : 0;
1010 if (meta->os)
1011 term_word(p, meta->os);
1012 term_flushln(p);
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);
1023 term_flushln(p);
1025 /* Bottom right corner: manual title and section. */
1027 p->flags &= ~TERMP_NOBREAK;
1028 p->flags |= TERMP_NOSPACE;
1029 p->trailspace = 0;
1030 p->tcol->offset = p->tcol->rmargin;
1031 p->tcol->rmargin = p->maxrmargin;
1033 term_word(p, title);
1034 term_flushln(p);
1037 * Reset the terminal state for more output after the footer:
1038 * Some output modes, in particular PostScript and PDF, print
1039 * the header and the footer into a buffer such that it can be
1040 * reused for multiple output pages, then go on to format the
1041 * main text.
1044 p->tcol->offset = 0;
1045 p->flags = 0;
1047 free(title);
1050 static void
1051 print_man_head(struct termp *p, const struct roff_meta *meta)
1053 const char *volume;
1054 char *title;
1055 size_t vollen, titlen;
1057 assert(meta->title);
1058 assert(meta->msec);
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;
1069 p->trailspace = 1;
1070 p->tcol->offset = 0;
1071 p->tcol->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ?
1072 (p->maxrmargin - vollen + term_len(p, 1)) / 2 :
1073 vollen < p->maxrmargin ? p->maxrmargin - vollen : 0;
1075 term_word(p, title);
1076 term_flushln(p);
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);
1086 term_flushln(p);
1088 /* Top right corner: title and section, again. */
1090 p->flags &= ~TERMP_NOBREAK;
1091 p->trailspace = 0;
1092 if (p->tcol->rmargin + titlen <= p->maxrmargin) {
1093 p->flags |= TERMP_NOSPACE;
1094 p->tcol->offset = p->tcol->rmargin;
1095 p->tcol->rmargin = p->maxrmargin;
1096 term_word(p, title);
1097 term_flushln(p);
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.
1110 term_vspace(p);
1111 if ( ! p->mdocstyle) {
1112 term_vspace(p);
1113 term_vspace(p);
1115 free(title);