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