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