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