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