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