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