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