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