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