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