]> git.cameronkatri.com Git - mandoc.git/blob - mdoc_term.c
208dc94c4a12b04b60149a2b8b0454db20cb11eb
[mandoc.git] / mdoc_term.c
1 /* $Id: mdoc_term.c,v 1.37 2009/07/12 20:50:08 kristaps Exp $ */
2 /*
3 * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17 #include <sys/types.h>
18
19 #include <assert.h>
20 #include <ctype.h>
21 #include <err.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 #include "term.h"
27 #include "mdoc.h"
28
29 /* FIXME: macro arguments can be escaped. */
30 /* FIXME: support more offset/width tokens. */
31
32 #define TTYPE_PROG 0
33 #define TTYPE_CMD_FLAG 1
34 #define TTYPE_CMD_ARG 2
35 #define TTYPE_SECTION 3
36 #define TTYPE_FUNC_DECL 4
37 #define TTYPE_VAR_DECL 5
38 #define TTYPE_FUNC_TYPE 6
39 #define TTYPE_FUNC_NAME 7
40 #define TTYPE_FUNC_ARG 8
41 #define TTYPE_LINK 9
42 #define TTYPE_SSECTION 10
43 #define TTYPE_FILE 11
44 #define TTYPE_EMPH 12
45 #define TTYPE_CONFIG 13
46 #define TTYPE_CMD 14
47 #define TTYPE_INCLUDE 15
48 #define TTYPE_SYMB 16
49 #define TTYPE_SYMBOL 17
50 #define TTYPE_DIAG 18
51 #define TTYPE_LINK_ANCHOR 19
52 #define TTYPE_LINK_TEXT 20
53 #define TTYPE_REF_JOURNAL 21
54 #define TTYPE_LIST 22
55 #define TTYPE_NMAX 23
56
57 const int ttypes[TTYPE_NMAX] = {
58 TERMP_BOLD, /* TTYPE_PROG */
59 TERMP_BOLD, /* TTYPE_CMD_FLAG */
60 TERMP_UNDER, /* TTYPE_CMD_ARG */
61 TERMP_BOLD, /* TTYPE_SECTION */
62 TERMP_BOLD, /* TTYPE_FUNC_DECL */
63 TERMP_UNDER, /* TTYPE_VAR_DECL */
64 TERMP_UNDER, /* TTYPE_FUNC_TYPE */
65 TERMP_BOLD, /* TTYPE_FUNC_NAME */
66 TERMP_UNDER, /* TTYPE_FUNC_ARG */
67 TERMP_UNDER, /* TTYPE_LINK */
68 TERMP_BOLD, /* TTYPE_SSECTION */
69 TERMP_UNDER, /* TTYPE_FILE */
70 TERMP_UNDER, /* TTYPE_EMPH */
71 TERMP_BOLD, /* TTYPE_CONFIG */
72 TERMP_BOLD, /* TTYPE_CMD */
73 TERMP_BOLD, /* TTYPE_INCLUDE */
74 TERMP_BOLD, /* TTYPE_SYMB */
75 TERMP_BOLD, /* TTYPE_SYMBOL */
76 TERMP_BOLD, /* TTYPE_DIAG */
77 TERMP_UNDER, /* TTYPE_LINK_ANCHOR */
78 TERMP_BOLD, /* TTYPE_LINK_TEXT */
79 TERMP_UNDER, /* TTYPE_REF_JOURNAL */
80 TERMP_BOLD /* TTYPE_LIST */
81 };
82
83 struct termpair {
84 struct termpair *ppair;
85 int flag;
86 int count;
87 };
88
89 #define DECL_ARGS struct termp *p, \
90 struct termpair *pair, \
91 const struct mdoc_meta *meta, \
92 const struct mdoc_node *node
93
94 struct termact {
95 int (*pre)(DECL_ARGS);
96 void (*post)(DECL_ARGS);
97 };
98
99 static void termp____post(DECL_ARGS);
100 static void termp__t_post(DECL_ARGS);
101 static void termp_aq_post(DECL_ARGS);
102 static void termp_bd_post(DECL_ARGS);
103 static void termp_bl_post(DECL_ARGS);
104 static void termp_bq_post(DECL_ARGS);
105 static void termp_brq_post(DECL_ARGS);
106 static void termp_bx_post(DECL_ARGS);
107 static void termp_d1_post(DECL_ARGS);
108 static void termp_dq_post(DECL_ARGS);
109 static void termp_fd_post(DECL_ARGS);
110 static void termp_fn_post(DECL_ARGS);
111 static void termp_fo_post(DECL_ARGS);
112 static void termp_ft_post(DECL_ARGS);
113 static void termp_in_post(DECL_ARGS);
114 static void termp_it_post(DECL_ARGS);
115 static void termp_lb_post(DECL_ARGS);
116 static void termp_op_post(DECL_ARGS);
117 static void termp_pf_post(DECL_ARGS);
118 static void termp_pq_post(DECL_ARGS);
119 static void termp_qq_post(DECL_ARGS);
120 static void termp_sh_post(DECL_ARGS);
121 static void termp_sq_post(DECL_ARGS);
122 static void termp_ss_post(DECL_ARGS);
123 static void termp_vt_post(DECL_ARGS);
124
125 static int termp__j_pre(DECL_ARGS);
126 static int termp__t_pre(DECL_ARGS);
127 static int termp_ap_pre(DECL_ARGS);
128 static int termp_aq_pre(DECL_ARGS);
129 static int termp_ar_pre(DECL_ARGS);
130 static int termp_bd_pre(DECL_ARGS);
131 static int termp_bf_pre(DECL_ARGS);
132 static int termp_bq_pre(DECL_ARGS);
133 static int termp_brq_pre(DECL_ARGS);
134 static int termp_bt_pre(DECL_ARGS);
135 static int termp_cd_pre(DECL_ARGS);
136 static int termp_cm_pre(DECL_ARGS);
137 static int termp_d1_pre(DECL_ARGS);
138 static int termp_dq_pre(DECL_ARGS);
139 static int termp_em_pre(DECL_ARGS);
140 static int termp_ex_pre(DECL_ARGS);
141 static int termp_fa_pre(DECL_ARGS);
142 static int termp_fd_pre(DECL_ARGS);
143 static int termp_fl_pre(DECL_ARGS);
144 static int termp_fn_pre(DECL_ARGS);
145 static int termp_fo_pre(DECL_ARGS);
146 static int termp_ft_pre(DECL_ARGS);
147 static int termp_ic_pre(DECL_ARGS);
148 static int termp_in_pre(DECL_ARGS);
149 static int termp_it_pre(DECL_ARGS);
150 static int termp_lk_pre(DECL_ARGS);
151 static int termp_ms_pre(DECL_ARGS);
152 static int termp_mt_pre(DECL_ARGS);
153 static int termp_nd_pre(DECL_ARGS);
154 static int termp_nm_pre(DECL_ARGS);
155 static int termp_ns_pre(DECL_ARGS);
156 static int termp_op_pre(DECL_ARGS);
157 static int termp_pa_pre(DECL_ARGS);
158 static int termp_pf_pre(DECL_ARGS);
159 static int termp_pp_pre(DECL_ARGS);
160 static int termp_pq_pre(DECL_ARGS);
161 static int termp_qq_pre(DECL_ARGS);
162 static int termp_rs_pre(DECL_ARGS);
163 static int termp_rv_pre(DECL_ARGS);
164 static int termp_sh_pre(DECL_ARGS);
165 static int termp_sm_pre(DECL_ARGS);
166 static int termp_sq_pre(DECL_ARGS);
167 static int termp_ss_pre(DECL_ARGS);
168 static int termp_sx_pre(DECL_ARGS);
169 static int termp_sy_pre(DECL_ARGS);
170 static int termp_ud_pre(DECL_ARGS);
171 static int termp_va_pre(DECL_ARGS);
172 static int termp_vt_pre(DECL_ARGS);
173 static int termp_xr_pre(DECL_ARGS);
174 static int termp_xx_pre(DECL_ARGS);
175
176 static const struct termact termacts[MDOC_MAX] = {
177 { termp_ap_pre, NULL }, /* Ap */
178 { NULL, NULL }, /* Dd */
179 { NULL, NULL }, /* Dt */
180 { NULL, NULL }, /* Os */
181 { termp_sh_pre, termp_sh_post }, /* Sh */
182 { termp_ss_pre, termp_ss_post }, /* Ss */
183 { termp_pp_pre, NULL }, /* Pp */
184 { termp_d1_pre, termp_d1_post }, /* D1 */
185 { termp_d1_pre, termp_d1_post }, /* Dl */
186 { termp_bd_pre, termp_bd_post }, /* Bd */
187 { NULL, NULL }, /* Ed */
188 { NULL, termp_bl_post }, /* Bl */
189 { NULL, NULL }, /* El */
190 { termp_it_pre, termp_it_post }, /* It */
191 { NULL, NULL }, /* Ad */
192 { NULL, NULL }, /* An */
193 { termp_ar_pre, NULL }, /* Ar */
194 { termp_cd_pre, NULL }, /* Cd */
195 { termp_cm_pre, NULL }, /* Cm */
196 { NULL, NULL }, /* Dv */
197 { NULL, NULL }, /* Er */
198 { NULL, NULL }, /* Ev */
199 { termp_ex_pre, NULL }, /* Ex */
200 { termp_fa_pre, NULL }, /* Fa */
201 { termp_fd_pre, termp_fd_post }, /* Fd */
202 { termp_fl_pre, NULL }, /* Fl */
203 { termp_fn_pre, termp_fn_post }, /* Fn */
204 { termp_ft_pre, termp_ft_post }, /* Ft */
205 { termp_ic_pre, NULL }, /* Ic */
206 { termp_in_pre, termp_in_post }, /* In */
207 { NULL, NULL }, /* Li */
208 { termp_nd_pre, NULL }, /* Nd */
209 { termp_nm_pre, NULL }, /* Nm */
210 { termp_op_pre, termp_op_post }, /* Op */
211 { NULL, NULL }, /* Ot */
212 { termp_pa_pre, NULL }, /* Pa */
213 { termp_rv_pre, NULL }, /* Rv */
214 { NULL, NULL }, /* St */
215 { termp_va_pre, NULL }, /* Va */
216 { termp_vt_pre, termp_vt_post }, /* Vt */
217 { termp_xr_pre, NULL }, /* Xr */
218 { NULL, termp____post }, /* %A */
219 { NULL, termp____post }, /* %B */
220 { NULL, termp____post }, /* %D */
221 { NULL, termp____post }, /* %I */
222 { termp__j_pre, termp____post }, /* %J */
223 { NULL, termp____post }, /* %N */
224 { NULL, termp____post }, /* %O */
225 { NULL, termp____post }, /* %P */
226 { NULL, termp____post }, /* %R */
227 { termp__t_pre, termp__t_post }, /* %T */
228 { NULL, termp____post }, /* %V */
229 { NULL, NULL }, /* Ac */
230 { termp_aq_pre, termp_aq_post }, /* Ao */
231 { termp_aq_pre, termp_aq_post }, /* Aq */
232 { NULL, NULL }, /* At */
233 { NULL, NULL }, /* Bc */
234 { termp_bf_pre, NULL }, /* Bf */
235 { termp_bq_pre, termp_bq_post }, /* Bo */
236 { termp_bq_pre, termp_bq_post }, /* Bq */
237 { termp_xx_pre, NULL }, /* Bsx */
238 { NULL, termp_bx_post }, /* Bx */
239 { NULL, NULL }, /* Db */
240 { NULL, NULL }, /* Dc */
241 { termp_dq_pre, termp_dq_post }, /* Do */
242 { termp_dq_pre, termp_dq_post }, /* Dq */
243 { NULL, NULL }, /* Ec */
244 { NULL, NULL }, /* Ef */
245 { termp_em_pre, NULL }, /* Em */
246 { NULL, NULL }, /* Eo */
247 { termp_xx_pre, NULL }, /* Fx */
248 { termp_ms_pre, NULL }, /* Ms */
249 { NULL, NULL }, /* No */
250 { termp_ns_pre, NULL }, /* Ns */
251 { termp_xx_pre, NULL }, /* Nx */
252 { termp_xx_pre, NULL }, /* Ox */
253 { NULL, NULL }, /* Pc */
254 { termp_pf_pre, termp_pf_post }, /* Pf */
255 { termp_pq_pre, termp_pq_post }, /* Po */
256 { termp_pq_pre, termp_pq_post }, /* Pq */
257 { NULL, NULL }, /* Qc */
258 { termp_sq_pre, termp_sq_post }, /* Ql */
259 { termp_qq_pre, termp_qq_post }, /* Qo */
260 { termp_qq_pre, termp_qq_post }, /* Qq */
261 { NULL, NULL }, /* Re */
262 { termp_rs_pre, NULL }, /* Rs */
263 { NULL, NULL }, /* Sc */
264 { termp_sq_pre, termp_sq_post }, /* So */
265 { termp_sq_pre, termp_sq_post }, /* Sq */
266 { termp_sm_pre, NULL }, /* Sm */
267 { termp_sx_pre, NULL }, /* Sx */
268 { termp_sy_pre, NULL }, /* Sy */
269 { NULL, NULL }, /* Tn */
270 { termp_xx_pre, NULL }, /* Ux */
271 { NULL, NULL }, /* Xc */
272 { NULL, NULL }, /* Xo */
273 { termp_fo_pre, termp_fo_post }, /* Fo */
274 { NULL, NULL }, /* Fc */
275 { termp_op_pre, termp_op_post }, /* Oo */
276 { NULL, NULL }, /* Oc */
277 { NULL, NULL }, /* Bk */
278 { NULL, NULL }, /* Ek */
279 { termp_bt_pre, NULL }, /* Bt */
280 { NULL, NULL }, /* Hf */
281 { NULL, NULL }, /* Fr */
282 { termp_ud_pre, NULL }, /* Ud */
283 { NULL, termp_lb_post }, /* Lb */
284 { termp_pp_pre, NULL }, /* Lp */
285 { termp_lk_pre, NULL }, /* Lk */
286 { termp_mt_pre, NULL }, /* Mt */
287 { termp_brq_pre, termp_brq_post }, /* Brq */
288 { termp_brq_pre, termp_brq_post }, /* Bro */
289 { NULL, NULL }, /* Brc */
290 { NULL, NULL }, /* %C */
291 { NULL, NULL }, /* Es */
292 { NULL, NULL }, /* En */
293 { termp_xx_pre, NULL }, /* Dx */
294 { NULL, NULL }, /* %Q */
295 };
296
297 #ifdef __linux__
298 extern size_t strlcpy(char *, const char *, size_t);
299 extern size_t strlcat(char *, const char *, size_t);
300 #endif
301
302 static int arg_hasattr(int, const struct mdoc_node *);
303 static int arg_getattrs(const int *, int *, size_t,
304 const struct mdoc_node *);
305 static int arg_getattr(int, const struct mdoc_node *);
306 static size_t arg_offset(const struct mdoc_argv *);
307 static size_t arg_width(const struct mdoc_argv *, int);
308 static int arg_listtype(const struct mdoc_node *);
309 static int fmt_block_vspace(struct termp *,
310 const struct mdoc_node *,
311 const struct mdoc_node *);
312 static void print_node(DECL_ARGS);
313 static void print_head(struct termp *,
314 const struct mdoc_meta *);
315 static void print_body(DECL_ARGS);
316 static void print_foot(struct termp *,
317 const struct mdoc_meta *);
318
319
320 int
321 mdoc_run(struct termp *p, const struct mdoc *m)
322 {
323 /*
324 * Main output function. When this is called, assume that the
325 * tree is properly formed.
326 */
327 print_head(p, mdoc_meta(m));
328 assert(mdoc_node(m));
329 assert(MDOC_ROOT == mdoc_node(m)->type);
330 if (mdoc_node(m)->child)
331 print_body(p, NULL, mdoc_meta(m), mdoc_node(m)->child);
332 print_foot(p, mdoc_meta(m));
333 return(1);
334 }
335
336
337 static void
338 print_body(DECL_ARGS)
339 {
340
341 print_node(p, pair, meta, node);
342 if ( ! node->next)
343 return;
344 print_body(p, pair, meta, node->next);
345 }
346
347
348 static void
349 print_node(DECL_ARGS)
350 {
351 int dochild;
352 struct termpair npair;
353 size_t offset, rmargin;
354
355 dochild = 1;
356 offset = p->offset;
357 rmargin = p->rmargin;
358
359 npair.ppair = pair;
360 npair.flag = 0;
361 npair.count = 0;
362
363 if (MDOC_TEXT != node->type) {
364 if (termacts[node->tok].pre)
365 if ( ! (*termacts[node->tok].pre)(p, &npair, meta, node))
366 dochild = 0;
367 } else /* MDOC_TEXT == node->type */
368 term_word(p, node->string);
369
370 /* Children. */
371
372 p->flags |= npair.flag;
373
374 if (dochild && node->child)
375 print_body(p, &npair, meta, node->child);
376
377 /* Post-processing. */
378
379 if (MDOC_TEXT != node->type)
380 if (termacts[node->tok].post)
381 (*termacts[node->tok].post)(p, &npair, meta, node);
382
383 p->offset = offset;
384 p->rmargin = rmargin;
385 p->flags &= ~npair.flag;
386 }
387
388
389 static void
390 print_foot(struct termp *p, const struct mdoc_meta *meta)
391 {
392 struct tm *tm;
393 char *buf, *os;
394
395 /*
396 * Output the footer in new-groff style, that is, three columns
397 * with the middle being the manual date and flanking columns
398 * being the operating system:
399 *
400 * SYSTEM DATE SYSTEM
401 */
402
403 if (NULL == (buf = malloc(p->rmargin)))
404 err(1, "malloc");
405 if (NULL == (os = malloc(p->rmargin)))
406 err(1, "malloc");
407
408 tm = localtime(&meta->date);
409
410 if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
411 err(1, "strftime");
412
413 (void)strlcpy(os, meta->os, p->rmargin);
414
415 term_vspace(p);
416
417 p->offset = 0;
418 p->rmargin = (p->maxrmargin - strlen(buf) + 1) / 2;
419 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
420
421 term_word(p, os);
422 term_flushln(p);
423
424 p->offset = p->rmargin;
425 p->rmargin = p->maxrmargin - strlen(os);
426 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
427
428 term_word(p, buf);
429 term_flushln(p);
430
431 p->offset = p->rmargin;
432 p->rmargin = p->maxrmargin;
433 p->flags &= ~TERMP_NOBREAK;
434 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
435
436 term_word(p, os);
437 term_flushln(p);
438
439 p->offset = 0;
440 p->rmargin = p->maxrmargin;
441 p->flags = 0;
442
443 free(buf);
444 free(os);
445 }
446
447
448 static void
449 print_head(struct termp *p, const struct mdoc_meta *meta)
450 {
451 char *buf, *title;
452
453 p->rmargin = p->maxrmargin;
454 p->offset = 0;
455
456 if (NULL == (buf = malloc(p->rmargin)))
457 err(1, "malloc");
458 if (NULL == (title = malloc(p->rmargin)))
459 err(1, "malloc");
460
461 /*
462 * The header is strange. It has three components, which are
463 * really two with the first duplicated. It goes like this:
464 *
465 * IDENTIFIER TITLE IDENTIFIER
466 *
467 * The IDENTIFIER is NAME(SECTION), which is the command-name
468 * (if given, or "unknown" if not) followed by the manual page
469 * section. These are given in `Dt'. The TITLE is a free-form
470 * string depending on the manual volume. If not specified, it
471 * switches on the manual section.
472 */
473
474 assert(meta->vol);
475 (void)strlcpy(buf, meta->vol, p->rmargin);
476
477 if (meta->arch) {
478 (void)strlcat(buf, " (", p->rmargin);
479 (void)strlcat(buf, meta->arch, p->rmargin);
480 (void)strlcat(buf, ")", p->rmargin);
481 }
482
483 (void)snprintf(title, p->rmargin, "%s(%d)",
484 meta->title, meta->msec);
485
486 p->offset = 0;
487 p->rmargin = (p->maxrmargin - strlen(buf) + 1) / 2;
488 p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
489
490 term_word(p, title);
491 term_flushln(p);
492
493 p->offset = p->rmargin;
494 p->rmargin = p->maxrmargin - strlen(title);
495 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
496
497 term_word(p, buf);
498 term_flushln(p);
499
500 p->offset = p->rmargin;
501 p->rmargin = p->maxrmargin;
502 p->flags &= ~TERMP_NOBREAK;
503 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
504
505 term_word(p, title);
506 term_flushln(p);
507
508 p->offset = 0;
509 p->rmargin = p->maxrmargin;
510 p->flags &= ~TERMP_NOSPACE;
511
512 free(title);
513 free(buf);
514 }
515
516
517 static size_t
518 arg_width(const struct mdoc_argv *arg, int pos)
519 {
520 size_t v;
521 int i, len;
522
523 assert(pos < (int)arg->sz && pos >= 0);
524 assert(arg->value[pos]);
525
526 if (0 == (len = (int)strlen(arg->value[pos])))
527 return(0);
528
529 for (i = 0; i < len - 1; i++)
530 if ( ! isdigit((u_char)arg->value[pos][i]))
531 break;
532
533 if (i == len - 1) {
534 if ('n' == arg->value[pos][len - 1] ||
535 'm' == arg->value[pos][len - 1]) {
536 v = (size_t)atoi(arg->value[pos]);
537 return(v + 2);
538 }
539
540 }
541 return(strlen(arg->value[pos]) + 2);
542 }
543
544
545 static int
546 arg_listtype(const struct mdoc_node *n)
547 {
548 int i, len;
549
550 assert(MDOC_BLOCK == n->type);
551
552 len = (int)(n->args ? n->args->argc : 0);
553
554 for (i = 0; i < len; i++)
555 switch (n->args->argv[i].arg) {
556 case (MDOC_Bullet):
557 /* FALLTHROUGH */
558 case (MDOC_Dash):
559 /* FALLTHROUGH */
560 case (MDOC_Enum):
561 /* FALLTHROUGH */
562 case (MDOC_Hyphen):
563 /* FALLTHROUGH */
564 case (MDOC_Tag):
565 /* FALLTHROUGH */
566 case (MDOC_Inset):
567 /* FALLTHROUGH */
568 case (MDOC_Diag):
569 /* FALLTHROUGH */
570 case (MDOC_Item):
571 /* FALLTHROUGH */
572 case (MDOC_Column):
573 /* FALLTHROUGH */
574 case (MDOC_Ohang):
575 return(n->args->argv[i].arg);
576 default:
577 break;
578 }
579
580 /* FIXME: mandated by parser. */
581
582 errx(1, "list type not supported");
583 /* NOTREACHED */
584 }
585
586
587 static size_t
588 arg_offset(const struct mdoc_argv *arg)
589 {
590
591 assert(*arg->value);
592 if (0 == strcmp(*arg->value, "left"))
593 return(0);
594 if (0 == strcmp(*arg->value, "indent"))
595 return(INDENT + 1);
596 if (0 == strcmp(*arg->value, "indent-two"))
597 return((INDENT + 1) * 2);
598
599 /* FIXME: needs to support field-widths (10n, etc.). */
600
601 return(strlen(*arg->value));
602 }
603
604
605 static int
606 arg_hasattr(int arg, const struct mdoc_node *n)
607 {
608
609 return(-1 != arg_getattr(arg, n));
610 }
611
612
613 static int
614 arg_getattr(int v, const struct mdoc_node *n)
615 {
616 int val;
617
618 return(arg_getattrs(&v, &val, 1, n) ? val : -1);
619 }
620
621
622 static int
623 arg_getattrs(const int *keys, int *vals,
624 size_t sz, const struct mdoc_node *n)
625 {
626 int i, j, k;
627
628 if (NULL == n->args)
629 return(0);
630
631 for (k = i = 0; i < (int)n->args->argc; i++)
632 for (j = 0; j < (int)sz; j++)
633 if (n->args->argv[i].arg == keys[j]) {
634 vals[j] = i;
635 k++;
636 }
637 return(k);
638 }
639
640
641 /* ARGSUSED */
642 static int
643 fmt_block_vspace(struct termp *p,
644 const struct mdoc_node *bl,
645 const struct mdoc_node *node)
646 {
647 const struct mdoc_node *n;
648
649 term_newln(p);
650
651 if (arg_hasattr(MDOC_Compact, bl))
652 return(1);
653
654 for (n = node; n; n = n->parent) {
655 if (MDOC_BLOCK != n->type)
656 continue;
657 if (MDOC_Ss == n->tok)
658 break;
659 if (MDOC_Sh == n->tok)
660 break;
661 if (NULL == n->prev)
662 continue;
663 term_vspace(p);
664 break;
665 }
666
667 return(1);
668 }
669
670
671 /* ARGSUSED */
672 static int
673 termp_dq_pre(DECL_ARGS)
674 {
675
676 if (MDOC_BODY != node->type)
677 return(1);
678
679 term_word(p, "\\(lq");
680 p->flags |= TERMP_NOSPACE;
681 return(1);
682 }
683
684
685 /* ARGSUSED */
686 static void
687 termp_dq_post(DECL_ARGS)
688 {
689
690 if (MDOC_BODY != node->type)
691 return;
692
693 p->flags |= TERMP_NOSPACE;
694 term_word(p, "\\(rq");
695 }
696
697
698 /* ARGSUSED */
699 static int
700 termp_it_pre(DECL_ARGS)
701 {
702 const struct mdoc_node *bl, *n;
703 char buf[7];
704 int i, type, keys[3], vals[3], sv;
705 size_t width, offset;
706
707 if (MDOC_BLOCK == node->type)
708 return(fmt_block_vspace(p, node->parent->parent, node));
709
710 bl = node->parent->parent->parent;
711
712 /* Save parent attributes. */
713
714 pair->flag = p->flags;
715
716 /* Get list width and offset. */
717
718 keys[0] = MDOC_Width;
719 keys[1] = MDOC_Offset;
720 keys[2] = MDOC_Column;
721
722 vals[0] = vals[1] = vals[2] = -1;
723
724 width = offset = 0;
725
726 (void)arg_getattrs(keys, vals, 3, bl);
727
728 type = arg_listtype(bl);
729
730 /* Calculate real width and offset. */
731
732 switch (type) {
733 case (MDOC_Column):
734 if (MDOC_BODY == node->type)
735 break;
736 for (i = 0, n = node->prev; n; n = n->prev, i++)
737 offset += arg_width
738 (&bl->args->argv[vals[2]], i);
739 assert(i < (int)bl->args->argv[vals[2]].sz);
740 width = arg_width(&bl->args->argv[vals[2]], i);
741 if (vals[1] >= 0)
742 offset += arg_offset(&bl->args->argv[vals[1]]);
743 break;
744 default:
745 if (vals[0] >= 0)
746 width = arg_width(&bl->args->argv[vals[0]], 0);
747 if (vals[1] >= 0)
748 offset += arg_offset(&bl->args->argv[vals[1]]);
749 break;
750 }
751
752 /*
753 * List-type can override the width in the case of fixed-head
754 * values (bullet, dash/hyphen, enum). Tags need a non-zero
755 * offset.
756 */
757
758 switch (type) {
759 case (MDOC_Bullet):
760 /* FALLTHROUGH */
761 case (MDOC_Dash):
762 /* FALLTHROUGH */
763 case (MDOC_Hyphen):
764 if (width < 4)
765 width = 4;
766 break;
767 case (MDOC_Enum):
768 if (width < 5)
769 width = 5;
770 break;
771 case (MDOC_Tag):
772 if (0 == width)
773 width = 10;
774 break;
775 default:
776 break;
777 }
778
779 /*
780 * Whitespace control. Inset bodies need an initial space,
781 * while diagonal bodies need two.
782 */
783
784 switch (type) {
785 case (MDOC_Diag):
786 term_word(p, "\\ ");
787 /* FALLTHROUGH */
788 case (MDOC_Inset):
789 if (MDOC_BODY == node->type)
790 p->flags &= ~TERMP_NOSPACE;
791 else
792 p->flags |= TERMP_NOSPACE;
793 break;
794 default:
795 p->flags |= TERMP_NOSPACE;
796 break;
797 }
798
799 /*
800 * Style flags. Diagnostic heads need TTYPE_DIAG.
801 */
802
803 switch (type) {
804 case (MDOC_Diag):
805 if (MDOC_HEAD == node->type)
806 p->flags |= ttypes[TTYPE_DIAG];
807 break;
808 default:
809 break;
810 }
811
812 /*
813 * Pad and break control. This is the tricker part. Lists with
814 * set right-margins for the head get TERMP_NOBREAK because, if
815 * they overrun the margin, they wrap to the new margin.
816 * Correspondingly, the body for these types don't left-pad, as
817 * the head will pad out to to the right.
818 */
819
820 switch (type) {
821 case (MDOC_Bullet):
822 /* FALLTHROUGH */
823 case (MDOC_Dash):
824 /* FALLTHROUGH */
825 case (MDOC_Enum):
826 /* FALLTHROUGH */
827 case (MDOC_Hyphen):
828 /* FALLTHROUGH */
829 case (MDOC_Tag):
830 if (MDOC_HEAD == node->type)
831 p->flags |= TERMP_NOBREAK;
832 else
833 p->flags |= TERMP_NOLPAD;
834 if (MDOC_HEAD == node->type && MDOC_Tag == type)
835 if (NULL == node->next ||
836 NULL == node->next->child)
837 p->flags |= TERMP_NONOBREAK;
838 break;
839 case (MDOC_Column):
840 if (MDOC_HEAD == node->type) {
841 assert(node->next);
842 if (MDOC_BODY == node->next->type)
843 p->flags &= ~TERMP_NOBREAK;
844 else
845 p->flags |= TERMP_NOBREAK;
846 if (node->prev)
847 p->flags |= TERMP_NOLPAD;
848 }
849 break;
850 case (MDOC_Diag):
851 if (MDOC_HEAD == node->type)
852 p->flags |= TERMP_NOBREAK;
853 break;
854 default:
855 break;
856 }
857
858 /*
859 * Margin control. Set-head-width lists have their right
860 * margins shortened. The body for these lists has the offset
861 * necessarily lengthened. Everybody gets the offset.
862 */
863
864 p->offset += offset;
865
866 switch (type) {
867 case (MDOC_Bullet):
868 /* FALLTHROUGH */
869 case (MDOC_Dash):
870 /* FALLTHROUGH */
871 case (MDOC_Enum):
872 /* FALLTHROUGH */
873 case (MDOC_Hyphen):
874 /* FALLTHROUGH */
875 case (MDOC_Tag):
876 if (MDOC_HEAD == node->type)
877 p->rmargin = p->offset + width;
878 else
879 p->offset += width;
880 break;
881 case (MDOC_Column):
882 p->rmargin = p->offset + width;
883 break;
884 default:
885 break;
886 }
887
888 /*
889 * The dash, hyphen, bullet and enum lists all have a special
890 * HEAD character (temporarily bold, in some cases).
891 */
892
893 sv = p->flags;
894 if (MDOC_HEAD == node->type)
895 switch (type) {
896 case (MDOC_Bullet):
897 p->flags |= TERMP_BOLD;
898 term_word(p, "\\[bu]");
899 break;
900 case (MDOC_Dash):
901 /* FALLTHROUGH */
902 case (MDOC_Hyphen):
903 p->flags |= TERMP_BOLD;
904 term_word(p, "\\(hy");
905 break;
906 case (MDOC_Enum):
907 (pair->ppair->ppair->count)++;
908 (void)snprintf(buf, sizeof(buf), "%d.",
909 pair->ppair->ppair->count);
910 term_word(p, buf);
911 break;
912 default:
913 break;
914 }
915
916 p->flags = sv; /* Restore saved flags. */
917
918 /*
919 * If we're not going to process our children, indicate so here.
920 */
921
922 switch (type) {
923 case (MDOC_Bullet):
924 /* FALLTHROUGH */
925 case (MDOC_Item):
926 /* FALLTHROUGH */
927 case (MDOC_Dash):
928 /* FALLTHROUGH */
929 case (MDOC_Hyphen):
930 /* FALLTHROUGH */
931 case (MDOC_Enum):
932 if (MDOC_HEAD == node->type)
933 return(0);
934 break;
935 case (MDOC_Column):
936 if (MDOC_BODY == node->type)
937 return(0);
938 break;
939 default:
940 break;
941 }
942
943 return(1);
944 }
945
946
947 /* ARGSUSED */
948 static void
949 termp_it_post(DECL_ARGS)
950 {
951 int type;
952
953 if (MDOC_BODY != node->type && MDOC_HEAD != node->type)
954 return;
955
956 type = arg_listtype(node->parent->parent->parent);
957
958 switch (type) {
959 case (MDOC_Diag):
960 /* FALLTHROUGH */
961 case (MDOC_Item):
962 /* FALLTHROUGH */
963 case (MDOC_Inset):
964 if (MDOC_BODY == node->type)
965 term_flushln(p);
966 break;
967 case (MDOC_Column):
968 if (MDOC_HEAD == node->type)
969 term_flushln(p);
970 break;
971 default:
972 term_flushln(p);
973 break;
974 }
975
976 p->flags = pair->flag;
977 }
978
979
980 /* ARGSUSED */
981 static int
982 termp_nm_pre(DECL_ARGS)
983 {
984
985 if (SEC_SYNOPSIS == node->sec)
986 term_newln(p);
987
988 pair->flag |= ttypes[TTYPE_PROG];
989 p->flags |= ttypes[TTYPE_PROG];
990
991 if (NULL == node->child)
992 term_word(p, meta->name);
993
994 return(1);
995 }
996
997
998 /* ARGSUSED */
999 static int
1000 termp_fl_pre(DECL_ARGS)
1001 {
1002
1003 pair->flag |= ttypes[TTYPE_CMD_FLAG];
1004 p->flags |= ttypes[TTYPE_CMD_FLAG];
1005 term_word(p, "\\-");
1006 p->flags |= TERMP_NOSPACE;
1007 return(1);
1008 }
1009
1010
1011 /* ARGSUSED */
1012 static int
1013 termp_ar_pre(DECL_ARGS)
1014 {
1015
1016 pair->flag |= ttypes[TTYPE_CMD_ARG];
1017 return(1);
1018 }
1019
1020
1021 /* ARGSUSED */
1022 static int
1023 termp_ns_pre(DECL_ARGS)
1024 {
1025
1026 p->flags |= TERMP_NOSPACE;
1027 return(1);
1028 }
1029
1030
1031 /* ARGSUSED */
1032 static int
1033 termp_pp_pre(DECL_ARGS)
1034 {
1035
1036 term_vspace(p);
1037 return(1);
1038 }
1039
1040
1041 /* ARGSUSED */
1042 static int
1043 termp_rs_pre(DECL_ARGS)
1044 {
1045
1046 if (MDOC_BLOCK == node->type && node->prev)
1047 term_vspace(p);
1048 return(1);
1049 }
1050
1051
1052 /* ARGSUSED */
1053 static int
1054 termp_rv_pre(DECL_ARGS)
1055 {
1056 int i;
1057
1058 i = arg_getattr(MDOC_Std, node);
1059 assert(-1 != i);
1060 assert(node->args->argv[i].sz);
1061
1062 term_newln(p);
1063 term_word(p, "The");
1064
1065 p->flags |= ttypes[TTYPE_FUNC_NAME];
1066 term_word(p, *node->args->argv[i].value);
1067 p->flags &= ~ttypes[TTYPE_FUNC_NAME];
1068 p->flags |= TERMP_NOSPACE;
1069
1070 term_word(p, "() function returns the value 0 if successful;");
1071 term_word(p, "otherwise the value -1 is returned and the");
1072 term_word(p, "global variable");
1073
1074 p->flags |= ttypes[TTYPE_VAR_DECL];
1075 term_word(p, "errno");
1076 p->flags &= ~ttypes[TTYPE_VAR_DECL];
1077
1078 term_word(p, "is set to indicate the error.");
1079
1080 return(1);
1081 }
1082
1083
1084 /* ARGSUSED */
1085 static int
1086 termp_ex_pre(DECL_ARGS)
1087 {
1088 int i;
1089
1090 i = arg_getattr(MDOC_Std, node);
1091 assert(-1 != i);
1092 assert(node->args->argv[i].sz);
1093
1094 term_word(p, "The");
1095 p->flags |= ttypes[TTYPE_PROG];
1096 term_word(p, *node->args->argv[i].value);
1097 p->flags &= ~ttypes[TTYPE_PROG];
1098 term_word(p, "utility exits 0 on success, and >0 if an error occurs.");
1099
1100 return(1);
1101 }
1102
1103
1104 /* ARGSUSED */
1105 static int
1106 termp_nd_pre(DECL_ARGS)
1107 {
1108
1109 if (MDOC_BODY != node->type)
1110 return(1);
1111
1112 /*
1113 * XXX: signed off by jmc@openbsd.org. This technically
1114 * produces a minus sign after the Nd, which is wrong, but is
1115 * consistent with the historic OpenBSD tmac file.
1116 */
1117 #if defined(__OpenBSD__) || defined(__linux__)
1118 term_word(p, "\\-");
1119 #else
1120 term_word(p, "\\(em");
1121 #endif
1122 return(1);
1123 }
1124
1125
1126 /* ARGSUSED */
1127 static void
1128 termp_bl_post(DECL_ARGS)
1129 {
1130
1131 if (MDOC_BLOCK == node->type)
1132 term_newln(p);
1133 }
1134
1135
1136 /* ARGSUSED */
1137 static void
1138 termp_op_post(DECL_ARGS)
1139 {
1140
1141 if (MDOC_BODY != node->type)
1142 return;
1143 p->flags |= TERMP_NOSPACE;
1144 term_word(p, "\\(rB");
1145 }
1146
1147
1148 /* ARGSUSED */
1149 static int
1150 termp_xr_pre(DECL_ARGS)
1151 {
1152 const struct mdoc_node *n;
1153
1154 assert(node->child && MDOC_TEXT == node->child->type);
1155 n = node->child;
1156
1157 term_word(p, n->string);
1158 if (NULL == (n = n->next))
1159 return(0);
1160 p->flags |= TERMP_NOSPACE;
1161 term_word(p, "(");
1162 p->flags |= TERMP_NOSPACE;
1163 term_word(p, n->string);
1164 p->flags |= TERMP_NOSPACE;
1165 term_word(p, ")");
1166 return(0);
1167 }
1168
1169
1170 /* ARGSUSED */
1171 static int
1172 termp_vt_pre(DECL_ARGS)
1173 {
1174
1175 /* FIXME: this can be "type name". */
1176 pair->flag |= ttypes[TTYPE_VAR_DECL];
1177 return(1);
1178 }
1179
1180
1181 /* ARGSUSED */
1182 static void
1183 termp_vt_post(DECL_ARGS)
1184 {
1185
1186 if (node->sec != SEC_SYNOPSIS)
1187 return;
1188 if (node->next && MDOC_Vt == node->next->tok)
1189 term_newln(p);
1190 else if (node->next)
1191 term_vspace(p);
1192 }
1193
1194
1195 /* ARGSUSED */
1196 static int
1197 termp_fd_pre(DECL_ARGS)
1198 {
1199
1200 pair->flag |= ttypes[TTYPE_FUNC_DECL];
1201 return(1);
1202 }
1203
1204
1205 /* ARGSUSED */
1206 static void
1207 termp_fd_post(DECL_ARGS)
1208 {
1209
1210 if (node->sec != SEC_SYNOPSIS)
1211 return;
1212
1213 term_newln(p);
1214 if (node->next && MDOC_Fd != node->next->tok)
1215 term_vspace(p);
1216 }
1217
1218
1219 /* ARGSUSED */
1220 static int
1221 termp_sh_pre(DECL_ARGS)
1222 {
1223
1224 switch (node->type) {
1225 case (MDOC_HEAD):
1226 term_vspace(p);
1227 pair->flag |= ttypes[TTYPE_SECTION];
1228 break;
1229 case (MDOC_BODY):
1230 p->offset = INDENT;
1231 break;
1232 default:
1233 break;
1234 }
1235 return(1);
1236 }
1237
1238
1239 /* ARGSUSED */
1240 static void
1241 termp_sh_post(DECL_ARGS)
1242 {
1243
1244 switch (node->type) {
1245 case (MDOC_HEAD):
1246 term_newln(p);
1247 break;
1248 case (MDOC_BODY):
1249 term_newln(p);
1250 p->offset = 0;
1251 break;
1252 default:
1253 break;
1254 }
1255 }
1256
1257
1258 /* ARGSUSED */
1259 static int
1260 termp_op_pre(DECL_ARGS)
1261 {
1262
1263 switch (node->type) {
1264 case (MDOC_BODY):
1265 term_word(p, "\\(lB");
1266 p->flags |= TERMP_NOSPACE;
1267 break;
1268 default:
1269 break;
1270 }
1271 return(1);
1272 }
1273
1274
1275 /* ARGSUSED */
1276 static int
1277 termp_bt_pre(DECL_ARGS)
1278 {
1279
1280 term_word(p, "is currently in beta test.");
1281 return(1);
1282 }
1283
1284
1285 /* ARGSUSED */
1286 static void
1287 termp_lb_post(DECL_ARGS)
1288 {
1289
1290 term_newln(p);
1291 }
1292
1293
1294 /* ARGSUSED */
1295 static int
1296 termp_ud_pre(DECL_ARGS)
1297 {
1298
1299 term_word(p, "currently under development.");
1300 return(1);
1301 }
1302
1303
1304 /* ARGSUSED */
1305 static int
1306 termp_d1_pre(DECL_ARGS)
1307 {
1308
1309 if (MDOC_BLOCK != node->type)
1310 return(1);
1311 term_newln(p);
1312 p->offset += (INDENT + 1);
1313 return(1);
1314 }
1315
1316
1317 /* ARGSUSED */
1318 static void
1319 termp_d1_post(DECL_ARGS)
1320 {
1321
1322 if (MDOC_BLOCK != node->type)
1323 return;
1324 term_newln(p);
1325 }
1326
1327
1328 /* ARGSUSED */
1329 static int
1330 termp_aq_pre(DECL_ARGS)
1331 {
1332
1333 if (MDOC_BODY != node->type)
1334 return(1);
1335 term_word(p, "\\(la");
1336 p->flags |= TERMP_NOSPACE;
1337 return(1);
1338 }
1339
1340
1341 /* ARGSUSED */
1342 static void
1343 termp_aq_post(DECL_ARGS)
1344 {
1345
1346 if (MDOC_BODY != node->type)
1347 return;
1348 p->flags |= TERMP_NOSPACE;
1349 term_word(p, "\\(ra");
1350 }
1351
1352
1353 /* ARGSUSED */
1354 static int
1355 termp_ft_pre(DECL_ARGS)
1356 {
1357
1358 if (SEC_SYNOPSIS == node->sec)
1359 if (node->prev && MDOC_Fo == node->prev->tok)
1360 term_vspace(p);
1361 pair->flag |= ttypes[TTYPE_FUNC_TYPE];
1362 return(1);
1363 }
1364
1365
1366 /* ARGSUSED */
1367 static void
1368 termp_ft_post(DECL_ARGS)
1369 {
1370
1371 if (SEC_SYNOPSIS == node->sec)
1372 term_newln(p);
1373 }
1374
1375
1376 /* ARGSUSED */
1377 static int
1378 termp_fn_pre(DECL_ARGS)
1379 {
1380 const struct mdoc_node *n;
1381
1382 assert(node->child && MDOC_TEXT == node->child->type);
1383
1384 /* FIXME: can be "type funcname" "type varname"... */
1385
1386 p->flags |= ttypes[TTYPE_FUNC_NAME];
1387 term_word(p, node->child->string);
1388 p->flags &= ~ttypes[TTYPE_FUNC_NAME];
1389
1390 p->flags |= TERMP_NOSPACE;
1391 term_word(p, "(");
1392
1393 for (n = node->child->next; n; n = n->next) {
1394 p->flags |= ttypes[TTYPE_FUNC_ARG];
1395 term_word(p, n->string);
1396 p->flags &= ~ttypes[TTYPE_FUNC_ARG];
1397 if (n->next)
1398 term_word(p, ",");
1399 }
1400
1401 term_word(p, ")");
1402
1403 if (SEC_SYNOPSIS == node->sec)
1404 term_word(p, ";");
1405
1406 return(0);
1407 }
1408
1409
1410 /* ARGSUSED */
1411 static void
1412 termp_fn_post(DECL_ARGS)
1413 {
1414
1415 if (node->sec == SEC_SYNOPSIS && node->next)
1416 term_vspace(p);
1417 }
1418
1419
1420 /* ARGSUSED */
1421 static int
1422 termp_sx_pre(DECL_ARGS)
1423 {
1424
1425 pair->flag |= ttypes[TTYPE_LINK];
1426 return(1);
1427 }
1428
1429
1430 /* ARGSUSED */
1431 static int
1432 termp_fa_pre(DECL_ARGS)
1433 {
1434 struct mdoc_node *n;
1435
1436 if (node->parent->tok != MDOC_Fo) {
1437 pair->flag |= ttypes[TTYPE_FUNC_ARG];
1438 return(1);
1439 }
1440
1441 for (n = node->child; n; n = n->next) {
1442 p->flags |= ttypes[TTYPE_FUNC_ARG];
1443 term_word(p, n->string);
1444 p->flags &= ~ttypes[TTYPE_FUNC_ARG];
1445 if (n->next)
1446 term_word(p, ",");
1447 }
1448
1449 if (node->child && node->next && node->next->tok == MDOC_Fa)
1450 term_word(p, ",");
1451
1452 return(0);
1453 }
1454
1455
1456 /* ARGSUSED */
1457 static int
1458 termp_va_pre(DECL_ARGS)
1459 {
1460
1461 pair->flag |= ttypes[TTYPE_VAR_DECL];
1462 return(1);
1463 }
1464
1465
1466 /* ARGSUSED */
1467 static int
1468 termp_bd_pre(DECL_ARGS)
1469 {
1470 int i, type, ln;
1471
1472 /*
1473 * This is fairly tricky due primarily to crappy documentation.
1474 * If -ragged or -filled are specified, the block does nothing
1475 * but change the indentation.
1476 *
1477 * If, on the other hand, -unfilled or -literal are specified,
1478 * then the game changes. Text is printed exactly as entered in
1479 * the display: if a macro line, a newline is appended to the
1480 * line. Blank lines are allowed.
1481 */
1482
1483 if (MDOC_BLOCK == node->type)
1484 return(fmt_block_vspace(p, node, node));
1485 else if (MDOC_BODY != node->type)
1486 return(1);
1487
1488 /* FIXME: display type should be mandated by parser. */
1489
1490 if (NULL == node->parent->args)
1491 errx(1, "missing display type");
1492
1493 for (type = -1, i = 0;
1494 i < (int)node->parent->args->argc; i++) {
1495 switch (node->parent->args->argv[i].arg) {
1496 case (MDOC_Ragged):
1497 /* FALLTHROUGH */
1498 case (MDOC_Filled):
1499 /* FALLTHROUGH */
1500 case (MDOC_Unfilled):
1501 /* FALLTHROUGH */
1502 case (MDOC_Literal):
1503 type = node->parent->args->argv[i].arg;
1504 i = (int)node->parent->args->argc;
1505 break;
1506 default:
1507 break;
1508 }
1509 }
1510
1511 if (NULL == node->parent->args)
1512 errx(1, "missing display type");
1513
1514 i = arg_getattr(MDOC_Offset, node->parent);
1515 if (-1 != i) {
1516 if (1 != node->parent->args->argv[i].sz)
1517 errx(1, "expected single value");
1518 p->offset += arg_offset(&node->parent->args->argv[i]);
1519 }
1520
1521 switch (type) {
1522 case (MDOC_Literal):
1523 /* FALLTHROUGH */
1524 case (MDOC_Unfilled):
1525 break;
1526 default:
1527 return(1);
1528 }
1529
1530 /*
1531 * Tricky. Iterate through all children. If we're on a
1532 * different parse line, append a newline and then the contents.
1533 * Ew.
1534 */
1535
1536 p->flags |= TERMP_LITERAL;
1537 ln = node->child ? node->child->line : 0;
1538
1539 for (node = node->child; node; node = node->next) {
1540 if (ln < node->line) {
1541 term_flushln(p);
1542 p->flags |= TERMP_NOSPACE;
1543 }
1544 ln = node->line;
1545 print_node(p, pair, meta, node);
1546 }
1547
1548 return(0);
1549 }
1550
1551
1552 /* ARGSUSED */
1553 static void
1554 termp_bd_post(DECL_ARGS)
1555 {
1556
1557 if (MDOC_BODY != node->type)
1558 return;
1559
1560 term_flushln(p);
1561 p->flags &= ~TERMP_LITERAL;
1562 p->flags |= TERMP_NOSPACE;
1563 }
1564
1565
1566 /* ARGSUSED */
1567 static int
1568 termp_qq_pre(DECL_ARGS)
1569 {
1570
1571 if (MDOC_BODY != node->type)
1572 return(1);
1573 term_word(p, "\"");
1574 p->flags |= TERMP_NOSPACE;
1575 return(1);
1576 }
1577
1578
1579 /* ARGSUSED */
1580 static void
1581 termp_qq_post(DECL_ARGS)
1582 {
1583
1584 if (MDOC_BODY != node->type)
1585 return;
1586 p->flags |= TERMP_NOSPACE;
1587 term_word(p, "\"");
1588 }
1589
1590
1591 /* ARGSUSED */
1592 static void
1593 termp_bx_post(DECL_ARGS)
1594 {
1595
1596 if (node->child)
1597 p->flags |= TERMP_NOSPACE;
1598 term_word(p, "BSD");
1599 }
1600
1601
1602 /* ARGSUSED */
1603 static int
1604 termp_xx_pre(DECL_ARGS)
1605 {
1606 const char *pp;
1607
1608 pp = NULL;
1609 switch (node->tok) {
1610 case (MDOC_Bsx):
1611 pp = "BSDI BSD/OS";
1612 break;
1613 case (MDOC_Dx):
1614 pp = "DragonFlyBSD";
1615 break;
1616 case (MDOC_Fx):
1617 pp = "FreeBSD";
1618 break;
1619 case (MDOC_Nx):
1620 pp = "NetBSD";
1621 break;
1622 case (MDOC_Ox):
1623 pp = "OpenBSD";
1624 break;
1625 case (MDOC_Ux):
1626 pp = "UNIX";
1627 break;
1628 default:
1629 break;
1630 }
1631
1632 assert(pp);
1633 term_word(p, pp);
1634 return(1);
1635 }
1636
1637
1638 /* ARGSUSED */
1639 static int
1640 termp_sq_pre(DECL_ARGS)
1641 {
1642
1643 if (MDOC_BODY != node->type)
1644 return(1);
1645 term_word(p, "\\(oq");
1646 p->flags |= TERMP_NOSPACE;
1647 return(1);
1648 }
1649
1650
1651 /* ARGSUSED */
1652 static void
1653 termp_sq_post(DECL_ARGS)
1654 {
1655
1656 if (MDOC_BODY != node->type)
1657 return;
1658 p->flags |= TERMP_NOSPACE;
1659 term_word(p, "\\(aq");
1660 }
1661
1662
1663 /* ARGSUSED */
1664 static int
1665 termp_pf_pre(DECL_ARGS)
1666 {
1667
1668 p->flags |= TERMP_IGNDELIM;
1669 return(1);
1670 }
1671
1672
1673 /* ARGSUSED */
1674 static void
1675 termp_pf_post(DECL_ARGS)
1676 {
1677
1678 p->flags &= ~TERMP_IGNDELIM;
1679 p->flags |= TERMP_NOSPACE;
1680 }
1681
1682
1683 /* ARGSUSED */
1684 static int
1685 termp_ss_pre(DECL_ARGS)
1686 {
1687
1688 switch (node->type) {
1689 case (MDOC_BLOCK):
1690 term_newln(p);
1691 if (node->prev)
1692 term_vspace(p);
1693 break;
1694 case (MDOC_HEAD):
1695 pair->flag |= ttypes[TTYPE_SSECTION];
1696 p->offset = HALFINDENT;
1697 break;
1698 default:
1699 break;
1700 }
1701
1702 return(1);
1703 }
1704
1705
1706 /* ARGSUSED */
1707 static void
1708 termp_ss_post(DECL_ARGS)
1709 {
1710
1711 if (MDOC_HEAD == node->type)
1712 term_newln(p);
1713 }
1714
1715
1716 /* ARGSUSED */
1717 static int
1718 termp_pa_pre(DECL_ARGS)
1719 {
1720
1721 pair->flag |= ttypes[TTYPE_FILE];
1722 return(1);
1723 }
1724
1725
1726 /* ARGSUSED */
1727 static int
1728 termp_em_pre(DECL_ARGS)
1729 {
1730
1731 pair->flag |= ttypes[TTYPE_EMPH];
1732 return(1);
1733 }
1734
1735
1736 /* ARGSUSED */
1737 static int
1738 termp_cd_pre(DECL_ARGS)
1739 {
1740
1741 pair->flag |= ttypes[TTYPE_CONFIG];
1742 term_newln(p);
1743 return(1);
1744 }
1745
1746
1747 /* ARGSUSED */
1748 static int
1749 termp_cm_pre(DECL_ARGS)
1750 {
1751
1752 pair->flag |= ttypes[TTYPE_CMD_FLAG];
1753 return(1);
1754 }
1755
1756
1757 /* ARGSUSED */
1758 static int
1759 termp_ic_pre(DECL_ARGS)
1760 {
1761
1762 pair->flag |= ttypes[TTYPE_CMD];
1763 return(1);
1764 }
1765
1766
1767 /* ARGSUSED */
1768 static int
1769 termp_in_pre(DECL_ARGS)
1770 {
1771
1772 pair->flag |= ttypes[TTYPE_INCLUDE];
1773 p->flags |= ttypes[TTYPE_INCLUDE];
1774
1775 if (SEC_SYNOPSIS == node->sec)
1776 term_word(p, "#include");
1777
1778 term_word(p, "<");
1779 p->flags |= TERMP_NOSPACE;
1780 return(1);
1781 }
1782
1783
1784 /* ARGSUSED */
1785 static void
1786 termp_in_post(DECL_ARGS)
1787 {
1788
1789 p->flags |= TERMP_NOSPACE;
1790 term_word(p, ">");
1791
1792 if (SEC_SYNOPSIS != node->sec)
1793 return;
1794
1795 term_newln(p);
1796 /*
1797 * XXX Not entirely correct. If `.In foo bar' is specified in
1798 * the SYNOPSIS section, then it produces a single break after
1799 * the <foo>; mandoc asserts a vertical space. Since this
1800 * construction is rarely used, I think it's fine.
1801 */
1802 if (node->next && MDOC_In != node->next->tok)
1803 term_vspace(p);
1804 }
1805
1806
1807 /* ARGSUSED */
1808 static int
1809 termp_brq_pre(DECL_ARGS)
1810 {
1811
1812 if (MDOC_BODY != node->type)
1813 return(1);
1814 term_word(p, "\\(lC");
1815 p->flags |= TERMP_NOSPACE;
1816 return(1);
1817 }
1818
1819
1820 /* ARGSUSED */
1821 static void
1822 termp_brq_post(DECL_ARGS)
1823 {
1824
1825 if (MDOC_BODY != node->type)
1826 return;
1827 p->flags |= TERMP_NOSPACE;
1828 term_word(p, "\\(rC");
1829 }
1830
1831
1832 /* ARGSUSED */
1833 static int
1834 termp_bq_pre(DECL_ARGS)
1835 {
1836
1837 if (MDOC_BODY != node->type)
1838 return(1);
1839 term_word(p, "\\(lB");
1840 p->flags |= TERMP_NOSPACE;
1841 return(1);
1842 }
1843
1844
1845 /* ARGSUSED */
1846 static void
1847 termp_bq_post(DECL_ARGS)
1848 {
1849
1850 if (MDOC_BODY != node->type)
1851 return;
1852 p->flags |= TERMP_NOSPACE;
1853 term_word(p, "\\(rB");
1854 }
1855
1856
1857 /* ARGSUSED */
1858 static int
1859 termp_pq_pre(DECL_ARGS)
1860 {
1861
1862 if (MDOC_BODY != node->type)
1863 return(1);
1864 term_word(p, "\\&(");
1865 p->flags |= TERMP_NOSPACE;
1866 return(1);
1867 }
1868
1869
1870 /* ARGSUSED */
1871 static void
1872 termp_pq_post(DECL_ARGS)
1873 {
1874
1875 if (MDOC_BODY != node->type)
1876 return;
1877 term_word(p, ")");
1878 }
1879
1880
1881 /* ARGSUSED */
1882 static int
1883 termp_fo_pre(DECL_ARGS)
1884 {
1885 const struct mdoc_node *n;
1886
1887 if (MDOC_BODY == node->type) {
1888 p->flags |= TERMP_NOSPACE;
1889 term_word(p, "(");
1890 p->flags |= TERMP_NOSPACE;
1891 return(1);
1892 } else if (MDOC_HEAD != node->type)
1893 return(1);
1894
1895 p->flags |= ttypes[TTYPE_FUNC_NAME];
1896 for (n = node->child; n; n = n->next) {
1897 assert(MDOC_TEXT == n->type);
1898 term_word(p, n->string);
1899 }
1900 p->flags &= ~ttypes[TTYPE_FUNC_NAME];
1901
1902 return(0);
1903 }
1904
1905
1906 /* ARGSUSED */
1907 static void
1908 termp_fo_post(DECL_ARGS)
1909 {
1910
1911 if (MDOC_BODY != node->type)
1912 return;
1913 p->flags |= TERMP_NOSPACE;
1914 term_word(p, ")");
1915 p->flags |= TERMP_NOSPACE;
1916 term_word(p, ";");
1917 term_newln(p);
1918 }
1919
1920
1921 /* ARGSUSED */
1922 static int
1923 termp_bf_pre(DECL_ARGS)
1924 {
1925 const struct mdoc_node *n;
1926
1927 if (MDOC_HEAD == node->type)
1928 return(0);
1929 else if (MDOC_BLOCK != node->type)
1930 return(1);
1931
1932 if (NULL == (n = node->head->child)) {
1933 if (arg_hasattr(MDOC_Emphasis, node))
1934 pair->flag |= ttypes[TTYPE_EMPH];
1935 else if (arg_hasattr(MDOC_Symbolic, node))
1936 pair->flag |= ttypes[TTYPE_SYMB];
1937
1938 return(1);
1939 }
1940
1941 assert(MDOC_TEXT == n->type);
1942 if (0 == strcmp("Em", n->string))
1943 pair->flag |= ttypes[TTYPE_EMPH];
1944 else if (0 == strcmp("Sy", n->string))
1945 pair->flag |= ttypes[TTYPE_SYMB];
1946
1947 return(1);
1948 }
1949
1950
1951 /* ARGSUSED */
1952 static int
1953 termp_sy_pre(DECL_ARGS)
1954 {
1955
1956 pair->flag |= ttypes[TTYPE_SYMB];
1957 return(1);
1958 }
1959
1960
1961 /* ARGSUSED */
1962 static int
1963 termp_ms_pre(DECL_ARGS)
1964 {
1965
1966 pair->flag |= ttypes[TTYPE_SYMBOL];
1967 return(1);
1968 }
1969
1970
1971
1972 /* ARGSUSED */
1973 static int
1974 termp_sm_pre(DECL_ARGS)
1975 {
1976
1977 assert(node->child && MDOC_TEXT == node->child->type);
1978 if (0 == strcmp("on", node->child->string)) {
1979 p->flags &= ~TERMP_NONOSPACE;
1980 p->flags &= ~TERMP_NOSPACE;
1981 } else
1982 p->flags |= TERMP_NONOSPACE;
1983
1984 return(0);
1985 }
1986
1987
1988 /* ARGSUSED */
1989 static int
1990 termp_ap_pre(DECL_ARGS)
1991 {
1992
1993 p->flags |= TERMP_NOSPACE;
1994 term_word(p, "\\(aq");
1995 p->flags |= TERMP_NOSPACE;
1996 return(1);
1997 }
1998
1999
2000 /* ARGSUSED */
2001 static int
2002 termp__j_pre(DECL_ARGS)
2003 {
2004
2005 pair->flag |= ttypes[TTYPE_REF_JOURNAL];
2006 return(1);
2007 }
2008
2009
2010 /* ARGSUSED */
2011 static int
2012 termp__t_pre(DECL_ARGS)
2013 {
2014
2015 term_word(p, "\"");
2016 p->flags |= TERMP_NOSPACE;
2017 return(1);
2018 }
2019
2020
2021 /* ARGSUSED */
2022 static void
2023 termp__t_post(DECL_ARGS)
2024 {
2025
2026 p->flags |= TERMP_NOSPACE;
2027 term_word(p, "\"");
2028 termp____post(p, pair, meta, node);
2029 }
2030
2031
2032 /* ARGSUSED */
2033 static void
2034 termp____post(DECL_ARGS)
2035 {
2036
2037 p->flags |= TERMP_NOSPACE;
2038 term_word(p, node->next ? "," : ".");
2039 }
2040
2041
2042 /* ARGSUSED */
2043 static int
2044 termp_lk_pre(DECL_ARGS)
2045 {
2046 const struct mdoc_node *n;
2047
2048 assert(node->child);
2049 n = node->child;
2050
2051 if (NULL == n->next) {
2052 pair->flag |= ttypes[TTYPE_LINK_ANCHOR];
2053 return(1);
2054 }
2055
2056 p->flags |= ttypes[TTYPE_LINK_ANCHOR];
2057 term_word(p, n->string);
2058 p->flags |= TERMP_NOSPACE;
2059 term_word(p, ":");
2060 p->flags &= ~ttypes[TTYPE_LINK_ANCHOR];
2061
2062 p->flags |= ttypes[TTYPE_LINK_TEXT];
2063 for (n = n->next; n; n = n->next)
2064 term_word(p, n->string);
2065
2066 p->flags &= ~ttypes[TTYPE_LINK_TEXT];
2067 return(0);
2068 }
2069
2070
2071 /* ARGSUSED */
2072 static int
2073 termp_mt_pre(DECL_ARGS)
2074 {
2075
2076 pair->flag |= ttypes[TTYPE_LINK_ANCHOR];
2077 return(1);
2078 }
2079
2080