Bring in OpenBSD's mandoc(1) tool for formatting manual pages.
[dragonfly.git] / usr.bin / mandoc / mdoc_term.c
1 /*      $Id: mdoc_term.c,v 1.61 2009/10/21 19:13:50 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 #include <sys/types.h>
18
19 #include <assert.h>
20 #include <ctype.h>
21 #include <err.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 #include "out.h"
27 #include "term.h"
28 #include "mdoc.h"
29 #include "chars.h"
30 #include "main.h"
31
32 #define INDENT            5
33 #define HALFINDENT        3
34
35 struct  termpair {
36         struct termpair  *ppair;
37         int               flag;
38         int               count;
39 };
40
41 #define DECL_ARGS struct termp *p, \
42                   struct termpair *pair, \
43                   const struct mdoc_meta *m, \
44                   const struct mdoc_node *n
45
46 struct  termact {
47         int     (*pre)(DECL_ARGS);
48         void    (*post)(DECL_ARGS);
49 };
50
51 static  void      termp____post(DECL_ARGS);
52 static  void      termp_an_post(DECL_ARGS);
53 static  void      termp_aq_post(DECL_ARGS);
54 static  void      termp_bd_post(DECL_ARGS);
55 static  void      termp_bl_post(DECL_ARGS);
56 static  void      termp_bq_post(DECL_ARGS);
57 static  void      termp_brq_post(DECL_ARGS);
58 static  void      termp_bx_post(DECL_ARGS);
59 static  void      termp_d1_post(DECL_ARGS);
60 static  void      termp_dq_post(DECL_ARGS);
61 static  void      termp_fd_post(DECL_ARGS);
62 static  void      termp_fn_post(DECL_ARGS);
63 static  void      termp_fo_post(DECL_ARGS);
64 static  void      termp_ft_post(DECL_ARGS);
65 static  void      termp_in_post(DECL_ARGS);
66 static  void      termp_it_post(DECL_ARGS);
67 static  void      termp_lb_post(DECL_ARGS);
68 static  void      termp_op_post(DECL_ARGS);
69 static  void      termp_pf_post(DECL_ARGS);
70 static  void      termp_pq_post(DECL_ARGS);
71 static  void      termp_qq_post(DECL_ARGS);
72 static  void      termp_sh_post(DECL_ARGS);
73 static  void      termp_sq_post(DECL_ARGS);
74 static  void      termp_ss_post(DECL_ARGS);
75 static  void      termp_vt_post(DECL_ARGS);
76
77 static  int       termp__t_pre(DECL_ARGS);
78 static  int       termp_an_pre(DECL_ARGS);
79 static  int       termp_ap_pre(DECL_ARGS);
80 static  int       termp_aq_pre(DECL_ARGS);
81 static  int       termp_bd_pre(DECL_ARGS);
82 static  int       termp_bf_pre(DECL_ARGS);
83 static  int       termp_bold_pre(DECL_ARGS);
84 static  int       termp_bq_pre(DECL_ARGS);
85 static  int       termp_brq_pre(DECL_ARGS);
86 static  int       termp_bt_pre(DECL_ARGS);
87 static  int       termp_cd_pre(DECL_ARGS);
88 static  int       termp_d1_pre(DECL_ARGS);
89 static  int       termp_dq_pre(DECL_ARGS);
90 static  int       termp_ex_pre(DECL_ARGS);
91 static  int       termp_fa_pre(DECL_ARGS);
92 static  int       termp_fl_pre(DECL_ARGS);
93 static  int       termp_fn_pre(DECL_ARGS);
94 static  int       termp_fo_pre(DECL_ARGS);
95 static  int       termp_ft_pre(DECL_ARGS);
96 static  int       termp_in_pre(DECL_ARGS);
97 static  int       termp_it_pre(DECL_ARGS);
98 static  int       termp_lk_pre(DECL_ARGS);
99 static  int       termp_nd_pre(DECL_ARGS);
100 static  int       termp_nm_pre(DECL_ARGS);
101 static  int       termp_ns_pre(DECL_ARGS);
102 static  int       termp_op_pre(DECL_ARGS);
103 static  int       termp_pf_pre(DECL_ARGS);
104 static  int       termp_pq_pre(DECL_ARGS);
105 static  int       termp_qq_pre(DECL_ARGS);
106 static  int       termp_rs_pre(DECL_ARGS);
107 static  int       termp_rv_pre(DECL_ARGS);
108 static  int       termp_sh_pre(DECL_ARGS);
109 static  int       termp_sm_pre(DECL_ARGS);
110 static  int       termp_sp_pre(DECL_ARGS);
111 static  int       termp_sq_pre(DECL_ARGS);
112 static  int       termp_ss_pre(DECL_ARGS);
113 static  int       termp_under_pre(DECL_ARGS);
114 static  int       termp_ud_pre(DECL_ARGS);
115 static  int       termp_xr_pre(DECL_ARGS);
116 static  int       termp_xx_pre(DECL_ARGS);
117
118 static  const struct termact termacts[MDOC_MAX] = {
119         { termp_ap_pre, NULL }, /* Ap */
120         { NULL, NULL }, /* Dd */
121         { NULL, NULL }, /* Dt */
122         { NULL, NULL }, /* Os */
123         { termp_sh_pre, termp_sh_post }, /* Sh */
124         { termp_ss_pre, termp_ss_post }, /* Ss */
125         { termp_sp_pre, NULL }, /* Pp */
126         { termp_d1_pre, termp_d1_post }, /* D1 */
127         { termp_d1_pre, termp_d1_post }, /* Dl */
128         { termp_bd_pre, termp_bd_post }, /* Bd */
129         { NULL, NULL }, /* Ed */
130         { NULL, termp_bl_post }, /* Bl */
131         { NULL, NULL }, /* El */
132         { termp_it_pre, termp_it_post }, /* It */
133         { NULL, NULL }, /* Ad */
134         { termp_an_pre, termp_an_post }, /* An */
135         { termp_under_pre, NULL }, /* Ar */
136         { termp_cd_pre, NULL }, /* Cd */
137         { termp_bold_pre, NULL }, /* Cm */
138         { NULL, NULL }, /* Dv */
139         { NULL, NULL }, /* Er */
140         { NULL, NULL }, /* Ev */
141         { termp_ex_pre, NULL }, /* Ex */
142         { termp_fa_pre, NULL }, /* Fa */
143         { termp_bold_pre, termp_fd_post }, /* Fd */
144         { termp_fl_pre, NULL }, /* Fl */
145         { termp_fn_pre, termp_fn_post }, /* Fn */
146         { termp_ft_pre, termp_ft_post }, /* Ft */
147         { termp_bold_pre, NULL }, /* Ic */
148         { termp_in_pre, termp_in_post }, /* In */
149         { NULL, NULL }, /* Li */
150         { termp_nd_pre, NULL }, /* Nd */
151         { termp_nm_pre, NULL }, /* Nm */
152         { termp_op_pre, termp_op_post }, /* Op */
153         { NULL, NULL }, /* Ot */
154         { termp_under_pre, NULL }, /* Pa */
155         { termp_rv_pre, NULL }, /* Rv */
156         { NULL, NULL }, /* St */
157         { termp_under_pre, NULL }, /* Va */
158         { termp_under_pre, termp_vt_post }, /* Vt */
159         { termp_xr_pre, NULL }, /* Xr */
160         { NULL, termp____post }, /* %A */
161         { termp_under_pre, termp____post }, /* %B */
162         { NULL, termp____post }, /* %D */
163         { termp_under_pre, termp____post }, /* %I */
164         { termp_under_pre, termp____post }, /* %J */
165         { NULL, termp____post }, /* %N */
166         { NULL, termp____post }, /* %O */
167         { NULL, termp____post }, /* %P */
168         { NULL, termp____post }, /* %R */
169         { termp__t_pre, termp____post }, /* %T */
170         { NULL, termp____post }, /* %V */
171         { NULL, NULL }, /* Ac */
172         { termp_aq_pre, termp_aq_post }, /* Ao */
173         { termp_aq_pre, termp_aq_post }, /* Aq */
174         { NULL, NULL }, /* At */
175         { NULL, NULL }, /* Bc */
176         { termp_bf_pre, NULL }, /* Bf */
177         { termp_bq_pre, termp_bq_post }, /* Bo */
178         { termp_bq_pre, termp_bq_post }, /* Bq */
179         { termp_xx_pre, NULL }, /* Bsx */
180         { NULL, termp_bx_post }, /* Bx */
181         { NULL, NULL }, /* Db */
182         { NULL, NULL }, /* Dc */
183         { termp_dq_pre, termp_dq_post }, /* Do */
184         { termp_dq_pre, termp_dq_post }, /* Dq */
185         { NULL, NULL }, /* Ec */
186         { NULL, NULL }, /* Ef */
187         { termp_under_pre, NULL }, /* Em */
188         { NULL, NULL }, /* Eo */
189         { termp_xx_pre, NULL }, /* Fx */
190         { termp_bold_pre, NULL }, /* Ms */ /* FIXME: convert to symbol? */
191         { NULL, NULL }, /* No */
192         { termp_ns_pre, NULL }, /* Ns */
193         { termp_xx_pre, NULL }, /* Nx */
194         { termp_xx_pre, NULL }, /* Ox */
195         { NULL, NULL }, /* Pc */
196         { termp_pf_pre, termp_pf_post }, /* Pf */
197         { termp_pq_pre, termp_pq_post }, /* Po */
198         { termp_pq_pre, termp_pq_post }, /* Pq */
199         { NULL, NULL }, /* Qc */
200         { termp_sq_pre, termp_sq_post }, /* Ql */
201         { termp_qq_pre, termp_qq_post }, /* Qo */
202         { termp_qq_pre, termp_qq_post }, /* Qq */
203         { NULL, NULL }, /* Re */
204         { termp_rs_pre, NULL }, /* Rs */
205         { NULL, NULL }, /* Sc */
206         { termp_sq_pre, termp_sq_post }, /* So */
207         { termp_sq_pre, termp_sq_post }, /* Sq */
208         { termp_sm_pre, NULL }, /* Sm */
209         { termp_under_pre, NULL }, /* Sx */
210         { termp_bold_pre, NULL }, /* Sy */
211         { NULL, NULL }, /* Tn */
212         { termp_xx_pre, NULL }, /* Ux */
213         { NULL, NULL }, /* Xc */
214         { NULL, NULL }, /* Xo */
215         { termp_fo_pre, termp_fo_post }, /* Fo */
216         { NULL, NULL }, /* Fc */
217         { termp_op_pre, termp_op_post }, /* Oo */
218         { NULL, NULL }, /* Oc */
219         { NULL, NULL }, /* Bk */
220         { NULL, NULL }, /* Ek */
221         { termp_bt_pre, NULL }, /* Bt */
222         { NULL, NULL }, /* Hf */
223         { NULL, NULL }, /* Fr */
224         { termp_ud_pre, NULL }, /* Ud */
225         { NULL, termp_lb_post }, /* Lb */
226         { termp_sp_pre, NULL }, /* Lp */
227         { termp_lk_pre, NULL }, /* Lk */
228         { termp_under_pre, NULL }, /* Mt */
229         { termp_brq_pre, termp_brq_post }, /* Brq */
230         { termp_brq_pre, termp_brq_post }, /* Bro */
231         { NULL, NULL }, /* Brc */
232         { NULL, termp____post }, /* %C */
233         { NULL, NULL }, /* Es */ /* TODO */
234         { NULL, NULL }, /* En */ /* TODO */
235         { termp_xx_pre, NULL }, /* Dx */
236         { NULL, termp____post }, /* %Q */
237         { termp_sp_pre, NULL }, /* br */
238         { termp_sp_pre, NULL }, /* sp */
239 };
240
241 static  size_t    arg2width(const struct mdoc_argv *, int);
242 static  size_t    arg2height(const struct mdoc_node *);
243 static  size_t    arg2offs(const struct mdoc_argv *);
244
245 static  int       arg_hasattr(int, const struct mdoc_node *);
246 static  int       arg_getattrs(const int *, int *, size_t,
247                         const struct mdoc_node *);
248 static  int       arg_getattr(int, const struct mdoc_node *);
249 static  int       arg_listtype(const struct mdoc_node *);
250 static  void      print_bvspace(struct termp *,
251                         const struct mdoc_node *,
252                         const struct mdoc_node *);
253 static  void      print_node(DECL_ARGS);
254 static  void      print_head(DECL_ARGS);
255 static  void      print_body(DECL_ARGS);
256 static  void      print_foot(DECL_ARGS);
257
258
259 void
260 terminal_mdoc(void *arg, const struct mdoc *mdoc)
261 {
262         const struct mdoc_node  *n;
263         const struct mdoc_meta  *m;
264         struct termp            *p;
265
266         p = (struct termp *)arg;
267
268         if (NULL == p->symtab)
269                 switch (p->enc) {
270                 case (TERMENC_ASCII):
271                         p->symtab = chars_init(CHARS_ASCII);
272                         break;
273                 default:
274                         abort();
275                         /* NOTREACHED */
276                 }
277
278         n = mdoc_node(mdoc);
279         m = mdoc_meta(mdoc);
280
281         print_head(p, NULL, m, n);
282         if (n->child)
283                 print_body(p, NULL, m, n->child);
284         print_foot(p, NULL, m, n);
285 }
286
287
288 static void
289 print_body(DECL_ARGS)
290 {
291
292         print_node(p, pair, m, n);
293         if (n->next)
294                 print_body(p, pair, m, n->next);
295 }
296
297
298 /* ARGSUSED */
299 static void
300 print_node(DECL_ARGS)
301 {
302         int              chld, bold, under;
303         struct termpair  npair;
304         size_t           offset, rmargin;
305
306         chld = 1;
307         offset = p->offset;
308         rmargin = p->rmargin;
309         bold = p->bold;
310         under = p->under;
311
312         bzero(&npair, sizeof(struct termpair));
313         npair.ppair = pair;
314
315         if (MDOC_TEXT != n->type) {
316                 if (termacts[n->tok].pre)
317                         chld = (*termacts[n->tok].pre)(p, &npair, m, n);
318         } else
319                 term_word(p, n->string);
320         if (chld && n->child)
321                 print_body(p, &npair, m, n->child);
322
323         /*
324          * XXX - if bold/under were to span scopes, this wouldn't be
325          * possible, but because decoration is always in-scope, we can
326          * get away with this.
327          */
328
329         p->bold = bold;
330         p->under = under;
331
332         if (MDOC_TEXT != n->type)
333                 if (termacts[n->tok].post)
334                         (*termacts[n->tok].post)(p, &npair, m, n);
335
336         p->offset = offset;
337         p->rmargin = rmargin;
338 }
339
340
341 /* ARGSUSED */
342 static void
343 print_foot(DECL_ARGS)
344 {
345         struct tm       *tm;
346         char            *buf, *os;
347
348         /*
349          * Output the footer in new-groff style, that is, three columns
350          * with the middle being the manual date and flanking columns
351          * being the operating system:
352          *
353          * SYSTEM                  DATE                    SYSTEM
354          */
355
356         if (NULL == (buf = malloc(p->rmargin)))
357                 err(EXIT_FAILURE, "malloc");
358         if (NULL == (os = malloc(p->rmargin)))
359                 err(EXIT_FAILURE, "malloc");
360
361         tm = localtime(&m->date);
362
363         if (0 == strftime(buf, p->rmargin, "%B %e, %Y", tm))
364                 err(EXIT_FAILURE, "strftime");
365
366         (void)strlcpy(os, m->os, p->rmargin);
367
368         term_vspace(p);
369
370         p->offset = 0;
371         p->rmargin = (p->maxrmargin - strlen(buf) + 1) / 2;
372         p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
373
374         term_word(p, os);
375         term_flushln(p);
376
377         p->offset = p->rmargin;
378         p->rmargin = p->maxrmargin - strlen(os);
379         p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
380
381         term_word(p, buf);
382         term_flushln(p);
383
384         p->offset = p->rmargin;
385         p->rmargin = p->maxrmargin;
386         p->flags &= ~TERMP_NOBREAK;
387         p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
388
389         term_word(p, os);
390         term_flushln(p);
391
392         p->offset = 0;
393         p->rmargin = p->maxrmargin;
394         p->flags = 0;
395
396         free(buf);
397         free(os);
398 }
399
400
401 /* FIXME: put in utility library. */
402 /* ARGSUSED */
403 static void
404 print_head(DECL_ARGS)
405 {
406         char            *buf, *title;
407
408         p->rmargin = p->maxrmargin;
409         p->offset = 0;
410
411         if (NULL == (buf = malloc(p->rmargin)))
412                 err(EXIT_FAILURE, "malloc");
413         if (NULL == (title = malloc(p->rmargin)))
414                 err(EXIT_FAILURE, "malloc");
415
416         /*
417          * The header is strange.  It has three components, which are
418          * really two with the first duplicated.  It goes like this:
419          *
420          * IDENTIFIER              TITLE                   IDENTIFIER
421          *
422          * The IDENTIFIER is NAME(SECTION), which is the command-name
423          * (if given, or "unknown" if not) followed by the manual page
424          * section.  These are given in `Dt'.  The TITLE is a free-form
425          * string depending on the manual volume.  If not specified, it
426          * switches on the manual section.
427          */
428
429         assert(m->vol);
430         (void)strlcpy(buf, m->vol, p->rmargin);
431
432         if (m->arch) {
433                 (void)strlcat(buf, " (", p->rmargin);
434                 (void)strlcat(buf, m->arch, p->rmargin);
435                 (void)strlcat(buf, ")", p->rmargin);
436         }
437
438         snprintf(title, p->rmargin, "%s(%d)", m->title, m->msec);
439
440         p->offset = 0;
441         p->rmargin = (p->maxrmargin - strlen(buf) + 1) / 2;
442         p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
443
444         term_word(p, title);
445         term_flushln(p);
446
447         p->offset = p->rmargin;
448         p->rmargin = p->maxrmargin - strlen(title);
449         p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
450
451         term_word(p, buf);
452         term_flushln(p);
453
454         p->offset = p->rmargin;
455         p->rmargin = p->maxrmargin;
456         p->flags &= ~TERMP_NOBREAK;
457         p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
458
459         term_word(p, title);
460         term_flushln(p);
461
462         p->offset = 0;
463         p->rmargin = p->maxrmargin;
464         p->flags &= ~TERMP_NOSPACE;
465
466         free(title);
467         free(buf);
468 }
469
470
471 static size_t
472 arg2height(const struct mdoc_node *n)
473 {
474         struct roffsu    su;
475
476         assert(MDOC_TEXT == n->type);
477         assert(n->string);
478         if ( ! a2roffsu(n->string, &su, SCALE_VS))
479                 SCALE_VS_INIT(&su, strlen(n->string));
480
481         return(term_vspan(&su));
482 }
483
484
485 static size_t
486 arg2width(const struct mdoc_argv *arg, int pos)
487 {
488         struct roffsu    su;
489
490         assert(arg->value[pos]);
491         if ( ! a2roffsu(arg->value[pos], &su, SCALE_MAX))
492                 SCALE_HS_INIT(&su, strlen(arg->value[pos]));
493
494         /* XXX: pachemu? */
495         return(term_hspan(&su) + 2);
496 }
497
498
499 static int
500 arg_listtype(const struct mdoc_node *n)
501 {
502         int              i, len;
503
504         assert(MDOC_BLOCK == n->type);
505
506         len = (int)(n->args ? n->args->argc : 0);
507
508         for (i = 0; i < len; i++)
509                 switch (n->args->argv[i].arg) {
510                 case (MDOC_Bullet):
511                         /* FALLTHROUGH */
512                 case (MDOC_Dash):
513                         /* FALLTHROUGH */
514                 case (MDOC_Enum):
515                         /* FALLTHROUGH */
516                 case (MDOC_Hyphen):
517                         /* FALLTHROUGH */
518                 case (MDOC_Tag):
519                         /* FALLTHROUGH */
520                 case (MDOC_Inset):
521                         /* FALLTHROUGH */
522                 case (MDOC_Diag):
523                         /* FALLTHROUGH */
524                 case (MDOC_Item):
525                         /* FALLTHROUGH */
526                 case (MDOC_Column):
527                         /* FALLTHROUGH */
528                 case (MDOC_Hang):
529                         /* FALLTHROUGH */
530                 case (MDOC_Ohang):
531                         return(n->args->argv[i].arg);
532                 default:
533                         break;
534                 }
535
536         return(-1);
537 }
538
539
540 static size_t
541 arg2offs(const struct mdoc_argv *arg)
542 {
543         struct roffsu    su;
544
545         if ('\0' == arg->value[0][0])
546                 return(0);
547         else if (0 == strcmp(arg->value[0], "left"))
548                 return(0);
549         else if (0 == strcmp(arg->value[0], "indent"))
550                 return(INDENT + 1);
551         else if (0 == strcmp(arg->value[0], "indent-two"))
552                 return((INDENT + 1) * 2);
553         else if ( ! a2roffsu(arg->value[0], &su, SCALE_MAX))
554                 SCALE_HS_INIT(&su, strlen(arg->value[0]));
555
556         return(term_hspan(&su));
557 }
558
559
560 static int
561 arg_hasattr(int arg, const struct mdoc_node *n)
562 {
563
564         return(-1 != arg_getattr(arg, n));
565 }
566
567
568 static int
569 arg_getattr(int v, const struct mdoc_node *n)
570 {
571         int              val;
572
573         return(arg_getattrs(&v, &val, 1, n) ? val : -1);
574 }
575
576
577 static int
578 arg_getattrs(const int *keys, int *vals,
579                 size_t sz, const struct mdoc_node *n)
580 {
581         int              i, j, k;
582
583         if (NULL == n->args)
584                 return(0);
585
586         for (k = i = 0; i < (int)n->args->argc; i++)
587                 for (j = 0; j < (int)sz; j++)
588                         if (n->args->argv[i].arg == keys[j]) {
589                                 vals[j] = i;
590                                 k++;
591                         }
592         return(k);
593 }
594
595
596 static void
597 print_bvspace(struct termp *p,
598                 const struct mdoc_node *bl,
599                 const struct mdoc_node *n)
600 {
601         const struct mdoc_node  *nn;
602
603         term_newln(p);
604         if (arg_hasattr(MDOC_Compact, bl))
605                 return;
606
607         /* Do not vspace directly after Ss/Sh. */
608
609         for (nn = n; nn; nn = nn->parent) {
610                 if (MDOC_BLOCK != nn->type)
611                         continue;
612                 if (MDOC_Ss == nn->tok)
613                         return;
614                 if (MDOC_Sh == nn->tok)
615                         return;
616                 if (NULL == nn->prev)
617                         continue;
618                 break;
619         }
620
621         /* A `-column' does not assert vspace within the list. */
622
623         if (MDOC_Bl == bl->tok && arg_hasattr(MDOC_Column, bl))
624                 if (n->prev && MDOC_It == n->prev->tok)
625                         return;
626
627         /* A `-diag' without body does not vspace. */
628
629         if (MDOC_Bl == bl->tok && arg_hasattr(MDOC_Diag, bl))
630                 if (n->prev && MDOC_It == n->prev->tok) {
631                         assert(n->prev->body);
632                         if (NULL == n->prev->body->child)
633                                 return;
634                 }
635
636         term_vspace(p);
637 }
638
639
640 /* ARGSUSED */
641 static int
642 termp_dq_pre(DECL_ARGS)
643 {
644
645         if (MDOC_BODY != n->type)
646                 return(1);
647
648         term_word(p, "\\(lq");
649         p->flags |= TERMP_NOSPACE;
650         return(1);
651 }
652
653
654 /* ARGSUSED */
655 static void
656 termp_dq_post(DECL_ARGS)
657 {
658
659         if (MDOC_BODY != n->type)
660                 return;
661
662         p->flags |= TERMP_NOSPACE;
663         term_word(p, "\\(rq");
664 }
665
666
667 /* ARGSUSED */
668 static int
669 termp_it_pre(DECL_ARGS)
670 {
671         const struct mdoc_node *bl, *nn;
672         char                    buf[7];
673         int                     i, type, keys[3], vals[3];
674         size_t                  width, offset;
675
676         if (MDOC_BLOCK == n->type) {
677                 print_bvspace(p, n->parent->parent, n);
678                 return(1);
679         }
680
681         bl = n->parent->parent->parent;
682
683         /* Save parent attributes. */
684
685         pair->flag = p->flags;
686
687         /* Get list width and offset. */
688
689         keys[0] = MDOC_Width;
690         keys[1] = MDOC_Offset;
691         keys[2] = MDOC_Column;
692
693         vals[0] = vals[1] = vals[2] = -1;
694
695         width = offset = 0;
696
697         (void)arg_getattrs(keys, vals, 3, bl);
698
699         type = arg_listtype(bl);
700         assert(-1 != type);
701
702         /* Calculate real width and offset. */
703
704         switch (type) {
705         case (MDOC_Column):
706                 if (MDOC_BODY == n->type)
707                         break;
708                 /*
709                  * Work around groff's column handling.  The offset is
710                  * equal to the sum of all widths leading to the current
711                  * column (plus the -offset value).  If this column
712                  * exceeds the stated number of columns, the width is
713                  * set as 0, else it's the stated column width (later
714                  * the 0 will be adjusted to default 10 or, if in the
715                  * last column case, set to stretch to the margin).
716                  */
717                 for (i = 0, nn = n->prev; nn &&
718                                 i < (int)bl->args->argv[vals[2]].sz;
719                                 nn = nn->prev, i++)
720                         offset += arg2width
721                                 (&bl->args->argv[vals[2]], i);
722
723                 /* Whether exceeds maximum column. */
724                 if (i < (int)bl->args->argv[vals[2]].sz)
725                         width = arg2width(&bl->args->argv[vals[2]], i);
726                 else
727                         width = 0;
728
729                 if (vals[1] >= 0)
730                         offset += arg2offs(&bl->args->argv[vals[1]]);
731                 break;
732         default:
733                 if (vals[0] >= 0)
734                         width = arg2width(&bl->args->argv[vals[0]], 0);
735                 if (vals[1] >= 0)
736                         offset += arg2offs(&bl->args->argv[vals[1]]);
737                 break;
738         }
739
740         /*
741          * List-type can override the width in the case of fixed-head
742          * values (bullet, dash/hyphen, enum).  Tags need a non-zero
743          * offset.
744          */
745
746         switch (type) {
747         case (MDOC_Bullet):
748                 /* FALLTHROUGH */
749         case (MDOC_Dash):
750                 /* FALLTHROUGH */
751         case (MDOC_Hyphen):
752                 if (width < 4)
753                         width = 4;
754                 break;
755         case (MDOC_Enum):
756                 if (width < 5)
757                         width = 5;
758                 break;
759         case (MDOC_Hang):
760                 if (0 == width)
761                         width = 8;
762                 break;
763         case (MDOC_Column):
764                 /* FALLTHROUGH */
765         case (MDOC_Tag):
766                 if (0 == width)
767                         width = 10;
768                 break;
769         default:
770                 break;
771         }
772
773         /*
774          * Whitespace control.  Inset bodies need an initial space,
775          * while diagonal bodies need two.
776          */
777
778         p->flags |= TERMP_NOSPACE;
779
780         switch (type) {
781         case (MDOC_Diag):
782                 if (MDOC_BODY == n->type)
783                         term_word(p, "\\ \\ ");
784                 break;
785         case (MDOC_Inset):
786                 if (MDOC_BODY == n->type)
787                         term_word(p, "\\ ");
788                 break;
789         default:
790                 break;
791         }
792
793         p->flags |= TERMP_NOSPACE;
794
795         switch (type) {
796         case (MDOC_Diag):
797                 if (MDOC_HEAD == n->type)
798                         p->bold++;
799                 break;
800         default:
801                 break;
802         }
803
804         /*
805          * Pad and break control.  This is the tricker part.  Lists with
806          * set right-margins for the head get TERMP_NOBREAK because, if
807          * they overrun the margin, they wrap to the new margin.
808          * Correspondingly, the body for these types don't left-pad, as
809          * the head will pad out to to the right.
810          */
811
812         switch (type) {
813         case (MDOC_Bullet):
814                 /* FALLTHROUGH */
815         case (MDOC_Dash):
816                 /* FALLTHROUGH */
817         case (MDOC_Enum):
818                 /* FALLTHROUGH */
819         case (MDOC_Hyphen):
820                 if (MDOC_HEAD == n->type)
821                         p->flags |= TERMP_NOBREAK;
822                 else
823                         p->flags |= TERMP_NOLPAD;
824                 break;
825         case (MDOC_Hang):
826                 if (MDOC_HEAD == n->type)
827                         p->flags |= TERMP_NOBREAK;
828                 else
829                         p->flags |= TERMP_NOLPAD;
830
831                 if (MDOC_HEAD != n->type)
832                         break;
833
834                 /*
835                  * This is ugly.  If `-hang' is specified and the body
836                  * is a `Bl' or `Bd', then we want basically to nullify
837                  * the "overstep" effect in term_flushln() and treat
838                  * this as a `-ohang' list instead.
839                  */
840                 if (n->next->child &&
841                                 (MDOC_Bl == n->next->child->tok ||
842                                  MDOC_Bd == n->next->child->tok)) {
843                         p->flags &= ~TERMP_NOBREAK;
844                         p->flags &= ~TERMP_NOLPAD;
845                 } else
846                         p->flags |= TERMP_HANG;
847                 break;
848         case (MDOC_Tag):
849                 if (MDOC_HEAD == n->type)
850                         p->flags |= TERMP_NOBREAK | TERMP_TWOSPACE;
851                 else
852                         p->flags |= TERMP_NOLPAD;
853
854                 if (MDOC_HEAD != n->type)
855                         break;
856                 if (NULL == n->next || NULL == n->next->child)
857                         p->flags |= TERMP_DANGLE;
858                 break;
859         case (MDOC_Column):
860                 if (MDOC_HEAD == n->type) {
861                         assert(n->next);
862                         if (MDOC_BODY == n->next->type)
863                                 p->flags &= ~TERMP_NOBREAK;
864                         else
865                                 p->flags |= TERMP_NOBREAK;
866                         if (n->prev)
867                                 p->flags |= TERMP_NOLPAD;
868                 }
869                 break;
870         case (MDOC_Diag):
871                 if (MDOC_HEAD == n->type)
872                         p->flags |= TERMP_NOBREAK;
873                 break;
874         default:
875                 break;
876         }
877
878         /*
879          * Margin control.  Set-head-width lists have their right
880          * margins shortened.  The body for these lists has the offset
881          * necessarily lengthened.  Everybody gets the offset.
882          */
883
884         p->offset += offset;
885
886         switch (type) {
887         case (MDOC_Hang):
888                 /*
889                  * Same stipulation as above, regarding `-hang'.  We
890                  * don't want to recalculate rmargin and offsets when
891                  * using `Bd' or `Bl' within `-hang' overstep lists.
892                  */
893                 if (MDOC_HEAD == n->type && n->next->child &&
894                                 (MDOC_Bl == n->next->child->tok ||
895                                  MDOC_Bd == n->next->child->tok))
896                         break;
897                 /* FALLTHROUGH */
898         case (MDOC_Bullet):
899                 /* FALLTHROUGH */
900         case (MDOC_Dash):
901                 /* FALLTHROUGH */
902         case (MDOC_Enum):
903                 /* FALLTHROUGH */
904         case (MDOC_Hyphen):
905                 /* FALLTHROUGH */
906         case (MDOC_Tag):
907                 assert(width);
908                 if (MDOC_HEAD == n->type)
909                         p->rmargin = p->offset + width;
910                 else
911                         p->offset += width;
912                 break;
913         case (MDOC_Column):
914                 assert(width);
915                 p->rmargin = p->offset + width;
916                 /*
917                  * XXX - this behaviour is not documented: the
918                  * right-most column is filled to the right margin.
919                  */
920                 if (MDOC_HEAD == n->type &&
921                                 MDOC_BODY == n->next->type)
922                         p->rmargin = p->maxrmargin;
923                 break;
924         default:
925                 break;
926         }
927
928         /*
929          * The dash, hyphen, bullet and enum lists all have a special
930          * HEAD character (temporarily bold, in some cases).
931          */
932
933         if (MDOC_HEAD == n->type)
934                 switch (type) {
935                 case (MDOC_Bullet):
936                         p->bold++;
937                         term_word(p, "\\[bu]");
938                         p->bold--;
939                         break;
940                 case (MDOC_Dash):
941                         /* FALLTHROUGH */
942                 case (MDOC_Hyphen):
943                         p->bold++;
944                         term_word(p, "\\(hy");
945                         p->bold--;
946                         break;
947                 case (MDOC_Enum):
948                         (pair->ppair->ppair->count)++;
949                         (void)snprintf(buf, sizeof(buf), "%d.",
950                                         pair->ppair->ppair->count);
951                         term_word(p, buf);
952                         break;
953                 default:
954                         break;
955                 }
956
957         /*
958          * If we're not going to process our children, indicate so here.
959          */
960
961         switch (type) {
962         case (MDOC_Bullet):
963                 /* FALLTHROUGH */
964         case (MDOC_Item):
965                 /* FALLTHROUGH */
966         case (MDOC_Dash):
967                 /* FALLTHROUGH */
968         case (MDOC_Hyphen):
969                 /* FALLTHROUGH */
970         case (MDOC_Enum):
971                 if (MDOC_HEAD == n->type)
972                         return(0);
973                 break;
974         case (MDOC_Column):
975                 if (MDOC_BODY == n->type)
976                         return(0);
977                 break;
978         default:
979                 break;
980         }
981
982         return(1);
983 }
984
985
986 /* ARGSUSED */
987 static void
988 termp_it_post(DECL_ARGS)
989 {
990         int                type;
991
992         if (MDOC_BODY != n->type && MDOC_HEAD != n->type)
993                 return;
994
995         type = arg_listtype(n->parent->parent->parent);
996         assert(-1 != type);
997
998         switch (type) {
999         case (MDOC_Item):
1000                 /* FALLTHROUGH */
1001         case (MDOC_Diag):
1002                 /* FALLTHROUGH */
1003         case (MDOC_Inset):
1004                 if (MDOC_BODY == n->type)
1005                         term_flushln(p);
1006                 break;
1007         case (MDOC_Column):
1008                 if (MDOC_HEAD == n->type)
1009                         term_flushln(p);
1010                 break;
1011         default:
1012                 term_flushln(p);
1013                 break;
1014         }
1015
1016         p->flags = pair->flag;
1017 }
1018
1019
1020 /* ARGSUSED */
1021 static int
1022 termp_nm_pre(DECL_ARGS)
1023 {
1024
1025         if (SEC_SYNOPSIS == n->sec)
1026                 term_newln(p);
1027         p->bold++;
1028         if (NULL == n->child)
1029                 term_word(p, m->name);
1030         return(1);
1031 }
1032
1033
1034 /* ARGSUSED */
1035 static int
1036 termp_fl_pre(DECL_ARGS)
1037 {
1038
1039         p->bold++;
1040         term_word(p, "\\-");
1041         p->flags |= TERMP_NOSPACE;
1042         return(1);
1043 }
1044
1045
1046 /* ARGSUSED */
1047 static int
1048 termp_an_pre(DECL_ARGS)
1049 {
1050
1051         if (NULL == n->child)
1052                 return(1);
1053
1054         /*
1055          * If not in the AUTHORS section, `An -split' will cause
1056          * newlines to occur before the author name.  If in the AUTHORS
1057          * section, by default, the first `An' invocation is nosplit,
1058          * then all subsequent ones, regardless of whether interspersed
1059          * with other macros/text, are split.  -split, in this case,
1060          * will override the condition of the implied first -nosplit.
1061          */
1062
1063         if (n->sec == SEC_AUTHORS) {
1064                 if ( ! (TERMP_ANPREC & p->flags)) {
1065                         if (TERMP_SPLIT & p->flags)
1066                                 term_newln(p);
1067                         return(1);
1068                 }
1069                 if (TERMP_NOSPLIT & p->flags)
1070                         return(1);
1071                 term_newln(p);
1072                 return(1);
1073         }
1074
1075         if (TERMP_SPLIT & p->flags)
1076                 term_newln(p);
1077
1078         return(1);
1079 }
1080
1081
1082 /* ARGSUSED */
1083 static void
1084 termp_an_post(DECL_ARGS)
1085 {
1086
1087         if (n->child) {
1088                 if (SEC_AUTHORS == n->sec)
1089                         p->flags |= TERMP_ANPREC;
1090                 return;
1091         }
1092
1093         if (arg_getattr(MDOC_Split, n) > -1) {
1094                 p->flags &= ~TERMP_NOSPLIT;
1095                 p->flags |= TERMP_SPLIT;
1096         } else {
1097                 p->flags &= ~TERMP_SPLIT;
1098                 p->flags |= TERMP_NOSPLIT;
1099         }
1100
1101 }
1102
1103
1104 /* ARGSUSED */
1105 static int
1106 termp_ns_pre(DECL_ARGS)
1107 {
1108
1109         p->flags |= TERMP_NOSPACE;
1110         return(1);
1111 }
1112
1113
1114 /* ARGSUSED */
1115 static int
1116 termp_rs_pre(DECL_ARGS)
1117 {
1118
1119         if (SEC_SEE_ALSO != n->sec)
1120                 return(1);
1121         if (MDOC_BLOCK == n->type && n->prev)
1122                 term_vspace(p);
1123         return(1);
1124 }
1125
1126
1127 /* ARGSUSED */
1128 static int
1129 termp_rv_pre(DECL_ARGS)
1130 {
1131         const struct mdoc_node  *nn;
1132
1133         term_newln(p);
1134         term_word(p, "The");
1135
1136         for (nn = n->child; nn; nn = nn->next) {
1137                 p->bold++;
1138                 term_word(p, nn->string);
1139                 p->bold--;
1140                 p->flags |= TERMP_NOSPACE;
1141                 if (nn->next && NULL == nn->next->next)
1142                         term_word(p, "(), and");
1143                 else if (nn->next)
1144                         term_word(p, "(),");
1145                 else
1146                         term_word(p, "()");
1147         }
1148
1149         if (n->child->next)
1150                 term_word(p, "functions return");
1151         else
1152                 term_word(p, "function returns");
1153
1154         term_word(p, "the value 0 if successful; otherwise the value "
1155                         "-1 is returned and the global variable");
1156
1157         p->under++;
1158         term_word(p, "errno");
1159         p->under--;
1160
1161         term_word(p, "is set to indicate the error.");
1162
1163         return(0);
1164 }
1165
1166
1167 /* ARGSUSED */
1168 static int
1169 termp_ex_pre(DECL_ARGS)
1170 {
1171         const struct mdoc_node  *nn;
1172
1173         term_word(p, "The");
1174
1175         for (nn = n->child; nn; nn = nn->next) {
1176                 p->bold++;
1177                 term_word(p, nn->string);
1178                 p->bold--;
1179                 p->flags |= TERMP_NOSPACE;
1180                 if (nn->next && NULL == nn->next->next)
1181                         term_word(p, ", and");
1182                 else if (nn->next)
1183                         term_word(p, ",");
1184                 else
1185                         p->flags &= ~TERMP_NOSPACE;
1186         }
1187
1188         if (n->child->next)
1189                 term_word(p, "utilities exit");
1190         else
1191                 term_word(p, "utility exits");
1192
1193         term_word(p, "0 on success, and >0 if an error occurs.");
1194
1195         return(0);
1196 }
1197
1198
1199 /* ARGSUSED */
1200 static int
1201 termp_nd_pre(DECL_ARGS)
1202 {
1203
1204         if (MDOC_BODY != n->type)
1205                 return(1);
1206
1207 #if defined(__OpenBSD__) || defined(__linux__)
1208         term_word(p, "\\(en");
1209 #else
1210         term_word(p, "\\(em");
1211 #endif
1212         return(1);
1213 }
1214
1215
1216 /* ARGSUSED */
1217 static void
1218 termp_bl_post(DECL_ARGS)
1219 {
1220
1221         if (MDOC_BLOCK == n->type)
1222                 term_newln(p);
1223 }
1224
1225
1226 /* ARGSUSED */
1227 static void
1228 termp_op_post(DECL_ARGS)
1229 {
1230
1231         if (MDOC_BODY != n->type)
1232                 return;
1233         p->flags |= TERMP_NOSPACE;
1234         term_word(p, "\\(rB");
1235 }
1236
1237
1238 /* ARGSUSED */
1239 static int
1240 termp_xr_pre(DECL_ARGS)
1241 {
1242         const struct mdoc_node *nn;
1243
1244         assert(n->child && MDOC_TEXT == n->child->type);
1245         nn = n->child;
1246
1247         term_word(p, nn->string);
1248         if (NULL == (nn = nn->next))
1249                 return(0);
1250         p->flags |= TERMP_NOSPACE;
1251         term_word(p, "(");
1252         p->flags |= TERMP_NOSPACE;
1253         term_word(p, nn->string);
1254         p->flags |= TERMP_NOSPACE;
1255         term_word(p, ")");
1256
1257         return(0);
1258 }
1259
1260
1261 /* ARGSUSED */
1262 static void
1263 termp_vt_post(DECL_ARGS)
1264 {
1265
1266         if (n->sec != SEC_SYNOPSIS)
1267                 return;
1268         if (n->next && MDOC_Vt == n->next->tok)
1269                 term_newln(p);
1270         else if (n->next)
1271                 term_vspace(p);
1272 }
1273
1274
1275 /* ARGSUSED */
1276 static int
1277 termp_bold_pre(DECL_ARGS)
1278 {
1279
1280         p->bold++;
1281         return(1);
1282 }
1283
1284
1285 /* ARGSUSED */
1286 static void
1287 termp_fd_post(DECL_ARGS)
1288 {
1289
1290         if (n->sec != SEC_SYNOPSIS)
1291                 return;
1292
1293         term_newln(p);
1294         if (n->next && MDOC_Fd != n->next->tok)
1295                 term_vspace(p);
1296 }
1297
1298
1299 /* ARGSUSED */
1300 static int
1301 termp_sh_pre(DECL_ARGS)
1302 {
1303
1304         /* No vspace between consecutive `Sh' calls. */
1305
1306         switch (n->type) {
1307         case (MDOC_BLOCK):
1308                 if (n->prev && MDOC_Sh == n->prev->tok)
1309                         if (NULL == n->prev->body->child)
1310                                 break;
1311                 term_vspace(p);
1312                 break;
1313         case (MDOC_HEAD):
1314                 p->bold++;
1315                 break;
1316         case (MDOC_BODY):
1317                 p->offset = INDENT;
1318                 break;
1319         default:
1320                 break;
1321         }
1322         return(1);
1323 }
1324
1325
1326 /* ARGSUSED */
1327 static void
1328 termp_sh_post(DECL_ARGS)
1329 {
1330
1331         switch (n->type) {
1332         case (MDOC_HEAD):
1333                 term_newln(p);
1334                 break;
1335         case (MDOC_BODY):
1336                 term_newln(p);
1337                 p->offset = 0;
1338                 break;
1339         default:
1340                 break;
1341         }
1342 }
1343
1344
1345 /* ARGSUSED */
1346 static int
1347 termp_op_pre(DECL_ARGS)
1348 {
1349
1350         switch (n->type) {
1351         case (MDOC_BODY):
1352                 term_word(p, "\\(lB");
1353                 p->flags |= TERMP_NOSPACE;
1354                 break;
1355         default:
1356                 break;
1357         }
1358         return(1);
1359 }
1360
1361
1362 /* ARGSUSED */
1363 static int
1364 termp_bt_pre(DECL_ARGS)
1365 {
1366
1367         term_word(p, "is currently in beta test.");
1368         return(0);
1369 }
1370
1371
1372 /* ARGSUSED */
1373 static void
1374 termp_lb_post(DECL_ARGS)
1375 {
1376
1377         if (SEC_LIBRARY == n->sec)
1378                 term_newln(p);
1379 }
1380
1381
1382 /* ARGSUSED */
1383 static int
1384 termp_ud_pre(DECL_ARGS)
1385 {
1386
1387         term_word(p, "currently under development.");
1388         return(0);
1389 }
1390
1391
1392 /* ARGSUSED */
1393 static int
1394 termp_d1_pre(DECL_ARGS)
1395 {
1396
1397         if (MDOC_BLOCK != n->type)
1398                 return(1);
1399         term_newln(p);
1400         p->offset += (INDENT + 1);
1401         return(1);
1402 }
1403
1404
1405 /* ARGSUSED */
1406 static void
1407 termp_d1_post(DECL_ARGS)
1408 {
1409
1410         if (MDOC_BLOCK != n->type)
1411                 return;
1412         term_newln(p);
1413 }
1414
1415
1416 /* ARGSUSED */
1417 static int
1418 termp_aq_pre(DECL_ARGS)
1419 {
1420
1421         if (MDOC_BODY != n->type)
1422                 return(1);
1423         term_word(p, "\\(la");
1424         p->flags |= TERMP_NOSPACE;
1425         return(1);
1426 }
1427
1428
1429 /* ARGSUSED */
1430 static void
1431 termp_aq_post(DECL_ARGS)
1432 {
1433
1434         if (MDOC_BODY != n->type)
1435                 return;
1436         p->flags |= TERMP_NOSPACE;
1437         term_word(p, "\\(ra");
1438 }
1439
1440
1441 /* ARGSUSED */
1442 static int
1443 termp_ft_pre(DECL_ARGS)
1444 {
1445
1446         if (SEC_SYNOPSIS == n->sec)
1447                 if (n->prev && MDOC_Fo == n->prev->tok)
1448                         term_vspace(p);
1449         p->under++;
1450         return(1);
1451 }
1452
1453
1454 /* ARGSUSED */
1455 static void
1456 termp_ft_post(DECL_ARGS)
1457 {
1458
1459         if (SEC_SYNOPSIS == n->sec)
1460                 term_newln(p);
1461 }
1462
1463
1464 /* ARGSUSED */
1465 static int
1466 termp_fn_pre(DECL_ARGS)
1467 {
1468         const struct mdoc_node  *nn;
1469
1470         p->bold++;
1471         term_word(p, n->child->string);
1472         p->bold--;
1473
1474         p->flags |= TERMP_NOSPACE;
1475         term_word(p, "(");
1476
1477         for (nn = n->child->next; nn; nn = nn->next) {
1478                 p->under++;
1479                 term_word(p, nn->string);
1480                 p->under--;
1481                 if (nn->next)
1482                         term_word(p, ",");
1483         }
1484
1485         term_word(p, ")");
1486
1487         if (SEC_SYNOPSIS == n->sec)
1488                 term_word(p, ";");
1489
1490         return(0);
1491 }
1492
1493
1494 /* ARGSUSED */
1495 static void
1496 termp_fn_post(DECL_ARGS)
1497 {
1498
1499         if (n->sec == SEC_SYNOPSIS && n->next)
1500                 term_vspace(p);
1501 }
1502
1503
1504 /* ARGSUSED */
1505 static int
1506 termp_fa_pre(DECL_ARGS)
1507 {
1508         const struct mdoc_node  *nn;
1509
1510         if (n->parent->tok != MDOC_Fo) {
1511                 p->under++;
1512                 return(1);
1513         }
1514
1515         for (nn = n->child; nn; nn = nn->next) {
1516                 p->under++;
1517                 term_word(p, nn->string);
1518                 p->under--;
1519                 if (nn->next)
1520                         term_word(p, ",");
1521         }
1522
1523         if (n->child && n->next && n->next->tok == MDOC_Fa)
1524                 term_word(p, ",");
1525
1526         return(0);
1527 }
1528
1529
1530 /* ARGSUSED */
1531 static int
1532 termp_bd_pre(DECL_ARGS)
1533 {
1534         int                      i, type;
1535         const struct mdoc_node  *nn;
1536
1537         if (MDOC_BLOCK == n->type) {
1538                 print_bvspace(p, n, n);
1539                 return(1);
1540         } else if (MDOC_BODY != n->type)
1541                 return(1);
1542
1543         nn = n->parent;
1544
1545         for (type = -1, i = 0; i < (int)nn->args->argc; i++) {
1546                 switch (nn->args->argv[i].arg) {
1547                 case (MDOC_Centred):
1548                         /* FALLTHROUGH */
1549                 case (MDOC_Ragged):
1550                         /* FALLTHROUGH */
1551                 case (MDOC_Filled):
1552                         /* FALLTHROUGH */
1553                 case (MDOC_Unfilled):
1554                         /* FALLTHROUGH */
1555                 case (MDOC_Literal):
1556                         type = nn->args->argv[i].arg;
1557                         break;
1558                 case (MDOC_Offset):
1559                         p->offset += arg2offs(&nn->args->argv[i]);
1560                         break;
1561                 default:
1562                         break;
1563                 }
1564         }
1565
1566         /*
1567          * If -ragged or -filled are specified, the block does nothing
1568          * but change the indentation.  If -unfilled or -literal are
1569          * specified, text is printed exactly as entered in the display:
1570          * for macro lines, a newline is appended to the line.  Blank
1571          * lines are allowed.
1572          */
1573
1574         assert(type > -1);
1575         if (MDOC_Literal != type && MDOC_Unfilled != type)
1576                 return(1);
1577
1578         for (nn = n->child; nn; nn = nn->next) {
1579                 p->flags |= TERMP_NOSPACE;
1580                 print_node(p, pair, m, nn);
1581                 if (NULL == nn->next)
1582                         continue;
1583                 if (nn->prev && nn->prev->line < nn->line)
1584                         term_flushln(p);
1585                 else if (NULL == nn->prev)
1586                         term_flushln(p);
1587         }
1588
1589         return(0);
1590 }
1591
1592
1593 /* ARGSUSED */
1594 static void
1595 termp_bd_post(DECL_ARGS)
1596 {
1597
1598         if (MDOC_BODY != n->type)
1599                 return;
1600         p->flags |= TERMP_NOSPACE;
1601         term_flushln(p);
1602 }
1603
1604
1605 /* ARGSUSED */
1606 static int
1607 termp_qq_pre(DECL_ARGS)
1608 {
1609
1610         if (MDOC_BODY != n->type)
1611                 return(1);
1612         term_word(p, "\"");
1613         p->flags |= TERMP_NOSPACE;
1614         return(1);
1615 }
1616
1617
1618 /* ARGSUSED */
1619 static void
1620 termp_qq_post(DECL_ARGS)
1621 {
1622
1623         if (MDOC_BODY != n->type)
1624                 return;
1625         p->flags |= TERMP_NOSPACE;
1626         term_word(p, "\"");
1627 }
1628
1629
1630 /* ARGSUSED */
1631 static void
1632 termp_bx_post(DECL_ARGS)
1633 {
1634
1635         if (n->child)
1636                 p->flags |= TERMP_NOSPACE;
1637         term_word(p, "BSD");
1638 }
1639
1640
1641 /* ARGSUSED */
1642 static int
1643 termp_xx_pre(DECL_ARGS)
1644 {
1645         const char      *pp;
1646
1647         pp = NULL;
1648         switch (n->tok) {
1649         case (MDOC_Bsx):
1650                 pp = "BSDI BSD/OS";
1651                 break;
1652         case (MDOC_Dx):
1653                 pp = "DragonFlyBSD";
1654                 break;
1655         case (MDOC_Fx):
1656                 pp = "FreeBSD";
1657                 break;
1658         case (MDOC_Nx):
1659                 pp = "NetBSD";
1660                 break;
1661         case (MDOC_Ox):
1662                 pp = "OpenBSD";
1663                 break;
1664         case (MDOC_Ux):
1665                 pp = "UNIX";
1666                 break;
1667         default:
1668                 break;
1669         }
1670
1671         assert(pp);
1672         term_word(p, pp);
1673         return(1);
1674 }
1675
1676
1677 /* ARGSUSED */
1678 static int
1679 termp_sq_pre(DECL_ARGS)
1680 {
1681
1682         if (MDOC_BODY != n->type)
1683                 return(1);
1684         term_word(p, "\\(oq");
1685         p->flags |= TERMP_NOSPACE;
1686         return(1);
1687 }
1688
1689
1690 /* ARGSUSED */
1691 static void
1692 termp_sq_post(DECL_ARGS)
1693 {
1694
1695         if (MDOC_BODY != n->type)
1696                 return;
1697         p->flags |= TERMP_NOSPACE;
1698         term_word(p, "\\(aq");
1699 }
1700
1701
1702 /* ARGSUSED */
1703 static int
1704 termp_pf_pre(DECL_ARGS)
1705 {
1706
1707         p->flags |= TERMP_IGNDELIM;
1708         return(1);
1709 }
1710
1711
1712 /* ARGSUSED */
1713 static void
1714 termp_pf_post(DECL_ARGS)
1715 {
1716
1717         p->flags &= ~TERMP_IGNDELIM;
1718         p->flags |= TERMP_NOSPACE;
1719 }
1720
1721
1722 /* ARGSUSED */
1723 static int
1724 termp_ss_pre(DECL_ARGS)
1725 {
1726
1727         switch (n->type) {
1728         case (MDOC_BLOCK):
1729                 term_newln(p);
1730                 if (n->prev)
1731                         term_vspace(p);
1732                 break;
1733         case (MDOC_HEAD):
1734                 p->bold++;
1735                 p->offset = HALFINDENT;
1736                 break;
1737         default:
1738                 break;
1739         }
1740
1741         return(1);
1742 }
1743
1744
1745 /* ARGSUSED */
1746 static void
1747 termp_ss_post(DECL_ARGS)
1748 {
1749
1750         if (MDOC_HEAD == n->type)
1751                 term_newln(p);
1752 }
1753
1754
1755 /* ARGSUSED */
1756 static int
1757 termp_cd_pre(DECL_ARGS)
1758 {
1759
1760         p->bold++;
1761         term_newln(p);
1762         return(1);
1763 }
1764
1765
1766 /* ARGSUSED */
1767 static int
1768 termp_in_pre(DECL_ARGS)
1769 {
1770
1771         p->bold++;
1772         if (SEC_SYNOPSIS == n->sec)
1773                 term_word(p, "#include");
1774
1775         term_word(p, "<");
1776         p->flags |= TERMP_NOSPACE;
1777         return(1);
1778 }
1779
1780
1781 /* ARGSUSED */
1782 static void
1783 termp_in_post(DECL_ARGS)
1784 {
1785
1786         p->bold++;
1787         p->flags |= TERMP_NOSPACE;
1788         term_word(p, ">");
1789         p->bold--;
1790
1791         if (SEC_SYNOPSIS != n->sec)
1792                 return;
1793
1794         term_newln(p);
1795         /*
1796          * XXX Not entirely correct.  If `.In foo bar' is specified in
1797          * the SYNOPSIS section, then it produces a single break after
1798          * the <foo>; mandoc asserts a vertical space.  Since this
1799          * construction is rarely used, I think it's fine.
1800          */
1801         if (n->next && MDOC_In != n->next->tok)
1802                 term_vspace(p);
1803 }
1804
1805
1806 /* ARGSUSED */
1807 static int
1808 termp_sp_pre(DECL_ARGS)
1809 {
1810         size_t           i, len;
1811
1812         switch (n->tok) {
1813         case (MDOC_sp):
1814                 len = n->child ? arg2height(n->child) : 1;
1815                 break;
1816         case (MDOC_br):
1817                 len = 0;
1818                 break;
1819         default:
1820                 len = 1;
1821                 break;
1822         }
1823
1824         if (0 == len)
1825                 term_newln(p);
1826         for (i = 0; i < len; i++)
1827                 term_vspace(p);
1828
1829         return(0);
1830 }
1831
1832
1833 /* ARGSUSED */
1834 static int
1835 termp_brq_pre(DECL_ARGS)
1836 {
1837
1838         if (MDOC_BODY != n->type)
1839                 return(1);
1840         term_word(p, "\\(lC");
1841         p->flags |= TERMP_NOSPACE;
1842         return(1);
1843 }
1844
1845
1846 /* ARGSUSED */
1847 static void
1848 termp_brq_post(DECL_ARGS)
1849 {
1850
1851         if (MDOC_BODY != n->type)
1852                 return;
1853         p->flags |= TERMP_NOSPACE;
1854         term_word(p, "\\(rC");
1855 }
1856
1857
1858 /* ARGSUSED */
1859 static int
1860 termp_bq_pre(DECL_ARGS)
1861 {
1862
1863         if (MDOC_BODY != n->type)
1864                 return(1);
1865         term_word(p, "\\(lB");
1866         p->flags |= TERMP_NOSPACE;
1867         return(1);
1868 }
1869
1870
1871 /* ARGSUSED */
1872 static void
1873 termp_bq_post(DECL_ARGS)
1874 {
1875
1876         if (MDOC_BODY != n->type)
1877                 return;
1878         p->flags |= TERMP_NOSPACE;
1879         term_word(p, "\\(rB");
1880 }
1881
1882
1883 /* ARGSUSED */
1884 static int
1885 termp_pq_pre(DECL_ARGS)
1886 {
1887
1888         if (MDOC_BODY != n->type)
1889                 return(1);
1890         term_word(p, "\\&(");
1891         p->flags |= TERMP_NOSPACE;
1892         return(1);
1893 }
1894
1895
1896 /* ARGSUSED */
1897 static void
1898 termp_pq_post(DECL_ARGS)
1899 {
1900
1901         if (MDOC_BODY != n->type)
1902                 return;
1903         term_word(p, ")");
1904 }
1905
1906
1907 /* ARGSUSED */
1908 static int
1909 termp_fo_pre(DECL_ARGS)
1910 {
1911         const struct mdoc_node *nn;
1912
1913         if (MDOC_BODY == n->type) {
1914                 p->flags |= TERMP_NOSPACE;
1915                 term_word(p, "(");
1916                 p->flags |= TERMP_NOSPACE;
1917                 return(1);
1918         } else if (MDOC_HEAD != n->type)
1919                 return(1);
1920
1921         p->bold++;
1922         for (nn = n->child; nn; nn = nn->next) {
1923                 assert(MDOC_TEXT == nn->type);
1924                 term_word(p, nn->string);
1925         }
1926         p->bold--;
1927
1928         return(0);
1929 }
1930
1931
1932 /* ARGSUSED */
1933 static void
1934 termp_fo_post(DECL_ARGS)
1935 {
1936
1937         if (MDOC_BODY != n->type)
1938                 return;
1939         p->flags |= TERMP_NOSPACE;
1940         term_word(p, ")");
1941         p->flags |= TERMP_NOSPACE;
1942         term_word(p, ";");
1943         term_newln(p);
1944 }
1945
1946
1947 /* ARGSUSED */
1948 static int
1949 termp_bf_pre(DECL_ARGS)
1950 {
1951         const struct mdoc_node  *nn;
1952
1953         if (MDOC_HEAD == n->type)
1954                 return(0);
1955         else if (MDOC_BLOCK != n->type)
1956                 return(1);
1957
1958         if (NULL == (nn = n->head->child)) {
1959                 if (arg_hasattr(MDOC_Emphasis, n))
1960                         p->under++;
1961                 else if (arg_hasattr(MDOC_Symbolic, n))
1962                         p->bold++;
1963
1964                 return(1);
1965         }
1966
1967         assert(MDOC_TEXT == nn->type);
1968         if (0 == strcmp("Em", nn->string))
1969                 p->under++;
1970         else if (0 == strcmp("Sy", nn->string))
1971                 p->bold++;
1972
1973         return(1);
1974 }
1975
1976
1977 /* ARGSUSED */
1978 static int
1979 termp_sm_pre(DECL_ARGS)
1980 {
1981
1982         assert(n->child && MDOC_TEXT == n->child->type);
1983         if (0 == strcmp("on", n->child->string)) {
1984                 p->flags &= ~TERMP_NONOSPACE;
1985                 p->flags &= ~TERMP_NOSPACE;
1986         } else
1987                 p->flags |= TERMP_NONOSPACE;
1988
1989         return(0);
1990 }
1991
1992
1993 /* ARGSUSED */
1994 static int
1995 termp_ap_pre(DECL_ARGS)
1996 {
1997
1998         p->flags |= TERMP_NOSPACE;
1999         term_word(p, "\\(aq");
2000         p->flags |= TERMP_NOSPACE;
2001         return(1);
2002 }
2003
2004
2005 /* ARGSUSED */
2006 static void
2007 termp____post(DECL_ARGS)
2008 {
2009
2010         p->flags |= TERMP_NOSPACE;
2011         switch (n->tok) {
2012         case (MDOC__T):
2013                 term_word(p, "\\(rq");
2014                 p->flags |= TERMP_NOSPACE;
2015                 break;
2016         default:
2017                 break;
2018         }
2019         term_word(p, n->next ? "," : ".");
2020 }
2021
2022
2023 /* ARGSUSED */
2024 static int
2025 termp_lk_pre(DECL_ARGS)
2026 {
2027         const struct mdoc_node *nn;
2028
2029         if (NULL == (nn = n->child->next)) {
2030                 p->under++;
2031                 return(1);
2032         }
2033
2034         p->under++;
2035         term_word(p, nn->string);
2036         p->flags |= TERMP_NOSPACE;
2037         term_word(p, ":");
2038         p->under--;
2039
2040         p->bold++;
2041         for (nn = nn->next; nn; nn = nn->next)
2042                 term_word(p, nn->string);
2043         p->bold--;
2044
2045         return(0);
2046 }
2047
2048
2049 /* ARGSUSED */
2050 static int
2051 termp_under_pre(DECL_ARGS)
2052 {
2053
2054         p->under++;
2055         return(1);
2056 }
2057
2058
2059 /* ARGSUSED */
2060 static int
2061 termp__t_pre(DECL_ARGS)
2062 {
2063
2064         term_word(p, "\\(lq");
2065         p->flags |= TERMP_NOSPACE;
2066         return(1);
2067 }