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