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