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