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