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