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