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