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