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