mandoc: update to 1.14.2
[unleashed.git] / bin / mandoc / man_term.c
blobfcdb45df8ddb9acacb1ef68c992d199b9f7a781a
1 /* $Id: man_term.c,v 1.208 2017/06/25 11:42:02 schwarze Exp $ */
2 /*
3 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010-2015, 2017 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 != TOKEN_NONE &&
677 termacts[n->tok].flags & MAN_NOTEXT);
678 if (n == NULL || (n->tok == MAN_SS && n->body->child == NULL))
679 break;
681 for (i = 0; i < mt->pardist; i++)
682 term_vspace(p);
683 break;
684 case ROFFT_HEAD:
685 term_fontrepl(p, TERMFONT_BOLD);
686 p->tcol->offset = term_len(p, 3);
687 p->tcol->rmargin = mt->offset;
688 p->trailspace = mt->offset;
689 p->flags |= TERMP_NOBREAK | TERMP_BRIND;
690 break;
691 case ROFFT_BODY:
692 p->tcol->offset = mt->offset;
693 p->tcol->rmargin = p->maxrmargin;
694 p->trailspace = 0;
695 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
696 break;
697 default:
698 break;
701 return 1;
704 static void
705 post_SS(DECL_ARGS)
708 switch (n->type) {
709 case ROFFT_HEAD:
710 term_newln(p);
711 break;
712 case ROFFT_BODY:
713 term_newln(p);
714 break;
715 default:
716 break;
720 static int
721 pre_SH(DECL_ARGS)
723 int i;
725 switch (n->type) {
726 case ROFFT_BLOCK:
727 mt->fl &= ~MANT_LITERAL;
728 mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
729 mt->offset = term_len(p, p->defindent);
732 * No vertical space before the first section
733 * and after an empty section.
736 do {
737 n = n->prev;
738 } while (n != NULL && n->tok != TOKEN_NONE &&
739 termacts[n->tok].flags & MAN_NOTEXT);
740 if (n == NULL || (n->tok == MAN_SH && n->body->child == NULL))
741 break;
743 for (i = 0; i < mt->pardist; i++)
744 term_vspace(p);
745 break;
746 case ROFFT_HEAD:
747 term_fontrepl(p, TERMFONT_BOLD);
748 p->tcol->offset = 0;
749 p->tcol->rmargin = mt->offset;
750 p->trailspace = mt->offset;
751 p->flags |= TERMP_NOBREAK | TERMP_BRIND;
752 break;
753 case ROFFT_BODY:
754 p->tcol->offset = mt->offset;
755 p->tcol->rmargin = p->maxrmargin;
756 p->trailspace = 0;
757 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
758 break;
759 default:
760 break;
763 return 1;
766 static void
767 post_SH(DECL_ARGS)
770 switch (n->type) {
771 case ROFFT_HEAD:
772 term_newln(p);
773 break;
774 case ROFFT_BODY:
775 term_newln(p);
776 break;
777 default:
778 break;
782 static int
783 pre_RS(DECL_ARGS)
785 struct roffsu su;
787 switch (n->type) {
788 case ROFFT_BLOCK:
789 term_newln(p);
790 return 1;
791 case ROFFT_HEAD:
792 return 0;
793 default:
794 break;
797 n = n->parent->head;
798 n->aux = SHRT_MAX + 1;
799 if (n->child == NULL)
800 n->aux = mt->lmargin[mt->lmargincur];
801 else if (a2roffsu(n->child->string, &su, SCALE_EN) != NULL)
802 n->aux = term_hen(p, &su);
803 if (n->aux < 0 && (size_t)(-n->aux) > mt->offset)
804 n->aux = -mt->offset;
805 else if (n->aux > SHRT_MAX)
806 n->aux = term_len(p, p->defindent);
808 mt->offset += n->aux;
809 p->tcol->offset = mt->offset;
810 p->tcol->rmargin = p->maxrmargin;
812 if (++mt->lmarginsz < MAXMARGINS)
813 mt->lmargincur = mt->lmarginsz;
815 mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
816 return 1;
819 static void
820 post_RS(DECL_ARGS)
823 switch (n->type) {
824 case ROFFT_BLOCK:
825 return;
826 case ROFFT_HEAD:
827 return;
828 default:
829 term_newln(p);
830 break;
833 mt->offset -= n->parent->head->aux;
834 p->tcol->offset = mt->offset;
836 if (--mt->lmarginsz < MAXMARGINS)
837 mt->lmargincur = mt->lmarginsz;
840 static int
841 pre_UR(DECL_ARGS)
844 return n->type != ROFFT_HEAD;
847 static void
848 post_UR(DECL_ARGS)
851 if (n->type != ROFFT_BLOCK)
852 return;
854 term_word(p, "<");
855 p->flags |= TERMP_NOSPACE;
857 if (NULL != n->child->child)
858 print_man_node(p, mt, n->child->child, meta);
860 p->flags |= TERMP_NOSPACE;
861 term_word(p, ">");
864 static void
865 print_man_node(DECL_ARGS)
867 int c;
869 switch (n->type) {
870 case ROFFT_TEXT:
872 * If we have a blank line, output a vertical space.
873 * If we have a space as the first character, break
874 * before printing the line's data.
876 if (*n->string == '\0') {
877 if (p->flags & TERMP_NONEWLINE)
878 term_newln(p);
879 else
880 term_vspace(p);
881 return;
882 } else if (*n->string == ' ' && n->flags & NODE_LINE &&
883 (p->flags & TERMP_NONEWLINE) == 0)
884 term_newln(p);
886 term_word(p, n->string);
887 goto out;
889 case ROFFT_EQN:
890 if ( ! (n->flags & NODE_LINE))
891 p->flags |= TERMP_NOSPACE;
892 term_eqn(p, n->eqn);
893 if (n->next != NULL && ! (n->next->flags & NODE_LINE))
894 p->flags |= TERMP_NOSPACE;
895 return;
896 case ROFFT_TBL:
897 if (p->tbl.cols == NULL)
898 term_vspace(p);
899 term_tbl(p, n->span);
900 return;
901 default:
902 break;
905 if (n->tok < ROFF_MAX) {
906 roff_term_pre(p, n);
907 return;
910 assert(n->tok >= MAN_TH && n->tok <= MAN_MAX);
911 if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
912 term_fontrepl(p, TERMFONT_NONE);
914 c = 1;
915 if (termacts[n->tok].pre)
916 c = (*termacts[n->tok].pre)(p, mt, n, meta);
918 if (c && n->child)
919 print_man_nodelist(p, mt, n->child, meta);
921 if (termacts[n->tok].post)
922 (*termacts[n->tok].post)(p, mt, n, meta);
923 if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
924 term_fontrepl(p, TERMFONT_NONE);
926 out:
928 * If we're in a literal context, make sure that words
929 * together on the same line stay together. This is a
930 * POST-printing call, so we check the NEXT word. Since
931 * -man doesn't have nested macros, we don't need to be
932 * more specific than this.
934 if (mt->fl & MANT_LITERAL &&
935 ! (p->flags & (TERMP_NOBREAK | TERMP_NONEWLINE)) &&
936 (n->next == NULL || n->next->flags & NODE_LINE)) {
937 p->flags |= TERMP_BRNEVER | TERMP_NOSPACE;
938 if (n->string != NULL && *n->string != '\0')
939 term_flushln(p);
940 else
941 term_newln(p);
942 p->flags &= ~TERMP_BRNEVER;
943 if (p->tcol->rmargin < p->maxrmargin &&
944 n->parent->tok == MAN_HP) {
945 p->tcol->offset = p->tcol->rmargin;
946 p->tcol->rmargin = p->maxrmargin;
949 if (NODE_EOS & n->flags)
950 p->flags |= TERMP_SENTENCE;
954 static void
955 print_man_nodelist(DECL_ARGS)
958 while (n != NULL) {
959 print_man_node(p, mt, n, meta);
960 n = n->next;
964 static void
965 print_man_foot(struct termp *p, const struct roff_meta *meta)
967 char *title;
968 size_t datelen, titlen;
970 assert(meta->title);
971 assert(meta->msec);
972 assert(meta->date);
974 term_fontrepl(p, TERMFONT_NONE);
976 if (meta->hasbody)
977 term_vspace(p);
980 * Temporary, undocumented option to imitate mdoc(7) output.
981 * In the bottom right corner, use the operating system
982 * instead of the title.
985 if ( ! p->mdocstyle) {
986 if (meta->hasbody) {
987 term_vspace(p);
988 term_vspace(p);
990 mandoc_asprintf(&title, "%s(%s)",
991 meta->title, meta->msec);
992 } else if (meta->os) {
993 title = mandoc_strdup(meta->os);
994 } else {
995 title = mandoc_strdup("");
997 datelen = term_strlen(p, meta->date);
999 /* Bottom left corner: operating system. */
1001 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
1002 p->trailspace = 1;
1003 p->tcol->offset = 0;
1004 p->tcol->rmargin = p->maxrmargin > datelen ?
1005 (p->maxrmargin + term_len(p, 1) - datelen) / 2 : 0;
1007 if (meta->os)
1008 term_word(p, meta->os);
1009 term_flushln(p);
1011 /* At the bottom in the middle: manual date. */
1013 p->tcol->offset = p->tcol->rmargin;
1014 titlen = term_strlen(p, title);
1015 p->tcol->rmargin = p->maxrmargin > titlen ?
1016 p->maxrmargin - titlen : 0;
1017 p->flags |= TERMP_NOSPACE;
1019 term_word(p, meta->date);
1020 term_flushln(p);
1022 /* Bottom right corner: manual title and section. */
1024 p->flags &= ~TERMP_NOBREAK;
1025 p->flags |= TERMP_NOSPACE;
1026 p->trailspace = 0;
1027 p->tcol->offset = p->tcol->rmargin;
1028 p->tcol->rmargin = p->maxrmargin;
1030 term_word(p, title);
1031 term_flushln(p);
1032 free(title);
1035 static void
1036 print_man_head(struct termp *p, const struct roff_meta *meta)
1038 const char *volume;
1039 char *title;
1040 size_t vollen, titlen;
1042 assert(meta->title);
1043 assert(meta->msec);
1045 volume = NULL == meta->vol ? "" : meta->vol;
1046 vollen = term_strlen(p, volume);
1048 /* Top left corner: manual title and section. */
1050 mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec);
1051 titlen = term_strlen(p, title);
1053 p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
1054 p->trailspace = 1;
1055 p->tcol->offset = 0;
1056 p->tcol->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ?
1057 (p->maxrmargin - vollen + term_len(p, 1)) / 2 :
1058 vollen < p->maxrmargin ? p->maxrmargin - vollen : 0;
1060 term_word(p, title);
1061 term_flushln(p);
1063 /* At the top in the middle: manual volume. */
1065 p->flags |= TERMP_NOSPACE;
1066 p->tcol->offset = p->tcol->rmargin;
1067 p->tcol->rmargin = p->tcol->offset + vollen + titlen <
1068 p->maxrmargin ? p->maxrmargin - titlen : p->maxrmargin;
1070 term_word(p, volume);
1071 term_flushln(p);
1073 /* Top right corner: title and section, again. */
1075 p->flags &= ~TERMP_NOBREAK;
1076 p->trailspace = 0;
1077 if (p->tcol->rmargin + titlen <= p->maxrmargin) {
1078 p->flags |= TERMP_NOSPACE;
1079 p->tcol->offset = p->tcol->rmargin;
1080 p->tcol->rmargin = p->maxrmargin;
1081 term_word(p, title);
1082 term_flushln(p);
1085 p->flags &= ~TERMP_NOSPACE;
1086 p->tcol->offset = 0;
1087 p->tcol->rmargin = p->maxrmargin;
1090 * Groff prints three blank lines before the content.
1091 * Do the same, except in the temporary, undocumented
1092 * mode imitating mdoc(7) output.
1095 term_vspace(p);
1096 if ( ! p->mdocstyle) {
1097 term_vspace(p);
1098 term_vspace(p);
1100 free(title);