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