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