Annotation of mandoc/term.c, Revision 1.4
1.4 ! kristaps 1: /* $Id: term.c,v 1.3 2009/02/21 14:56:58 kristaps Exp $ */
1.1 kristaps 2: /*
3: * Copyright (c) 2008 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
7: * above copyright notice and this permission notice appear in all
8: * copies.
9: *
10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
11: * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
12: * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
13: * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
14: * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
15: * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
16: * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17: * PERFORMANCE OF THIS SOFTWARE.
18: */
19: #include <assert.h>
1.2 kristaps 20: #include <ctype.h>
1.1 kristaps 21: #include <curses.h>
22: #include <err.h>
23: #include <stdlib.h>
24: #include <stdio.h>
25: #include <string.h>
26: #include <term.h>
27: #include <unistd.h>
28:
1.2 kristaps 29: #include "private.h"
1.1 kristaps 30:
1.2 kristaps 31: enum termesc {
32: ESC_CLEAR,
33: ESC_BOLD,
34: ESC_UNDERLINE
35: };
36:
37: struct termp {
1.3 kristaps 38: size_t rmargin;
39: size_t maxrmargin;
1.2 kristaps 40: size_t maxcols;
1.3 kristaps 41: size_t offset;
1.2 kristaps 42: size_t col;
43: int flags;
1.3 kristaps 44: #define TERMP_BOLD (1 << 0) /* Embolden words. */
45: #define TERMP_UNDERLINE (1 << 1) /* Underline words. */
46: #define TERMP_NOSPACE (1 << 2) /* No space before words. */
47: #define TERMP_NOLPAD (1 << 3) /* No left-padding. */
48: #define TERMP_NOBREAK (1 << 4) /* No break after line */
1.2 kristaps 49: char *buf;
50: };
51:
52: struct termact {
53: int (*pre)(struct termp *,
54: const struct mdoc_meta *,
1.1 kristaps 55: const struct mdoc_node *);
1.2 kristaps 56: int (*post)(struct termp *,
57: const struct mdoc_meta *,
58: const struct mdoc_node *);
59: };
60:
61: static void termprint_r(struct termp *,
62: const struct mdoc_meta *,
63: const struct mdoc_node *);
64: static void termprint_header(struct termp *,
1.1 kristaps 65: const struct mdoc_meta *);
1.2 kristaps 66: static void termprint_footer(struct termp *,
1.1 kristaps 67: const struct mdoc_meta *);
68:
1.3 kristaps 69: static int arg_hasattr(int, size_t,
70: const struct mdoc_arg *);
71: static int arg_getattr(int, size_t,
72: const struct mdoc_arg *);
73:
1.2 kristaps 74: static void newln(struct termp *);
75: static void vspace(struct termp *);
76: static void pword(struct termp *, const char *, size_t);
77: static void word(struct termp *, const char *);
78:
1.3 kristaps 79: #define decl_prepost(name, suffix) \
80: static int name##_##suffix(struct termp *, \
81: const struct mdoc_meta *, \
82: const struct mdoc_node *)
83:
84: #define decl_pre(name) decl_prepost(name, pre)
85: #define decl_post(name) decl_prepost(name, post)
86:
87: decl_pre(termp_fl);
88: decl_pre(termp_it);
89: decl_pre(termp_nd);
90: decl_pre(termp_ns);
91: decl_pre(termp_op);
92: decl_pre(termp_pp);
93: decl_pre(termp_sh);
1.4 ! kristaps 94: decl_pre(termp_xr);
1.3 kristaps 95:
96: decl_post(termp_bl);
97: decl_post(termp_it);
98: decl_post(termp_op);
99: decl_post(termp_sh);
100:
101: decl_pre(termp_bold);
102: decl_pre(termp_under);
103:
104: decl_post(termp_bold);
105: decl_post(termp_under);
1.2 kristaps 106:
107: const struct termact termacts[MDOC_MAX] = {
108: { NULL, NULL }, /* \" */
109: { NULL, NULL }, /* Dd */
110: { NULL, NULL }, /* Dt */
111: { NULL, NULL }, /* Os */
112: { termp_sh_pre, termp_sh_post }, /* Sh */
113: { NULL, NULL }, /* Ss */
114: { termp_pp_pre, NULL }, /* Pp */
115: { NULL, NULL }, /* D1 */
116: { NULL, NULL }, /* Dl */
117: { NULL, NULL }, /* Bd */
118: { NULL, NULL }, /* Ed */
119: { NULL, termp_bl_post }, /* Bl */
120: { NULL, NULL }, /* El */
1.3 kristaps 121: { termp_it_pre, termp_it_post }, /* It */
1.2 kristaps 122: { NULL, NULL }, /* Ad */
123: { NULL, NULL }, /* An */
124: { termp_under_pre, termp_under_post }, /* Ar */
125: { NULL, NULL }, /* Cd */
126: { NULL, NULL }, /* Cm */
127: { NULL, NULL }, /* Dv */
128: { NULL, NULL }, /* Er */
129: { NULL, NULL }, /* Ev */
130: { NULL, NULL }, /* Ex */
131: { NULL, NULL }, /* Fa */
132: { NULL, NULL }, /* Fd */
133: { termp_fl_pre, termp_bold_post }, /* Fl */
134: { NULL, NULL }, /* Fn */
135: { NULL, NULL }, /* Ft */
136: { NULL, NULL }, /* Ic */
137: { NULL, NULL }, /* In */
138: { NULL, NULL }, /* Li */
139: { termp_nd_pre, NULL }, /* Nd */
140: { termp_bold_pre, termp_bold_post }, /* Nm */
141: { termp_op_pre, termp_op_post }, /* Op */
142: { NULL, NULL }, /* Ot */
143: { NULL, NULL }, /* Pa */
144: { NULL, NULL }, /* Rv */
145: { NULL, NULL }, /* St */
146: { NULL, NULL }, /* Va */
147: { NULL, NULL }, /* Vt */
1.4 ! kristaps 148: { termp_xr_pre, NULL }, /* Xr */
1.2 kristaps 149: { NULL, NULL }, /* %A */
150: { NULL, NULL }, /* %B */
151: { NULL, NULL }, /* %D */
152: { NULL, NULL }, /* %I */
153: { NULL, NULL }, /* %J */
154: { NULL, NULL }, /* %N */
155: { NULL, NULL }, /* %O */
156: { NULL, NULL }, /* %P */
157: { NULL, NULL }, /* %R */
158: { NULL, NULL }, /* %T */
159: { NULL, NULL }, /* %V */
160: { NULL, NULL }, /* Ac */
161: { NULL, NULL }, /* Ao */
162: { NULL, NULL }, /* Aq */
163: { NULL, NULL }, /* At */
164: { NULL, NULL }, /* Bc */
165: { NULL, NULL }, /* Bf */
166: { NULL, NULL }, /* Bo */
167: { NULL, NULL }, /* Bq */
168: { NULL, NULL }, /* Bsx */
169: { NULL, NULL }, /* Bx */
170: { NULL, NULL }, /* Db */
171: { NULL, NULL }, /* Dc */
172: { NULL, NULL }, /* Do */
173: { NULL, NULL }, /* Dq */
174: { NULL, NULL }, /* Ec */
175: { NULL, NULL }, /* Ef */
176: { NULL, NULL }, /* Em */
177: { NULL, NULL }, /* Eo */
178: { NULL, NULL }, /* Fx */
179: { NULL, NULL }, /* Ms */
180: { NULL, NULL }, /* No */
181: { termp_ns_pre, NULL }, /* Ns */
182: { NULL, NULL }, /* Nx */
183: { NULL, NULL }, /* Ox */
184: { NULL, NULL }, /* Pc */
185: { NULL, NULL }, /* Pf */
186: { NULL, NULL }, /* Po */
187: { NULL, NULL }, /* Pq */
188: { NULL, NULL }, /* Qc */
189: { NULL, NULL }, /* Ql */
190: { NULL, NULL }, /* Qo */
191: { NULL, NULL }, /* Qq */
192: { NULL, NULL }, /* Re */
193: { NULL, NULL }, /* Rs */
194: { NULL, NULL }, /* Sc */
195: { NULL, NULL }, /* So */
196: { NULL, NULL }, /* Sq */
197: { NULL, NULL }, /* Sm */
198: { NULL, NULL }, /* Sx */
199: { NULL, NULL }, /* Sy */
200: { NULL, NULL }, /* Tn */
201: { NULL, NULL }, /* Ux */
202: { NULL, NULL }, /* Xc */
203: { NULL, NULL }, /* Xo */
204: { NULL, NULL }, /* Fo */
205: { NULL, NULL }, /* Fc */
206: { NULL, NULL }, /* Oo */
207: { NULL, NULL }, /* Oc */
208: { NULL, NULL }, /* Bk */
209: { NULL, NULL }, /* Ek */
210: { NULL, NULL }, /* Bt */
211: { NULL, NULL }, /* Hf */
212: { NULL, NULL }, /* Fr */
213: { NULL, NULL }, /* Ud */
214: };
215:
216:
1.3 kristaps 217: static int
218: arg_hasattr(int arg, size_t argc, const struct mdoc_arg *argv)
219: {
220:
221: return(-1 != arg_getattr(arg, argc, argv));
222: }
223:
224:
225: static int
226: arg_getattr(int arg, size_t argc, const struct mdoc_arg *argv)
227: {
228: int i;
229:
230: for (i = 0; i < (int)argc; i++)
231: if (argv[i].arg == arg)
232: return(i);
233: return(-1);
234: }
235:
236:
1.2 kristaps 237: static void
1.3 kristaps 238: flushln(struct termp *p)
1.2 kristaps 239: {
240: size_t i, j, vsz, vis, maxvis;
241:
1.3 kristaps 242: /*
243: * First, establish the maximum columns of "visible" content.
244: * This is usually the difference between the right-margin and
245: * an indentation, but can be, for tagged lists or columns, a
246: * small set of values.
247: */
248:
249: assert(p->offset < p->rmargin);
250: maxvis = p->rmargin - p->offset;
1.2 kristaps 251: vis = 0;
252:
1.3 kristaps 253: /*
254: * If in the standard case (left-justified), then begin with our
255: * indentation, otherwise (columns, etc.) just start spitting
256: * out text.
257: */
258:
259: if ( ! (p->flags & TERMP_NOLPAD))
260: /* LINTED */
261: for (j = 0; j < p->offset; j++)
262: putchar(' ');
1.2 kristaps 263:
264: for (i = 0; i < p->col; i++) {
1.3 kristaps 265: /*
266: * Count up visible word characters. Control sequences
267: * (starting with the CSI) aren't counted.
268: */
269: assert( ! isspace(p->buf[i]));
270:
271: /* LINTED */
1.2 kristaps 272: for (j = i, vsz = 0; j < p->col; j++) {
273: if (isspace(p->buf[j]))
274: break;
275: else if (27 == p->buf[j]) {
276: assert(j + 4 <= p->col);
277: j += 3;
278: } else
279: vsz++;
280: }
281: assert(vsz > 0);
282:
1.3 kristaps 283: /*
284: * If a word is too long and we're within a line, put it
285: * on the next line. Puke if we're being asked to write
286: * something that will exceed the right margin (i.e.,
287: * from a fresh line or when we're not allowed to break
288: * the line with TERMP_NOBREAK).
289: */
290:
1.2 kristaps 291: if (vis && vis + vsz >= maxvis) {
1.3 kristaps 292: /* FIXME */
293: if (p->flags & TERMP_NOBREAK)
294: errx(1, "word breaks right margin");
1.2 kristaps 295: putchar('\n');
1.3 kristaps 296: for (j = 0; j < p->offset; j++)
1.2 kristaps 297: putchar(' ');
298: vis = 0;
1.3 kristaps 299: } else if (vis + vsz >= maxvis) {
300: /* FIXME */
301: errx(1, "word breaks right margin");
302: }
303:
304: /*
305: * Write out the word and a trailing space. Omit the
306: * space if we're the last word in the line.
307: */
1.2 kristaps 308:
309: for ( ; i < p->col; i++) {
310: if (isspace(p->buf[i]))
311: break;
312: putchar(p->buf[i]);
313: }
314: vis += vsz;
315: if (i < p->col) {
316: putchar(' ');
317: vis++;
318: }
319: }
320:
1.3 kristaps 321: /*
322: * If we're not to right-marginalise it (newline), then instead
323: * pad to the right margin and stay off.
324: */
325:
326: if (p->flags & TERMP_NOBREAK) {
327: for ( ; vis <= maxvis; vis++)
328: putchar(' ');
329: } else
330: putchar('\n');
331:
1.2 kristaps 332: p->col = 0;
333: }
334:
335:
336: static void
337: newln(struct termp *p)
338: {
339:
1.3 kristaps 340: /*
341: * A newline only breaks an existing line; it won't assert
342: * vertical space.
343: */
1.2 kristaps 344: p->flags |= TERMP_NOSPACE;
345: if (0 == p->col)
346: return;
1.3 kristaps 347: flushln(p);
1.2 kristaps 348: }
349:
350:
351: static void
352: vspace(struct termp *p)
353: {
354:
1.3 kristaps 355: /*
356: * Asserts a vertical space (a full, empty line-break between
357: * lines).
358: */
1.2 kristaps 359: newln(p);
360: putchar('\n');
361: }
362:
363:
364: static void
365: chara(struct termp *p, char c)
366: {
367:
1.3 kristaps 368: /* TODO: dynamically expand the buffer. */
1.2 kristaps 369: if (p->col + 1 >= p->maxcols)
370: errx(1, "line overrun");
371: p->buf[(p->col)++] = c;
372: }
373:
374:
375: static void
376: escape(struct termp *p, enum termesc esc)
377: {
378:
379: if (p->col + 4 >= p->maxcols)
380: errx(1, "line overrun");
381:
382: p->buf[(p->col)++] = 27;
383: p->buf[(p->col)++] = '[';
384: switch (esc) {
385: case (ESC_CLEAR):
386: p->buf[(p->col)++] = '0';
387: break;
388: case (ESC_BOLD):
389: p->buf[(p->col)++] = '1';
390: break;
391: case (ESC_UNDERLINE):
392: p->buf[(p->col)++] = '4';
393: break;
394: default:
395: abort();
396: /* NOTREACHED */
397: }
398: p->buf[(p->col)++] = 'm';
399: }
400:
401:
402: static void
403: pword(struct termp *p, const char *word, size_t len)
404: {
405: size_t i;
406:
407: assert(len > 0);
408:
409: if ( ! (p->flags & TERMP_NOSPACE))
410: chara(p, ' ');
411:
412: p->flags &= ~TERMP_NOSPACE;
413:
414: if (p->flags & TERMP_BOLD)
415: escape(p, ESC_BOLD);
416: if (p->flags & TERMP_UNDERLINE)
417: escape(p, ESC_UNDERLINE);
418:
1.3 kristaps 419: /* TODO: escape patterns. */
420:
1.2 kristaps 421: for (i = 0; i < len; i++)
422: chara(p, word[i]);
423:
424: if (p->flags & TERMP_BOLD ||
425: p->flags & TERMP_UNDERLINE)
426: escape(p, ESC_CLEAR);
427: }
428:
429:
430: static void
431: word(struct termp *p, const char *word)
432: {
433: size_t i, j, len;
434:
1.3 kristaps 435: if (mdoc_isdelim(word))
436: p->flags |= TERMP_NOSPACE;
1.2 kristaps 437:
438: len = strlen(word);
439: assert(len > 0);
440:
1.3 kristaps 441: /* LINTED */
1.2 kristaps 442: for (j = i = 0; i < len; i++) {
443: if ( ! isspace(word[i])) {
444: j++;
445: continue;
446: }
447: if (0 == j)
448: continue;
449: assert(i >= j);
450: pword(p, &word[i - j], j);
451: j = 0;
452: }
453: if (j > 0) {
454: assert(i >= j);
455: pword(p, &word[i - j], j);
456: }
457: }
458:
459:
1.3 kristaps 460: /* ARGSUSED */
461: static int
462: termp_it_post(struct termp *p, const struct mdoc_meta *meta,
463: const struct mdoc_node *node)
464: {
465: const struct mdoc_node *n, *it;
466: const struct mdoc_block *bl;
467: int i;
468: size_t width;
469:
470: switch (node->type) {
471: case (MDOC_BODY):
472: /* FALLTHROUGH */
473: case (MDOC_HEAD):
474: break;
475: default:
476: return(1);
477: }
478:
479: it = node->parent;
480: assert(MDOC_BLOCK == it->type);
481: assert(MDOC_It == it->tok);
482:
483: n = it->parent;
484: assert(MDOC_BODY == n->type);
485: assert(MDOC_Bl == n->tok);
486: n = n->parent;
487: bl = &n->data.block;
488:
489: /* If `-tag', adjust our margins accordingly. */
490:
491: if (arg_hasattr(MDOC_Tag, bl->argc, bl->argv)) {
492: i = arg_getattr(MDOC_Width, bl->argc, bl->argv);
493: assert(i >= 0);
494: assert(1 == bl->argv[i].sz);
495: width = strlen(*bl->argv[i].value); /* XXX */
496:
497: if (MDOC_HEAD == node->type) {
498: flushln(p);
499: /* FIXME: nested lists. */
500: p->rmargin = p->maxrmargin;
501: p->flags &= ~TERMP_NOBREAK;
502: } else {
503: flushln(p);
504: p->offset -= width + 1;
505: p->flags &= ~TERMP_NOLPAD;
506: }
507: }
508:
509: return(1);
510: }
511:
512:
513: /* ARGSUSED */
1.2 kristaps 514: static int
515: termp_it_pre(struct termp *p, const struct mdoc_meta *meta,
516: const struct mdoc_node *node)
517: {
1.3 kristaps 518: const struct mdoc_node *n, *it;
519: const struct mdoc_block *bl;
520: int i;
521: size_t width;
1.2 kristaps 522:
523: switch (node->type) {
1.3 kristaps 524: case (MDOC_BODY):
525: /* FALLTHROUGH */
1.2 kristaps 526: case (MDOC_HEAD):
1.3 kristaps 527: it = node->parent;
528: break;
529: case (MDOC_BLOCK):
530: it = node;
1.2 kristaps 531: break;
1.1 kristaps 532: default:
1.3 kristaps 533: return(1);
534: }
535:
536: assert(MDOC_BLOCK == it->type);
537: assert(MDOC_It == it->tok);
538:
539: n = it->parent;
540: assert(MDOC_BODY == n->type);
541: assert(MDOC_Bl == n->tok);
542: n = n->parent;
543: bl = &n->data.block;
544:
545: /* If `-compact', don't assert vertical space. */
546:
547: if (MDOC_BLOCK == node->type) {
548: if (arg_hasattr(MDOC_Compact, bl->argc, bl->argv))
549: newln(p);
550: else
551: vspace(p);
552: return(1);
553: }
554:
555: assert(MDOC_HEAD == node->type
556: || MDOC_BODY == node->type);
557:
558: /* If `-tag', adjust our margins accordingly. */
559:
560: if (arg_hasattr(MDOC_Tag, bl->argc, bl->argv)) {
561: i = arg_getattr(MDOC_Width, bl->argc, bl->argv);
562: assert(i >= 0); /* XXX */
563: assert(1 == bl->argv[i].sz);
564: width = strlen(*bl->argv[i].value); /* XXX */
565:
566: /* FIXME: nested lists. */
567:
568: if (MDOC_HEAD == node->type) {
569: p->flags |= TERMP_NOBREAK;
570: p->flags |= TERMP_NOSPACE;
571: p->rmargin = p->offset + width;
572: } else {
573: p->flags |= TERMP_NOSPACE;
574: p->flags |= TERMP_NOLPAD;
575: p->offset += width + 1;
576: }
1.1 kristaps 577: }
1.3 kristaps 578:
1.2 kristaps 579: return(1);
580: }
1.1 kristaps 581:
1.2 kristaps 582:
1.3 kristaps 583: /* ARGSUSED */
1.2 kristaps 584: static int
585: termp_bold_post(struct termp *p, const struct mdoc_meta *meta,
586: const struct mdoc_node *node)
587: {
588:
589: p->flags &= ~TERMP_BOLD;
590: return(1);
591: }
592:
593:
1.3 kristaps 594: /* ARGSUSED */
1.2 kristaps 595: static int
596: termp_under_pre(struct termp *p, const struct mdoc_meta *meta,
597: const struct mdoc_node *node)
598: {
599:
600: p->flags |= TERMP_UNDERLINE;
601: return(1);
1.1 kristaps 602: }
603:
604:
1.3 kristaps 605: /* ARGSUSED */
1.2 kristaps 606: static int
607: termp_bold_pre(struct termp *p, const struct mdoc_meta *meta,
608: const struct mdoc_node *node)
1.1 kristaps 609: {
610:
1.2 kristaps 611: p->flags |= TERMP_BOLD;
612: return(1);
613: }
614:
615:
1.3 kristaps 616: /* ARGSUSED */
1.2 kristaps 617: static int
618: termp_ns_pre(struct termp *p, const struct mdoc_meta *meta,
619: const struct mdoc_node *node)
620: {
621:
622: p->flags |= TERMP_NOSPACE;
623: return(1);
624: }
625:
626:
1.3 kristaps 627: /* ARGSUSED */
1.2 kristaps 628: static int
629: termp_pp_pre(struct termp *p, const struct mdoc_meta *meta,
630: const struct mdoc_node *node)
631: {
632:
633: vspace(p);
634: return(1);
635: }
636:
637:
1.3 kristaps 638: /* ARGSUSED */
1.2 kristaps 639: static int
640: termp_under_post(struct termp *p, const struct mdoc_meta *meta,
641: const struct mdoc_node *node)
642: {
643:
644: p->flags &= ~TERMP_UNDERLINE;
645: return(1);
646: }
647:
648:
1.3 kristaps 649: /* ARGSUSED */
1.2 kristaps 650: static int
651: termp_nd_pre(struct termp *p, const struct mdoc_meta *meta,
652: const struct mdoc_node *node)
653: {
654:
655: word(p, "-");
656: return(1);
657: }
658:
659:
1.3 kristaps 660: /* ARGSUSED */
1.2 kristaps 661: static int
662: termp_bl_post(struct termp *p, const struct mdoc_meta *meta,
663: const struct mdoc_node *node)
664: {
665:
666: switch (node->type) {
667: case (MDOC_BLOCK):
668: newln(p);
669: break;
1.1 kristaps 670: default:
671: break;
672: }
1.2 kristaps 673: return(1);
1.1 kristaps 674: }
675:
676:
1.3 kristaps 677: /* ARGSUSED */
1.2 kristaps 678: static int
679: termp_op_post(struct termp *p, const struct mdoc_meta *meta,
680: const struct mdoc_node *node)
681: {
682:
683: switch (node->type) {
684: case (MDOC_BODY):
685: p->flags |= TERMP_NOSPACE;
1.4 ! kristaps 686: word(p, "\\]");
1.2 kristaps 687: break;
688: default:
689: break;
690: }
691: return(1);
692: }
693:
694:
1.3 kristaps 695: /* ARGSUSED */
1.2 kristaps 696: static int
697: termp_sh_post(struct termp *p, const struct mdoc_meta *meta,
698: const struct mdoc_node *node)
1.1 kristaps 699: {
700:
1.2 kristaps 701: switch (node->type) {
702: case (MDOC_HEAD):
703: p->flags &= ~TERMP_BOLD;
704: newln(p);
705: break;
706: case (MDOC_BODY):
707: newln(p);
1.3 kristaps 708: p->offset -= 4;
1.2 kristaps 709: break;
710: default:
711: break;
712: }
713: return(1);
714: }
715:
1.1 kristaps 716:
1.3 kristaps 717: /* ARGSUSED */
1.2 kristaps 718: static int
1.4 ! kristaps 719: termp_xr_pre(struct termp *p, const struct mdoc_meta *meta,
! 720: const struct mdoc_node *node)
! 721: {
! 722: const struct mdoc_node *n;
! 723:
! 724: n = node->child;
! 725: assert(n);
! 726:
! 727: assert(MDOC_TEXT == n->type);
! 728: word(p, n->data.text.string);
! 729:
! 730: if (NULL == (n = n->next))
! 731: return(0);
! 732:
! 733: assert(MDOC_TEXT == n->type);
! 734: p->flags |= TERMP_NOSPACE;
! 735: word(p, "\\(");
! 736: p->flags |= TERMP_NOSPACE;
! 737: word(p, n->data.text.string);
! 738: p->flags |= TERMP_NOSPACE;
! 739: word(p, "\\)");
! 740:
! 741: return(0);
! 742: }
! 743:
! 744:
! 745: /* ARGSUSED */
! 746: static int
1.2 kristaps 747: termp_sh_pre(struct termp *p, const struct mdoc_meta *meta,
748: const struct mdoc_node *node)
749: {
1.1 kristaps 750:
1.2 kristaps 751: switch (node->type) {
752: case (MDOC_HEAD):
753: vspace(p);
754: p->flags |= TERMP_BOLD;
755: break;
756: case (MDOC_BODY):
1.3 kristaps 757: p->offset += 4;
1.2 kristaps 758: break;
759: default:
760: break;
1.1 kristaps 761: }
1.2 kristaps 762: return(1);
763: }
764:
1.1 kristaps 765:
1.3 kristaps 766: /* ARGSUSED */
1.2 kristaps 767: static int
768: termp_op_pre(struct termp *p, const struct mdoc_meta *meta,
769: const struct mdoc_node *node)
770: {
771:
772: switch (node->type) {
773: case (MDOC_BODY):
1.4 ! kristaps 774: word(p, "\\[");
1.2 kristaps 775: p->flags |= TERMP_NOSPACE;
776: break;
777: default:
778: break;
779: }
780: return(1);
1.1 kristaps 781: }
782:
783:
1.3 kristaps 784: /* ARGSUSED */
1.1 kristaps 785: static int
1.2 kristaps 786: termp_fl_pre(struct termp *p, const struct mdoc_meta *meta,
787: const struct mdoc_node *node)
1.1 kristaps 788: {
789:
1.2 kristaps 790: p->flags |= TERMP_BOLD;
791: word(p, "-");
792: p->flags |= TERMP_NOSPACE;
1.1 kristaps 793: return(1);
794: }
795:
796:
797: static void
1.2 kristaps 798: termprint_r(struct termp *p, const struct mdoc_meta *meta,
799: const struct mdoc_node *node)
800: {
1.4 ! kristaps 801: int dochild;
1.2 kristaps 802:
803: /* Pre-processing ----------------- */
804:
1.4 ! kristaps 805: dochild = 1;
! 806:
1.2 kristaps 807: if (MDOC_TEXT != node->type) {
808: if (termacts[node->tok].pre)
809: if ( ! (*termacts[node->tok].pre)(p, meta, node))
1.4 ! kristaps 810: dochild = 0;
1.2 kristaps 811: } else /* MDOC_TEXT == node->type */
812: word(p, node->data.text.string);
813:
814: /* Children ---------------------- */
815:
1.4 ! kristaps 816: if (dochild && NULL == node->child) {
1.2 kristaps 817: /* No-child processing. */
818: switch (node->type) {
819: case (MDOC_ELEM):
820: switch (node->tok) {
821: case (MDOC_Nm):
822: word(p, "progname"); /* TODO */
823: break;
824: case (MDOC_Ar):
825: word(p, "...");
826: break;
827: default:
828: break;
829: }
830: break;
831: default:
832: break;
833: }
1.4 ! kristaps 834: } else if (dochild)
1.2 kristaps 835: termprint_r(p, meta, node->child);
836:
837: /* Post-processing --------------- */
838:
839: if (MDOC_TEXT != node->type) {
840: if (termacts[node->tok].post)
841: if ( ! (*termacts[node->tok].post)(p, meta, node))
842: return;
843: }
844:
845: /* Siblings ---------------------- */
846:
847: if (node->next)
848: termprint_r(p, meta, node->next);
849: }
850:
851:
852: static void
853: termprint_footer(struct termp *p, const struct mdoc_meta *meta)
1.1 kristaps 854: {
855: struct tm *tm;
856: char *buf, *os;
857: size_t sz, osz, ssz, i;
858:
1.3 kristaps 859: if (NULL == (buf = malloc(p->rmargin)))
1.1 kristaps 860: err(1, "malloc");
1.3 kristaps 861: if (NULL == (os = malloc(p->rmargin)))
1.1 kristaps 862: err(1, "malloc");
863:
864: tm = localtime(&meta->date);
1.3 kristaps 865: if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm))
1.1 kristaps 866: err(1, "strftime");
867:
1.3 kristaps 868: osz = strlcpy(os, meta->os, p->rmargin);
1.1 kristaps 869:
870: sz = strlen(buf);
871: ssz = sz + osz + 1;
872:
1.3 kristaps 873: if (ssz > p->rmargin) {
874: ssz -= p->rmargin;
1.1 kristaps 875: assert(ssz <= osz);
876: os[osz - ssz] = 0;
877: ssz = 1;
878: } else
1.3 kristaps 879: ssz = p->rmargin - ssz + 1;
1.1 kristaps 880:
1.2 kristaps 881: printf("\n");
1.1 kristaps 882: printf("%s", os);
883: for (i = 0; i < ssz; i++)
884: printf(" ");
885:
886: printf("%s\n", buf);
1.2 kristaps 887: fflush(stdout);
1.1 kristaps 888:
889: free(buf);
890: free(os);
891: }
892:
893:
894: static void
1.2 kristaps 895: termprint_header(struct termp *p, const struct mdoc_meta *meta)
1.1 kristaps 896: {
1.2 kristaps 897: char *msec, *buf, *title, *pp;
898: size_t ssz, tsz, ttsz, i;;
1.1 kristaps 899:
1.3 kristaps 900: if (NULL == (buf = malloc(p->rmargin)))
1.1 kristaps 901: err(1, "malloc");
1.3 kristaps 902: if (NULL == (title = malloc(p->rmargin)))
1.1 kristaps 903: err(1, "malloc");
904:
1.2 kristaps 905: if (NULL == (pp = mdoc_vol2a(meta->vol)))
906: switch (meta->msec) {
907: case (MSEC_1):
908: /* FALLTHROUGH */
909: case (MSEC_6):
910: /* FALLTHROUGH */
911: case (MSEC_7):
912: pp = mdoc_vol2a(VOL_URM);
913: break;
914: case (MSEC_8):
915: pp = mdoc_vol2a(VOL_SMM);
916: break;
917: case (MSEC_2):
918: /* FALLTHROUGH */
919: case (MSEC_3):
920: /* FALLTHROUGH */
921: case (MSEC_4):
922: /* FALLTHROUGH */
923: case (MSEC_5):
924: pp = mdoc_vol2a(VOL_PRM);
925: break;
926: case (MSEC_9):
927: pp = mdoc_vol2a(VOL_KM);
928: break;
929: default:
930: /* FIXME: capitalise. */
931: if (NULL == (pp = mdoc_msec2a(meta->msec)))
932: pp = mdoc_msec2a(MSEC_local);
933: break;
934: }
935: assert(pp);
1.1 kristaps 936:
1.3 kristaps 937: tsz = strlcpy(buf, pp, p->rmargin);
938: assert(tsz < p->rmargin);
1.1 kristaps 939:
1.2 kristaps 940: if ((pp = mdoc_arch2a(meta->arch))) {
1.3 kristaps 941: tsz = strlcat(buf, " (", p->rmargin);
942: assert(tsz < p->rmargin);
943: tsz = strlcat(buf, pp, p->rmargin);
944: assert(tsz < p->rmargin);
945: tsz = strlcat(buf, ")", p->rmargin);
946: assert(tsz < p->rmargin);
1.2 kristaps 947: }
948:
1.3 kristaps 949: ttsz = strlcpy(title, meta->title, p->rmargin);
1.2 kristaps 950:
951: if (NULL == (msec = mdoc_msec2a(meta->msec)))
1.1 kristaps 952: msec = "";
953:
954: ssz = (2 * (ttsz + 2 + strlen(msec))) + tsz + 2;
955:
1.3 kristaps 956: if (ssz > p->rmargin) {
957: if ((ssz -= p->rmargin) % 2)
1.1 kristaps 958: ssz++;
959: ssz /= 2;
960:
961: assert(ssz <= ttsz);
962: title[ttsz - ssz] = 0;
963: ssz = 1;
964: } else
1.3 kristaps 965: ssz = ((p->rmargin - ssz) / 2) + 1;
1.1 kristaps 966:
967: printf("%s(%s)", title, msec);
968:
969: for (i = 0; i < ssz; i++)
970: printf(" ");
971:
972: printf("%s", buf);
973:
974: for (i = 0; i < ssz; i++)
975: printf(" ");
976:
1.2 kristaps 977: printf("%s(%s)\n", title, msec);
978: fflush(stdout);
1.1 kristaps 979:
980: free(title);
981: free(buf);
982: }
983:
984:
985: int
986: termprint(const struct mdoc_node *node,
987: const struct mdoc_meta *meta)
988: {
1.2 kristaps 989: struct termp p;
1.1 kristaps 990:
991: if (ERR == setupterm(NULL, STDOUT_FILENO, NULL))
992: return(0);
993:
1.3 kristaps 994: p.maxrmargin = columns < 60 ? 60 : (size_t)columns;
995: p.rmargin = p.maxrmargin;
1.2 kristaps 996: p.maxcols = 1024;
1.3 kristaps 997: p.offset = p.col = 0;
1.2 kristaps 998: p.flags = TERMP_NOSPACE;
999:
1000: if (NULL == (p.buf = malloc(p.maxcols)))
1001: err(1, "malloc");
1002:
1003: termprint_header(&p, meta);
1004: termprint_r(&p, meta, node);
1005: termprint_footer(&p, meta);
1006:
1007: free(p.buf);
1.1 kristaps 1008:
1009: return(1);
1010: }
1011:
1012:
CVSweb