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