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