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