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