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