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