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