]> git.cameronkatri.com Git - mandoc.git/blob - mdoc_term.c
Vastly simplify man(7) block unwinding, similar to mdoc_macro.c 1.171.
[mandoc.git] / mdoc_term.c
1 /* $Id: mdoc_term.c,v 1.316 2015/04/02 23:48:20 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 "main.h"
38
39 struct termpair {
40 struct termpair *ppair;
41 int count;
42 };
43
44 #define DECL_ARGS struct termp *p, \
45 struct termpair *pair, \
46 const struct roff_meta *meta, \
47 struct roff_node *n
48
49 struct termact {
50 int (*pre)(DECL_ARGS);
51 void (*post)(DECL_ARGS);
52 };
53
54 static int a2width(const struct termp *, const char *);
55
56 static void print_bvspace(struct termp *,
57 const struct roff_node *,
58 const struct roff_node *);
59 static void print_mdoc_node(DECL_ARGS);
60 static void print_mdoc_nodelist(DECL_ARGS);
61 static void print_mdoc_head(struct termp *, const struct roff_meta *);
62 static void print_mdoc_foot(struct termp *, const struct roff_meta *);
63 static void synopsis_pre(struct termp *,
64 const struct roff_node *);
65
66 static void termp____post(DECL_ARGS);
67 static void termp__t_post(DECL_ARGS);
68 static void termp_bd_post(DECL_ARGS);
69 static void termp_bk_post(DECL_ARGS);
70 static void termp_bl_post(DECL_ARGS);
71 static void termp_eo_post(DECL_ARGS);
72 static void termp_fd_post(DECL_ARGS);
73 static void termp_fo_post(DECL_ARGS);
74 static void termp_in_post(DECL_ARGS);
75 static void termp_it_post(DECL_ARGS);
76 static void termp_lb_post(DECL_ARGS);
77 static void termp_nm_post(DECL_ARGS);
78 static void termp_pf_post(DECL_ARGS);
79 static void termp_quote_post(DECL_ARGS);
80 static void termp_sh_post(DECL_ARGS);
81 static void termp_ss_post(DECL_ARGS);
82
83 static int termp__a_pre(DECL_ARGS);
84 static int termp__t_pre(DECL_ARGS);
85 static int termp_an_pre(DECL_ARGS);
86 static int termp_ap_pre(DECL_ARGS);
87 static int termp_bd_pre(DECL_ARGS);
88 static int termp_bf_pre(DECL_ARGS);
89 static int termp_bk_pre(DECL_ARGS);
90 static int termp_bl_pre(DECL_ARGS);
91 static int termp_bold_pre(DECL_ARGS);
92 static int termp_bt_pre(DECL_ARGS);
93 static int termp_bx_pre(DECL_ARGS);
94 static int termp_cd_pre(DECL_ARGS);
95 static int termp_d1_pre(DECL_ARGS);
96 static int termp_eo_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_skip_pre(DECL_ARGS);
117 static int termp_sm_pre(DECL_ARGS);
118 static int termp_sp_pre(DECL_ARGS);
119 static int termp_ss_pre(DECL_ARGS);
120 static int termp_under_pre(DECL_ARGS);
121 static int termp_ud_pre(DECL_ARGS);
122 static int termp_vt_pre(DECL_ARGS);
123 static int termp_xr_pre(DECL_ARGS);
124 static int termp_xx_pre(DECL_ARGS);
125
126 static const struct termact termacts[MDOC_MAX] = {
127 { termp_ap_pre, NULL }, /* Ap */
128 { NULL, NULL }, /* Dd */
129 { NULL, NULL }, /* Dt */
130 { NULL, NULL }, /* Os */
131 { termp_sh_pre, termp_sh_post }, /* Sh */
132 { termp_ss_pre, termp_ss_post }, /* Ss */
133 { termp_sp_pre, NULL }, /* Pp */
134 { termp_d1_pre, termp_bl_post }, /* D1 */
135 { termp_d1_pre, termp_bl_post }, /* Dl */
136 { termp_bd_pre, termp_bd_post }, /* Bd */
137 { NULL, NULL }, /* Ed */
138 { termp_bl_pre, termp_bl_post }, /* Bl */
139 { NULL, NULL }, /* El */
140 { termp_it_pre, termp_it_post }, /* It */
141 { termp_under_pre, NULL }, /* Ad */
142 { termp_an_pre, NULL }, /* An */
143 { termp_under_pre, NULL }, /* Ar */
144 { termp_cd_pre, NULL }, /* Cd */
145 { termp_bold_pre, NULL }, /* Cm */
146 { NULL, NULL }, /* Dv */
147 { NULL, NULL }, /* Er */
148 { NULL, NULL }, /* Ev */
149 { termp_ex_pre, NULL }, /* Ex */
150 { termp_fa_pre, NULL }, /* Fa */
151 { termp_fd_pre, termp_fd_post }, /* Fd */
152 { termp_fl_pre, NULL }, /* Fl */
153 { termp_fn_pre, NULL }, /* Fn */
154 { termp_ft_pre, NULL }, /* Ft */
155 { termp_bold_pre, NULL }, /* Ic */
156 { termp_in_pre, termp_in_post }, /* In */
157 { termp_li_pre, NULL }, /* Li */
158 { termp_nd_pre, NULL }, /* Nd */
159 { termp_nm_pre, termp_nm_post }, /* Nm */
160 { termp_quote_pre, termp_quote_post }, /* Op */
161 { termp_ft_pre, NULL }, /* Ot */
162 { termp_under_pre, NULL }, /* Pa */
163 { termp_rv_pre, NULL }, /* Rv */
164 { NULL, NULL }, /* St */
165 { termp_under_pre, NULL }, /* Va */
166 { termp_vt_pre, NULL }, /* Vt */
167 { termp_xr_pre, NULL }, /* Xr */
168 { termp__a_pre, termp____post }, /* %A */
169 { termp_under_pre, termp____post }, /* %B */
170 { NULL, termp____post }, /* %D */
171 { termp_under_pre, termp____post }, /* %I */
172 { termp_under_pre, termp____post }, /* %J */
173 { NULL, termp____post }, /* %N */
174 { NULL, termp____post }, /* %O */
175 { NULL, termp____post }, /* %P */
176 { NULL, termp____post }, /* %R */
177 { termp__t_pre, termp__t_post }, /* %T */
178 { NULL, termp____post }, /* %V */
179 { NULL, NULL }, /* Ac */
180 { termp_quote_pre, termp_quote_post }, /* Ao */
181 { termp_quote_pre, termp_quote_post }, /* Aq */
182 { NULL, NULL }, /* At */
183 { NULL, NULL }, /* Bc */
184 { termp_bf_pre, NULL }, /* Bf */
185 { termp_quote_pre, termp_quote_post }, /* Bo */
186 { termp_quote_pre, termp_quote_post }, /* Bq */
187 { termp_xx_pre, NULL }, /* Bsx */
188 { termp_bx_pre, NULL }, /* Bx */
189 { termp_skip_pre, NULL }, /* Db */
190 { NULL, NULL }, /* Dc */
191 { termp_quote_pre, termp_quote_post }, /* Do */
192 { termp_quote_pre, termp_quote_post }, /* Dq */
193 { NULL, NULL }, /* Ec */ /* FIXME: no space */
194 { NULL, NULL }, /* Ef */
195 { termp_under_pre, NULL }, /* Em */
196 { termp_eo_pre, termp_eo_post }, /* Eo */
197 { termp_xx_pre, NULL }, /* Fx */
198 { termp_bold_pre, NULL }, /* Ms */
199 { termp_li_pre, NULL }, /* No */
200 { termp_ns_pre, NULL }, /* Ns */
201 { termp_xx_pre, NULL }, /* Nx */
202 { termp_xx_pre, NULL }, /* Ox */
203 { NULL, NULL }, /* Pc */
204 { NULL, termp_pf_post }, /* Pf */
205 { termp_quote_pre, termp_quote_post }, /* Po */
206 { termp_quote_pre, termp_quote_post }, /* Pq */
207 { NULL, NULL }, /* Qc */
208 { termp_quote_pre, termp_quote_post }, /* Ql */
209 { termp_quote_pre, termp_quote_post }, /* Qo */
210 { termp_quote_pre, termp_quote_post }, /* Qq */
211 { NULL, NULL }, /* Re */
212 { termp_rs_pre, NULL }, /* Rs */
213 { NULL, NULL }, /* Sc */
214 { termp_quote_pre, termp_quote_post }, /* So */
215 { termp_quote_pre, termp_quote_post }, /* Sq */
216 { termp_sm_pre, NULL }, /* Sm */
217 { termp_under_pre, NULL }, /* Sx */
218 { termp_bold_pre, NULL }, /* Sy */
219 { NULL, NULL }, /* Tn */
220 { termp_xx_pre, NULL }, /* Ux */
221 { NULL, NULL }, /* Xc */
222 { NULL, NULL }, /* Xo */
223 { termp_fo_pre, termp_fo_post }, /* Fo */
224 { NULL, NULL }, /* Fc */
225 { termp_quote_pre, termp_quote_post }, /* Oo */
226 { NULL, NULL }, /* Oc */
227 { termp_bk_pre, termp_bk_post }, /* Bk */
228 { NULL, NULL }, /* Ek */
229 { termp_bt_pre, NULL }, /* Bt */
230 { NULL, NULL }, /* Hf */
231 { termp_under_pre, NULL }, /* Fr */
232 { termp_ud_pre, NULL }, /* Ud */
233 { NULL, termp_lb_post }, /* Lb */
234 { termp_sp_pre, NULL }, /* Lp */
235 { termp_lk_pre, NULL }, /* Lk */
236 { termp_under_pre, NULL }, /* Mt */
237 { termp_quote_pre, termp_quote_post }, /* Brq */
238 { termp_quote_pre, termp_quote_post }, /* Bro */
239 { NULL, NULL }, /* Brc */
240 { NULL, termp____post }, /* %C */
241 { termp_skip_pre, NULL }, /* Es */
242 { termp_quote_pre, termp_quote_post }, /* En */
243 { termp_xx_pre, NULL }, /* Dx */
244 { NULL, termp____post }, /* %Q */
245 { termp_sp_pre, NULL }, /* br */
246 { termp_sp_pre, NULL }, /* sp */
247 { NULL, termp____post }, /* %U */
248 { NULL, NULL }, /* Ta */
249 { termp_ll_pre, NULL }, /* ll */
250 };
251
252
253 void
254 terminal_mdoc(void *arg, const struct mdoc *mdoc)
255 {
256 const struct roff_meta *meta;
257 struct roff_node *n;
258 struct termp *p;
259
260 p = (struct termp *)arg;
261
262 p->overstep = 0;
263 p->rmargin = p->maxrmargin = p->defrmargin;
264 p->tabwidth = term_len(p, 5);
265
266 n = mdoc_node(mdoc)->child;
267 meta = mdoc_meta(mdoc);
268
269 if (p->synopsisonly) {
270 while (n != NULL) {
271 if (n->tok == MDOC_Sh && n->sec == SEC_SYNOPSIS) {
272 if (n->child->next->child != NULL)
273 print_mdoc_nodelist(p, NULL,
274 meta, n->child->next->child);
275 term_newln(p);
276 break;
277 }
278 n = n->next;
279 }
280 } else {
281 if (p->defindent == 0)
282 p->defindent = 5;
283 term_begin(p, print_mdoc_head, print_mdoc_foot, meta);
284 if (n != NULL) {
285 if (n->tok != MDOC_Sh)
286 term_vspace(p);
287 print_mdoc_nodelist(p, NULL, 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));
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 term_fontpush(p, TERMFONT_BOLD);
1055 term_word(p, "\\-");
1056
1057 if ( ! (n->nchild == 0 &&
1058 (n->next == NULL ||
1059 n->next->type == ROFFT_TEXT ||
1060 n->next->flags & MDOC_LINE)))
1061 p->flags |= TERMP_NOSPACE;
1062
1063 return(1);
1064 }
1065
1066 static int
1067 termp__a_pre(DECL_ARGS)
1068 {
1069
1070 if (n->prev && MDOC__A == n->prev->tok)
1071 if (NULL == n->next || MDOC__A != n->next->tok)
1072 term_word(p, "and");
1073
1074 return(1);
1075 }
1076
1077 static int
1078 termp_an_pre(DECL_ARGS)
1079 {
1080
1081 if (n->norm->An.auth == AUTH_split) {
1082 p->flags &= ~TERMP_NOSPLIT;
1083 p->flags |= TERMP_SPLIT;
1084 return(0);
1085 }
1086 if (n->norm->An.auth == AUTH_nosplit) {
1087 p->flags &= ~TERMP_SPLIT;
1088 p->flags |= TERMP_NOSPLIT;
1089 return(0);
1090 }
1091
1092 if (p->flags & TERMP_SPLIT)
1093 term_newln(p);
1094
1095 if (n->sec == SEC_AUTHORS && ! (p->flags & TERMP_NOSPLIT))
1096 p->flags |= TERMP_SPLIT;
1097
1098 return(1);
1099 }
1100
1101 static int
1102 termp_ns_pre(DECL_ARGS)
1103 {
1104
1105 if ( ! (MDOC_LINE & n->flags))
1106 p->flags |= TERMP_NOSPACE;
1107 return(1);
1108 }
1109
1110 static int
1111 termp_rs_pre(DECL_ARGS)
1112 {
1113
1114 if (SEC_SEE_ALSO != n->sec)
1115 return(1);
1116 if (n->type == ROFFT_BLOCK && n->prev != NULL)
1117 term_vspace(p);
1118 return(1);
1119 }
1120
1121 static int
1122 termp_rv_pre(DECL_ARGS)
1123 {
1124 int nchild;
1125
1126 term_newln(p);
1127
1128 nchild = n->nchild;
1129 if (nchild > 0) {
1130 term_word(p, "The");
1131
1132 for (n = n->child; n; n = n->next) {
1133 term_fontpush(p, TERMFONT_BOLD);
1134 term_word(p, n->string);
1135 term_fontpop(p);
1136
1137 p->flags |= TERMP_NOSPACE;
1138 term_word(p, "()");
1139
1140 if (n->next == NULL)
1141 continue;
1142
1143 if (nchild > 2) {
1144 p->flags |= TERMP_NOSPACE;
1145 term_word(p, ",");
1146 }
1147 if (n->next->next == NULL)
1148 term_word(p, "and");
1149 }
1150
1151 if (nchild > 1)
1152 term_word(p, "functions return");
1153 else
1154 term_word(p, "function returns");
1155
1156 term_word(p, "the value\\~0 if successful;");
1157 } else
1158 term_word(p, "Upon successful completion,"
1159 " the value\\~0 is returned;");
1160
1161 term_word(p, "otherwise the value\\~\\-1 is returned"
1162 " and the global variable");
1163
1164 term_fontpush(p, TERMFONT_UNDER);
1165 term_word(p, "errno");
1166 term_fontpop(p);
1167
1168 term_word(p, "is set to indicate the error.");
1169 p->flags |= TERMP_SENTENCE;
1170
1171 return(0);
1172 }
1173
1174 static int
1175 termp_ex_pre(DECL_ARGS)
1176 {
1177 int nchild;
1178
1179 term_newln(p);
1180 term_word(p, "The");
1181
1182 nchild = n->nchild;
1183 for (n = n->child; n; n = n->next) {
1184 term_fontpush(p, TERMFONT_BOLD);
1185 term_word(p, n->string);
1186 term_fontpop(p);
1187
1188 if (nchild > 2 && n->next) {
1189 p->flags |= TERMP_NOSPACE;
1190 term_word(p, ",");
1191 }
1192
1193 if (n->next && NULL == n->next->next)
1194 term_word(p, "and");
1195 }
1196
1197 if (nchild > 1)
1198 term_word(p, "utilities exit\\~0");
1199 else
1200 term_word(p, "utility exits\\~0");
1201
1202 term_word(p, "on success, and\\~>0 if an error occurs.");
1203
1204 p->flags |= TERMP_SENTENCE;
1205 return(0);
1206 }
1207
1208 static int
1209 termp_nd_pre(DECL_ARGS)
1210 {
1211
1212 if (n->type == ROFFT_BODY)
1213 term_word(p, "\\(en");
1214 return(1);
1215 }
1216
1217 static int
1218 termp_bl_pre(DECL_ARGS)
1219 {
1220
1221 return(n->type != ROFFT_HEAD);
1222 }
1223
1224 static void
1225 termp_bl_post(DECL_ARGS)
1226 {
1227
1228 if (n->type == ROFFT_BLOCK)
1229 term_newln(p);
1230 }
1231
1232 static int
1233 termp_xr_pre(DECL_ARGS)
1234 {
1235
1236 if (NULL == (n = n->child))
1237 return(0);
1238
1239 assert(n->type == ROFFT_TEXT);
1240 term_word(p, n->string);
1241
1242 if (NULL == (n = n->next))
1243 return(0);
1244
1245 p->flags |= TERMP_NOSPACE;
1246 term_word(p, "(");
1247 p->flags |= TERMP_NOSPACE;
1248
1249 assert(n->type == ROFFT_TEXT);
1250 term_word(p, n->string);
1251
1252 p->flags |= TERMP_NOSPACE;
1253 term_word(p, ")");
1254
1255 return(0);
1256 }
1257
1258 /*
1259 * This decides how to assert whitespace before any of the SYNOPSIS set
1260 * of macros (which, as in the case of Ft/Fo and Ft/Fn, may contain
1261 * macro combos).
1262 */
1263 static void
1264 synopsis_pre(struct termp *p, const struct roff_node *n)
1265 {
1266 /*
1267 * Obviously, if we're not in a SYNOPSIS or no prior macros
1268 * exist, do nothing.
1269 */
1270 if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags))
1271 return;
1272
1273 /*
1274 * If we're the second in a pair of like elements, emit our
1275 * newline and return. UNLESS we're `Fo', `Fn', `Fn', in which
1276 * case we soldier on.
1277 */
1278 if (n->prev->tok == n->tok &&
1279 MDOC_Ft != n->tok &&
1280 MDOC_Fo != n->tok &&
1281 MDOC_Fn != n->tok) {
1282 term_newln(p);
1283 return;
1284 }
1285
1286 /*
1287 * If we're one of the SYNOPSIS set and non-like pair-wise after
1288 * another (or Fn/Fo, which we've let slip through) then assert
1289 * vertical space, else only newline and move on.
1290 */
1291 switch (n->prev->tok) {
1292 case MDOC_Fd:
1293 /* FALLTHROUGH */
1294 case MDOC_Fn:
1295 /* FALLTHROUGH */
1296 case MDOC_Fo:
1297 /* FALLTHROUGH */
1298 case MDOC_In:
1299 /* FALLTHROUGH */
1300 case MDOC_Vt:
1301 term_vspace(p);
1302 break;
1303 case MDOC_Ft:
1304 if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) {
1305 term_vspace(p);
1306 break;
1307 }
1308 /* FALLTHROUGH */
1309 default:
1310 term_newln(p);
1311 break;
1312 }
1313 }
1314
1315 static int
1316 termp_vt_pre(DECL_ARGS)
1317 {
1318
1319 if (n->type == ROFFT_ELEM) {
1320 synopsis_pre(p, n);
1321 return(termp_under_pre(p, pair, meta, n));
1322 } else if (n->type == ROFFT_BLOCK) {
1323 synopsis_pre(p, n);
1324 return(1);
1325 } else if (n->type == ROFFT_HEAD)
1326 return(0);
1327
1328 return(termp_under_pre(p, pair, meta, n));
1329 }
1330
1331 static int
1332 termp_bold_pre(DECL_ARGS)
1333 {
1334
1335 term_fontpush(p, TERMFONT_BOLD);
1336 return(1);
1337 }
1338
1339 static int
1340 termp_fd_pre(DECL_ARGS)
1341 {
1342
1343 synopsis_pre(p, n);
1344 return(termp_bold_pre(p, pair, meta, n));
1345 }
1346
1347 static void
1348 termp_fd_post(DECL_ARGS)
1349 {
1350
1351 term_newln(p);
1352 }
1353
1354 static int
1355 termp_sh_pre(DECL_ARGS)
1356 {
1357
1358 switch (n->type) {
1359 case ROFFT_BLOCK:
1360 /*
1361 * Vertical space before sections, except
1362 * when the previous section was empty.
1363 */
1364 if (n->prev == NULL ||
1365 MDOC_Sh != n->prev->tok ||
1366 (n->prev->body != NULL &&
1367 n->prev->body->child != NULL))
1368 term_vspace(p);
1369 break;
1370 case ROFFT_HEAD:
1371 term_fontpush(p, TERMFONT_BOLD);
1372 break;
1373 case ROFFT_BODY:
1374 p->offset = term_len(p, p->defindent);
1375 if (SEC_AUTHORS == n->sec)
1376 p->flags &= ~(TERMP_SPLIT|TERMP_NOSPLIT);
1377 break;
1378 default:
1379 break;
1380 }
1381 return(1);
1382 }
1383
1384 static void
1385 termp_sh_post(DECL_ARGS)
1386 {
1387
1388 switch (n->type) {
1389 case ROFFT_HEAD:
1390 term_newln(p);
1391 break;
1392 case ROFFT_BODY:
1393 term_newln(p);
1394 p->offset = 0;
1395 break;
1396 default:
1397 break;
1398 }
1399 }
1400
1401 static int
1402 termp_bt_pre(DECL_ARGS)
1403 {
1404
1405 term_word(p, "is currently in beta test.");
1406 p->flags |= TERMP_SENTENCE;
1407 return(0);
1408 }
1409
1410 static void
1411 termp_lb_post(DECL_ARGS)
1412 {
1413
1414 if (SEC_LIBRARY == n->sec && MDOC_LINE & n->flags)
1415 term_newln(p);
1416 }
1417
1418 static int
1419 termp_ud_pre(DECL_ARGS)
1420 {
1421
1422 term_word(p, "currently under development.");
1423 p->flags |= TERMP_SENTENCE;
1424 return(0);
1425 }
1426
1427 static int
1428 termp_d1_pre(DECL_ARGS)
1429 {
1430
1431 if (n->type != ROFFT_BLOCK)
1432 return(1);
1433 term_newln(p);
1434 p->offset += term_len(p, p->defindent + 1);
1435 return(1);
1436 }
1437
1438 static int
1439 termp_ft_pre(DECL_ARGS)
1440 {
1441
1442 /* NB: MDOC_LINE does not effect this! */
1443 synopsis_pre(p, n);
1444 term_fontpush(p, TERMFONT_UNDER);
1445 return(1);
1446 }
1447
1448 static int
1449 termp_fn_pre(DECL_ARGS)
1450 {
1451 size_t rmargin = 0;
1452 int pretty;
1453
1454 pretty = MDOC_SYNPRETTY & n->flags;
1455
1456 synopsis_pre(p, n);
1457
1458 if (NULL == (n = n->child))
1459 return(0);
1460
1461 if (pretty) {
1462 rmargin = p->rmargin;
1463 p->rmargin = p->offset + term_len(p, 4);
1464 p->flags |= TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG;
1465 }
1466
1467 assert(n->type == ROFFT_TEXT);
1468 term_fontpush(p, TERMFONT_BOLD);
1469 term_word(p, n->string);
1470 term_fontpop(p);
1471
1472 if (pretty) {
1473 term_flushln(p);
1474 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG);
1475 p->offset = p->rmargin;
1476 p->rmargin = rmargin;
1477 }
1478
1479 p->flags |= TERMP_NOSPACE;
1480 term_word(p, "(");
1481 p->flags |= TERMP_NOSPACE;
1482
1483 for (n = n->next; n; n = n->next) {
1484 assert(n->type == ROFFT_TEXT);
1485 term_fontpush(p, TERMFONT_UNDER);
1486 if (pretty)
1487 p->flags |= TERMP_NBRWORD;
1488 term_word(p, n->string);
1489 term_fontpop(p);
1490
1491 if (n->next) {
1492 p->flags |= TERMP_NOSPACE;
1493 term_word(p, ",");
1494 }
1495 }
1496
1497 p->flags |= TERMP_NOSPACE;
1498 term_word(p, ")");
1499
1500 if (pretty) {
1501 p->flags |= TERMP_NOSPACE;
1502 term_word(p, ";");
1503 term_flushln(p);
1504 }
1505
1506 return(0);
1507 }
1508
1509 static int
1510 termp_fa_pre(DECL_ARGS)
1511 {
1512 const struct roff_node *nn;
1513
1514 if (n->parent->tok != MDOC_Fo) {
1515 term_fontpush(p, TERMFONT_UNDER);
1516 return(1);
1517 }
1518
1519 for (nn = n->child; nn; nn = nn->next) {
1520 term_fontpush(p, TERMFONT_UNDER);
1521 p->flags |= TERMP_NBRWORD;
1522 term_word(p, nn->string);
1523 term_fontpop(p);
1524
1525 if (nn->next || (n->next && n->next->tok == MDOC_Fa)) {
1526 p->flags |= TERMP_NOSPACE;
1527 term_word(p, ",");
1528 }
1529 }
1530
1531 return(0);
1532 }
1533
1534 static int
1535 termp_bd_pre(DECL_ARGS)
1536 {
1537 size_t tabwidth, lm, len, rm, rmax;
1538 struct roff_node *nn;
1539 int offset;
1540
1541 if (n->type == ROFFT_BLOCK) {
1542 print_bvspace(p, n, n);
1543 return(1);
1544 } else if (n->type == ROFFT_HEAD)
1545 return(0);
1546
1547 /* Handle the -offset argument. */
1548
1549 if (n->norm->Bd.offs == NULL ||
1550 ! strcmp(n->norm->Bd.offs, "left"))
1551 /* nothing */;
1552 else if ( ! strcmp(n->norm->Bd.offs, "indent"))
1553 p->offset += term_len(p, p->defindent + 1);
1554 else if ( ! strcmp(n->norm->Bd.offs, "indent-two"))
1555 p->offset += term_len(p, (p->defindent + 1) * 2);
1556 else {
1557 offset = a2width(p, n->norm->Bd.offs);
1558 if (offset < 0 && (size_t)(-offset) > p->offset)
1559 p->offset = 0;
1560 else if (offset < SHRT_MAX)
1561 p->offset += offset;
1562 }
1563
1564 /*
1565 * If -ragged or -filled are specified, the block does nothing
1566 * but change the indentation. If -unfilled or -literal are
1567 * specified, text is printed exactly as entered in the display:
1568 * for macro lines, a newline is appended to the line. Blank
1569 * lines are allowed.
1570 */
1571
1572 if (DISP_literal != n->norm->Bd.type &&
1573 DISP_unfilled != n->norm->Bd.type &&
1574 DISP_centered != n->norm->Bd.type)
1575 return(1);
1576
1577 tabwidth = p->tabwidth;
1578 if (DISP_literal == n->norm->Bd.type)
1579 p->tabwidth = term_len(p, 8);
1580
1581 lm = p->offset;
1582 rm = p->rmargin;
1583 rmax = p->maxrmargin;
1584 p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
1585
1586 for (nn = n->child; nn; nn = nn->next) {
1587 if (DISP_centered == n->norm->Bd.type) {
1588 if (nn->type == ROFFT_TEXT) {
1589 len = term_strlen(p, nn->string);
1590 p->offset = len >= rm ? 0 :
1591 lm + len >= rm ? rm - len :
1592 (lm + rm - len) / 2;
1593 } else
1594 p->offset = lm;
1595 }
1596 print_mdoc_node(p, pair, meta, nn);
1597 /*
1598 * If the printed node flushes its own line, then we
1599 * needn't do it here as well. This is hacky, but the
1600 * notion of selective eoln whitespace is pretty dumb
1601 * anyway, so don't sweat it.
1602 */
1603 switch (nn->tok) {
1604 case MDOC_Sm:
1605 /* FALLTHROUGH */
1606 case MDOC_br:
1607 /* FALLTHROUGH */
1608 case MDOC_sp:
1609 /* FALLTHROUGH */
1610 case MDOC_Bl:
1611 /* FALLTHROUGH */
1612 case MDOC_D1:
1613 /* FALLTHROUGH */
1614 case MDOC_Dl:
1615 /* FALLTHROUGH */
1616 case MDOC_Lp:
1617 /* FALLTHROUGH */
1618 case MDOC_Pp:
1619 continue;
1620 default:
1621 break;
1622 }
1623 if (p->flags & TERMP_NONEWLINE ||
1624 (nn->next && ! (nn->next->flags & MDOC_LINE)))
1625 continue;
1626 term_flushln(p);
1627 p->flags |= TERMP_NOSPACE;
1628 }
1629
1630 p->tabwidth = tabwidth;
1631 p->rmargin = rm;
1632 p->maxrmargin = rmax;
1633 return(0);
1634 }
1635
1636 static void
1637 termp_bd_post(DECL_ARGS)
1638 {
1639 size_t rm, rmax;
1640
1641 if (n->type != ROFFT_BODY)
1642 return;
1643
1644 rm = p->rmargin;
1645 rmax = p->maxrmargin;
1646
1647 if (DISP_literal == n->norm->Bd.type ||
1648 DISP_unfilled == n->norm->Bd.type)
1649 p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
1650
1651 p->flags |= TERMP_NOSPACE;
1652 term_newln(p);
1653
1654 p->rmargin = rm;
1655 p->maxrmargin = rmax;
1656 }
1657
1658 static int
1659 termp_bx_pre(DECL_ARGS)
1660 {
1661
1662 if (NULL != (n = n->child)) {
1663 term_word(p, n->string);
1664 p->flags |= TERMP_NOSPACE;
1665 term_word(p, "BSD");
1666 } else {
1667 term_word(p, "BSD");
1668 return(0);
1669 }
1670
1671 if (NULL != (n = n->next)) {
1672 p->flags |= TERMP_NOSPACE;
1673 term_word(p, "-");
1674 p->flags |= TERMP_NOSPACE;
1675 term_word(p, n->string);
1676 }
1677
1678 return(0);
1679 }
1680
1681 static int
1682 termp_xx_pre(DECL_ARGS)
1683 {
1684 const char *pp;
1685 int flags;
1686
1687 pp = NULL;
1688 switch (n->tok) {
1689 case MDOC_Bsx:
1690 pp = "BSD/OS";
1691 break;
1692 case MDOC_Dx:
1693 pp = "DragonFly";
1694 break;
1695 case MDOC_Fx:
1696 pp = "FreeBSD";
1697 break;
1698 case MDOC_Nx:
1699 pp = "NetBSD";
1700 break;
1701 case MDOC_Ox:
1702 pp = "OpenBSD";
1703 break;
1704 case MDOC_Ux:
1705 pp = "UNIX";
1706 break;
1707 default:
1708 abort();
1709 /* NOTREACHED */
1710 }
1711
1712 term_word(p, pp);
1713 if (n->child) {
1714 flags = p->flags;
1715 p->flags |= TERMP_KEEP;
1716 term_word(p, n->child->string);
1717 p->flags = flags;
1718 }
1719 return(0);
1720 }
1721
1722 static void
1723 termp_pf_post(DECL_ARGS)
1724 {
1725
1726 if ( ! (n->next == NULL || n->next->flags & MDOC_LINE))
1727 p->flags |= TERMP_NOSPACE;
1728 }
1729
1730 static int
1731 termp_ss_pre(DECL_ARGS)
1732 {
1733
1734 switch (n->type) {
1735 case ROFFT_BLOCK:
1736 term_newln(p);
1737 if (n->prev)
1738 term_vspace(p);
1739 break;
1740 case ROFFT_HEAD:
1741 term_fontpush(p, TERMFONT_BOLD);
1742 p->offset = term_len(p, (p->defindent+1)/2);
1743 break;
1744 case ROFFT_BODY:
1745 p->offset = term_len(p, p->defindent);
1746 break;
1747 default:
1748 break;
1749 }
1750
1751 return(1);
1752 }
1753
1754 static void
1755 termp_ss_post(DECL_ARGS)
1756 {
1757
1758 if (n->type == ROFFT_HEAD || n->type == ROFFT_BODY)
1759 term_newln(p);
1760 }
1761
1762 static int
1763 termp_cd_pre(DECL_ARGS)
1764 {
1765
1766 synopsis_pre(p, n);
1767 term_fontpush(p, TERMFONT_BOLD);
1768 return(1);
1769 }
1770
1771 static int
1772 termp_in_pre(DECL_ARGS)
1773 {
1774
1775 synopsis_pre(p, n);
1776
1777 if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags) {
1778 term_fontpush(p, TERMFONT_BOLD);
1779 term_word(p, "#include");
1780 term_word(p, "<");
1781 } else {
1782 term_word(p, "<");
1783 term_fontpush(p, TERMFONT_UNDER);
1784 }
1785
1786 p->flags |= TERMP_NOSPACE;
1787 return(1);
1788 }
1789
1790 static void
1791 termp_in_post(DECL_ARGS)
1792 {
1793
1794 if (MDOC_SYNPRETTY & n->flags)
1795 term_fontpush(p, TERMFONT_BOLD);
1796
1797 p->flags |= TERMP_NOSPACE;
1798 term_word(p, ">");
1799
1800 if (MDOC_SYNPRETTY & n->flags)
1801 term_fontpop(p);
1802 }
1803
1804 static int
1805 termp_sp_pre(DECL_ARGS)
1806 {
1807 struct roffsu su;
1808 int i, len;
1809
1810 switch (n->tok) {
1811 case MDOC_sp:
1812 if (n->child) {
1813 if ( ! a2roffsu(n->child->string, &su, SCALE_VS))
1814 su.scale = 1.0;
1815 len = term_vspan(p, &su);
1816 } else
1817 len = 1;
1818 break;
1819 case MDOC_br:
1820 len = 0;
1821 break;
1822 default:
1823 len = 1;
1824 break;
1825 }
1826
1827 if (0 == len)
1828 term_newln(p);
1829 else if (len < 0)
1830 p->skipvsp -= len;
1831 else
1832 for (i = 0; i < len; i++)
1833 term_vspace(p);
1834
1835 return(0);
1836 }
1837
1838 static int
1839 termp_skip_pre(DECL_ARGS)
1840 {
1841
1842 return(0);
1843 }
1844
1845 static int
1846 termp_quote_pre(DECL_ARGS)
1847 {
1848
1849 if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM)
1850 return(1);
1851
1852 switch (n->tok) {
1853 case MDOC_Ao:
1854 /* FALLTHROUGH */
1855 case MDOC_Aq:
1856 term_word(p, n->nchild == 1 &&
1857 n->child->tok == MDOC_Mt ? "<" : "\\(la");
1858 break;
1859 case MDOC_Bro:
1860 /* FALLTHROUGH */
1861 case MDOC_Brq:
1862 term_word(p, "{");
1863 break;
1864 case MDOC_Oo:
1865 /* FALLTHROUGH */
1866 case MDOC_Op:
1867 /* FALLTHROUGH */
1868 case MDOC_Bo:
1869 /* FALLTHROUGH */
1870 case MDOC_Bq:
1871 term_word(p, "[");
1872 break;
1873 case MDOC_Do:
1874 /* FALLTHROUGH */
1875 case MDOC_Dq:
1876 term_word(p, "\\(Lq");
1877 break;
1878 case MDOC_En:
1879 if (NULL == n->norm->Es ||
1880 NULL == n->norm->Es->child)
1881 return(1);
1882 term_word(p, n->norm->Es->child->string);
1883 break;
1884 case MDOC_Po:
1885 /* FALLTHROUGH */
1886 case MDOC_Pq:
1887 term_word(p, "(");
1888 break;
1889 case MDOC__T:
1890 /* FALLTHROUGH */
1891 case MDOC_Qo:
1892 /* FALLTHROUGH */
1893 case MDOC_Qq:
1894 term_word(p, "\"");
1895 break;
1896 case MDOC_Ql:
1897 /* FALLTHROUGH */
1898 case MDOC_So:
1899 /* FALLTHROUGH */
1900 case MDOC_Sq:
1901 term_word(p, "\\(oq");
1902 break;
1903 default:
1904 abort();
1905 /* NOTREACHED */
1906 }
1907
1908 p->flags |= TERMP_NOSPACE;
1909 return(1);
1910 }
1911
1912 static void
1913 termp_quote_post(DECL_ARGS)
1914 {
1915
1916 if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM)
1917 return;
1918
1919 p->flags |= TERMP_NOSPACE;
1920
1921 switch (n->tok) {
1922 case MDOC_Ao:
1923 /* FALLTHROUGH */
1924 case MDOC_Aq:
1925 term_word(p, n->nchild == 1 &&
1926 n->child->tok == MDOC_Mt ? ">" : "\\(ra");
1927 break;
1928 case MDOC_Bro:
1929 /* FALLTHROUGH */
1930 case MDOC_Brq:
1931 term_word(p, "}");
1932 break;
1933 case MDOC_Oo:
1934 /* FALLTHROUGH */
1935 case MDOC_Op:
1936 /* FALLTHROUGH */
1937 case MDOC_Bo:
1938 /* FALLTHROUGH */
1939 case MDOC_Bq:
1940 term_word(p, "]");
1941 break;
1942 case MDOC_Do:
1943 /* FALLTHROUGH */
1944 case MDOC_Dq:
1945 term_word(p, "\\(Rq");
1946 break;
1947 case MDOC_En:
1948 if (n->norm->Es == NULL ||
1949 n->norm->Es->child == NULL ||
1950 n->norm->Es->child->next == NULL)
1951 p->flags &= ~TERMP_NOSPACE;
1952 else
1953 term_word(p, n->norm->Es->child->next->string);
1954 break;
1955 case MDOC_Po:
1956 /* FALLTHROUGH */
1957 case MDOC_Pq:
1958 term_word(p, ")");
1959 break;
1960 case MDOC__T:
1961 /* FALLTHROUGH */
1962 case MDOC_Qo:
1963 /* FALLTHROUGH */
1964 case MDOC_Qq:
1965 term_word(p, "\"");
1966 break;
1967 case MDOC_Ql:
1968 /* FALLTHROUGH */
1969 case MDOC_So:
1970 /* FALLTHROUGH */
1971 case MDOC_Sq:
1972 term_word(p, "\\(cq");
1973 break;
1974 default:
1975 abort();
1976 /* NOTREACHED */
1977 }
1978 }
1979
1980 static int
1981 termp_eo_pre(DECL_ARGS)
1982 {
1983
1984 if (n->type != ROFFT_BODY)
1985 return(1);
1986
1987 if (n->end == ENDBODY_NOT &&
1988 n->parent->head->child == NULL &&
1989 n->child != NULL &&
1990 n->child->end != ENDBODY_NOT)
1991 term_word(p, "\\&");
1992 else if (n->end != ENDBODY_NOT ? n->child != NULL :
1993 n->parent->head->child != NULL && (n->child != NULL ||
1994 (n->parent->tail != NULL && n->parent->tail->child != NULL)))
1995 p->flags |= TERMP_NOSPACE;
1996
1997 return(1);
1998 }
1999
2000 static void
2001 termp_eo_post(DECL_ARGS)
2002 {
2003 int body, tail;
2004
2005 if (n->type != ROFFT_BODY)
2006 return;
2007
2008 if (n->end != ENDBODY_NOT) {
2009 p->flags &= ~TERMP_NOSPACE;
2010 return;
2011 }
2012
2013 body = n->child != NULL || n->parent->head->child != NULL;
2014 tail = n->parent->tail != NULL && n->parent->tail->child != NULL;
2015
2016 if (body && tail)
2017 p->flags |= TERMP_NOSPACE;
2018 else if ( ! (body || tail))
2019 term_word(p, "\\&");
2020 else if ( ! tail)
2021 p->flags &= ~TERMP_NOSPACE;
2022 }
2023
2024 static int
2025 termp_fo_pre(DECL_ARGS)
2026 {
2027 size_t rmargin = 0;
2028 int pretty;
2029
2030 pretty = MDOC_SYNPRETTY & n->flags;
2031
2032 if (n->type == ROFFT_BLOCK) {
2033 synopsis_pre(p, n);
2034 return(1);
2035 } else if (n->type == ROFFT_BODY) {
2036 if (pretty) {
2037 rmargin = p->rmargin;
2038 p->rmargin = p->offset + term_len(p, 4);
2039 p->flags |= TERMP_NOBREAK | TERMP_BRIND |
2040 TERMP_HANG;
2041 }
2042 p->flags |= TERMP_NOSPACE;
2043 term_word(p, "(");
2044 p->flags |= TERMP_NOSPACE;
2045 if (pretty) {
2046 term_flushln(p);
2047 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND |
2048 TERMP_HANG);
2049 p->offset = p->rmargin;
2050 p->rmargin = rmargin;
2051 }
2052 return(1);
2053 }
2054
2055 if (NULL == n->child)
2056 return(0);
2057
2058 /* XXX: we drop non-initial arguments as per groff. */
2059
2060 assert(n->child->string);
2061 term_fontpush(p, TERMFONT_BOLD);
2062 term_word(p, n->child->string);
2063 return(0);
2064 }
2065
2066 static void
2067 termp_fo_post(DECL_ARGS)
2068 {
2069
2070 if (n->type != ROFFT_BODY)
2071 return;
2072
2073 p->flags |= TERMP_NOSPACE;
2074 term_word(p, ")");
2075
2076 if (MDOC_SYNPRETTY & n->flags) {
2077 p->flags |= TERMP_NOSPACE;
2078 term_word(p, ";");
2079 term_flushln(p);
2080 }
2081 }
2082
2083 static int
2084 termp_bf_pre(DECL_ARGS)
2085 {
2086
2087 if (n->type == ROFFT_HEAD)
2088 return(0);
2089 else if (n->type != ROFFT_BODY)
2090 return(1);
2091
2092 if (FONT_Em == n->norm->Bf.font)
2093 term_fontpush(p, TERMFONT_UNDER);
2094 else if (FONT_Sy == n->norm->Bf.font)
2095 term_fontpush(p, TERMFONT_BOLD);
2096 else
2097 term_fontpush(p, TERMFONT_NONE);
2098
2099 return(1);
2100 }
2101
2102 static int
2103 termp_sm_pre(DECL_ARGS)
2104 {
2105
2106 if (NULL == n->child)
2107 p->flags ^= TERMP_NONOSPACE;
2108 else if (0 == strcmp("on", n->child->string))
2109 p->flags &= ~TERMP_NONOSPACE;
2110 else
2111 p->flags |= TERMP_NONOSPACE;
2112
2113 if (p->col && ! (TERMP_NONOSPACE & p->flags))
2114 p->flags &= ~TERMP_NOSPACE;
2115
2116 return(0);
2117 }
2118
2119 static int
2120 termp_ap_pre(DECL_ARGS)
2121 {
2122
2123 p->flags |= TERMP_NOSPACE;
2124 term_word(p, "'");
2125 p->flags |= TERMP_NOSPACE;
2126 return(1);
2127 }
2128
2129 static void
2130 termp____post(DECL_ARGS)
2131 {
2132
2133 /*
2134 * Handle lists of authors. In general, print each followed by
2135 * a comma. Don't print the comma if there are only two
2136 * authors.
2137 */
2138 if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok)
2139 if (NULL == n->next->next || MDOC__A != n->next->next->tok)
2140 if (NULL == n->prev || MDOC__A != n->prev->tok)
2141 return;
2142
2143 /* TODO: %U. */
2144
2145 if (NULL == n->parent || MDOC_Rs != n->parent->tok)
2146 return;
2147
2148 p->flags |= TERMP_NOSPACE;
2149 if (NULL == n->next) {
2150 term_word(p, ".");
2151 p->flags |= TERMP_SENTENCE;
2152 } else
2153 term_word(p, ",");
2154 }
2155
2156 static int
2157 termp_li_pre(DECL_ARGS)
2158 {
2159
2160 term_fontpush(p, TERMFONT_NONE);
2161 return(1);
2162 }
2163
2164 static int
2165 termp_lk_pre(DECL_ARGS)
2166 {
2167 const struct roff_node *link, *descr;
2168
2169 if (NULL == (link = n->child))
2170 return(0);
2171
2172 if (NULL != (descr = link->next)) {
2173 term_fontpush(p, TERMFONT_UNDER);
2174 while (NULL != descr) {
2175 term_word(p, descr->string);
2176 descr = descr->next;
2177 }
2178 p->flags |= TERMP_NOSPACE;
2179 term_word(p, ":");
2180 term_fontpop(p);
2181 }
2182
2183 term_fontpush(p, TERMFONT_BOLD);
2184 term_word(p, link->string);
2185 term_fontpop(p);
2186
2187 return(0);
2188 }
2189
2190 static int
2191 termp_bk_pre(DECL_ARGS)
2192 {
2193
2194 switch (n->type) {
2195 case ROFFT_BLOCK:
2196 break;
2197 case ROFFT_HEAD:
2198 return(0);
2199 case ROFFT_BODY:
2200 if (n->parent->args || 0 == n->prev->nchild)
2201 p->flags |= TERMP_PREKEEP;
2202 break;
2203 default:
2204 abort();
2205 /* NOTREACHED */
2206 }
2207
2208 return(1);
2209 }
2210
2211 static void
2212 termp_bk_post(DECL_ARGS)
2213 {
2214
2215 if (n->type == ROFFT_BODY)
2216 p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
2217 }
2218
2219 static void
2220 termp__t_post(DECL_ARGS)
2221 {
2222
2223 /*
2224 * If we're in an `Rs' and there's a journal present, then quote
2225 * us instead of underlining us (for disambiguation).
2226 */
2227 if (n->parent && MDOC_Rs == n->parent->tok &&
2228 n->parent->norm->Rs.quote_T)
2229 termp_quote_post(p, pair, meta, n);
2230
2231 termp____post(p, pair, meta, n);
2232 }
2233
2234 static int
2235 termp__t_pre(DECL_ARGS)
2236 {
2237
2238 /*
2239 * If we're in an `Rs' and there's a journal present, then quote
2240 * us instead of underlining us (for disambiguation).
2241 */
2242 if (n->parent && MDOC_Rs == n->parent->tok &&
2243 n->parent->norm->Rs.quote_T)
2244 return(termp_quote_pre(p, pair, meta, n));
2245
2246 term_fontpush(p, TERMFONT_UNDER);
2247 return(1);
2248 }
2249
2250 static int
2251 termp_under_pre(DECL_ARGS)
2252 {
2253
2254 term_fontpush(p, TERMFONT_UNDER);
2255 return(1);
2256 }