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