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