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