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