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