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