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