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