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