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