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