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