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