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