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