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