Annotation of mandoc/mdocterm.c, Revision 1.42
1.42 ! kristaps 1: /* $Id: mdocterm.c,v 1.41 2009/03/14 05:36:07 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: */
1.37 kristaps 19: #include <sys/types.h>
20:
1.1 kristaps 21: #include <assert.h>
1.3 kristaps 22: #include <ctype.h>
1.1 kristaps 23: #include <err.h>
24: #include <getopt.h>
1.3 kristaps 25: #include <stdio.h>
1.1 kristaps 26: #include <stdlib.h>
1.3 kristaps 27: #include <string.h>
1.5 kristaps 28:
1.2 kristaps 29: #include "mmain.h"
1.1 kristaps 30: #include "term.h"
31:
1.42 ! kristaps 32: struct termseq {
1.27 kristaps 33: const char *enc;
34: int sym;
35: };
36:
1.42 ! kristaps 37: static int option(void *, int, const char *);
1.3 kristaps 38: static void body(struct termp *,
1.12 kristaps 39: struct termpair *,
1.3 kristaps 40: const struct mdoc_meta *,
41: const struct mdoc_node *);
42: static void header(struct termp *,
43: const struct mdoc_meta *);
44: static void footer(struct termp *,
45: const struct mdoc_meta *);
46:
47: static void pword(struct termp *, const char *, size_t);
1.25 kristaps 48: static void pescape(struct termp *, const char *,
49: size_t *, size_t);
50: static void nescape(struct termp *,
1.12 kristaps 51: const char *, size_t);
1.3 kristaps 52: static void chara(struct termp *, char);
1.25 kristaps 53: static void stringa(struct termp *,
54: const char *, size_t);
55: static void symbola(struct termp *, enum tsym);
1.38 kristaps 56: static void sanity(const struct mdoc_node *);
1.3 kristaps 57:
58: #ifdef __linux__
59: extern size_t strlcat(char *, const char *, size_t);
60: extern size_t strlcpy(char *, const char *, size_t);
61: #endif
62:
1.42 ! kristaps 63: static struct termseq termenc1[] = {
1.27 kristaps 64: { "\\", TERMSYM_SLASH },
65: { "\'", TERMSYM_RSQUOTE },
66: { "`", TERMSYM_LSQUOTE },
67: { "-", TERMSYM_HYPHEN },
68: { " ", TERMSYM_SPACE },
69: { ".", TERMSYM_PERIOD },
70: { "&", TERMSYM_BREAK },
71: { "e", TERMSYM_SLASH },
72: { "q", TERMSYM_DQUOTE },
1.41 kristaps 73: { "|", TERMSYM_BREAK },
1.27 kristaps 74: { NULL, 0 }
75: };
76:
1.42 ! kristaps 77: static struct termseq termenc2[] = {
1.39 kristaps 78: { "rC", TERMSYM_RBRACE },
79: { "lC", TERMSYM_LBRACE },
1.27 kristaps 80: { "rB", TERMSYM_RBRACK },
81: { "lB", TERMSYM_LBRACK },
1.31 kristaps 82: { "ra", TERMSYM_RANGLE },
83: { "la", TERMSYM_LANGLE },
1.27 kristaps 84: { "Lq", TERMSYM_LDQUOTE },
85: { "lq", TERMSYM_LDQUOTE },
86: { "Rq", TERMSYM_RDQUOTE },
87: { "rq", TERMSYM_RDQUOTE },
88: { "oq", TERMSYM_LSQUOTE },
89: { "aq", TERMSYM_RSQUOTE },
90:
91: { "<-", TERMSYM_LARROW },
92: { "->", TERMSYM_RARROW },
93: { "ua", TERMSYM_UARROW },
94: { "da", TERMSYM_DARROW },
95:
96: { "bu", TERMSYM_BULLET },
97: { "Ba", TERMSYM_BAR },
98: { "ba", TERMSYM_BAR },
99: { "co", TERMSYM_COPY },
100: { "Am", TERMSYM_AMP },
101:
102: { "Le", TERMSYM_LE },
103: { "<=", TERMSYM_LE },
104: { "Ge", TERMSYM_GE },
1.31 kristaps 105: { ">=", TERMSYM_GE },
1.27 kristaps 106: { "==", TERMSYM_EQ },
107: { "Ne", TERMSYM_NEQ },
108: { "!=", TERMSYM_NEQ },
109: { "Pm", TERMSYM_PLUSMINUS },
110: { "+-", TERMSYM_PLUSMINUS },
111: { "If", TERMSYM_INF2 },
112: { "if", TERMSYM_INF },
113: { "Na", TERMSYM_NAN },
114: { "na", TERMSYM_NAN },
115: { "**", TERMSYM_ASTERISK },
116: { "Gt", TERMSYM_GT },
117: { "Lt", TERMSYM_LT },
118:
119: { "aa", TERMSYM_ACUTE },
120: { "ga", TERMSYM_GRAVE },
121:
122: { "en", TERMSYM_EN },
123: { "em", TERMSYM_EM },
124:
125: { "Pi", TERMSYM_PI },
126: { NULL, 0 }
127: };
128:
1.42 ! kristaps 129: static struct termsym termsym_ascii[TERMSYM_MAX] = {
1.25 kristaps 130: { "]", 1 }, /* TERMSYM_RBRACK */
131: { "[", 1 }, /* TERMSYM_LBRACK */
132: { "<-", 2 }, /* TERMSYM_LARROW */
133: { "->", 2 }, /* TERMSYM_RARROW */
134: { "^", 1 }, /* TERMSYM_UARROW */
135: { "v", 1 }, /* TERMSYM_DARROW */
136: { "`", 1 }, /* TERMSYM_LSQUOTE */
137: { "\'", 1 }, /* TERMSYM_RSQUOTE */
138: { "\'", 1 }, /* TERMSYM_SQUOTE */
139: { "``", 2 }, /* TERMSYM_LDQUOTE */
140: { "\'\'", 2 }, /* TERMSYM_RDQUOTE */
141: { "\"", 1 }, /* TERMSYM_DQUOTE */
142: { "<", 1 }, /* TERMSYM_LT */
143: { ">", 1 }, /* TERMSYM_GT */
144: { "<=", 2 }, /* TERMSYM_LE */
145: { ">=", 2 }, /* TERMSYM_GE */
146: { "==", 2 }, /* TERMSYM_EQ */
147: { "!=", 2 }, /* TERMSYM_NEQ */
148: { "\'", 1 }, /* TERMSYM_ACUTE */
149: { "`", 1 }, /* TERMSYM_GRAVE */
150: { "pi", 2 }, /* TERMSYM_PI */
151: { "+=", 2 }, /* TERMSYM_PLUSMINUS */
152: { "oo", 2 }, /* TERMSYM_INF */
153: { "infinity", 8 }, /* TERMSYM_INF2 */
154: { "NaN", 3 }, /* TERMSYM_NAN */
155: { "|", 1 }, /* TERMSYM_BAR */
156: { "o", 1 }, /* TERMSYM_BULLET */
1.27 kristaps 157: { "&", 1 }, /* TERMSYM_AMP */
158: { "--", 2 }, /* TERMSYM_EM */
159: { "-", 1 }, /* TERMSYM_EN */
160: { "(C)", 3 }, /* TERMSYM_COPY */
161: { "*", 1 }, /* TERMSYM_ASTERISK */
162: { "\\", 1 }, /* TERMSYM_SLASH */
163: { "-", 1 }, /* TERMSYM_HYPHEN */
164: { " ", 1 }, /* TERMSYM_SPACE */
165: { ".", 1 }, /* TERMSYM_PERIOD */
166: { "", 0 }, /* TERMSYM_BREAK */
1.31 kristaps 167: { "<", 1 }, /* TERMSYM_LANGLE */
168: { ">", 1 }, /* TERMSYM_RANGLE */
1.39 kristaps 169: { "{", 1 }, /* TERMSYM_LBRACE */
170: { "}", 1 }, /* TERMSYM_RBRACE */
1.25 kristaps 171: };
172:
1.3 kristaps 173:
1.1 kristaps 174: int
175: main(int argc, char *argv[])
176: {
1.34 kristaps 177: struct mmain *p;
178: int c;
1.2 kristaps 179: const struct mdoc *mdoc;
1.34 kristaps 180: struct termp termp;
1.2 kristaps 181:
1.42 ! kristaps 182: (void)memset(&termp, 0, sizeof(struct termp));
! 183:
! 184: termp.maxrmargin = termp.rmargin = 78; /* XXX */
! 185: termp.maxcols = 1024;
! 186: termp.offset = termp.col = 0;
! 187: termp.flags = TERMP_NOSPACE;
! 188: termp.symtab = termsym_ascii;
! 189: termp.enc = TERMENC_ANSI;
! 190:
1.2 kristaps 191: p = mmain_alloc();
1.42 ! kristaps 192: c = mmain_getopt(p, argc, argv, "[-Ooption...]",
! 193: "O:", &termp, option);
1.1 kristaps 194:
1.34 kristaps 195: if (1 != c)
196: mmain_exit(p, -1 == c ? 1 : 0);
1.1 kristaps 197:
1.3 kristaps 198: if (NULL == (mdoc = mmain_mdoc(p)))
199: mmain_exit(p, 1);
200:
201: if (NULL == (termp.buf = malloc(termp.maxcols)))
202: err(1, "malloc");
203:
1.15 kristaps 204: header(&termp, mdoc_meta(mdoc));
1.12 kristaps 205: body(&termp, NULL, mdoc_meta(mdoc), mdoc_node(mdoc));
1.3 kristaps 206: footer(&termp, mdoc_meta(mdoc));
207:
208: free(termp.buf);
209:
210: mmain_exit(p, 0);
211: /* NOTREACHED */
212: }
213:
214:
1.42 ! kristaps 215: int
! 216: option(void *ptr, int c, const char *arg)
! 217: {
! 218: struct termp *p;
! 219:
! 220: p = (struct termp *)ptr;
! 221:
! 222: if (0 == strcmp(arg, "nroff")) {
! 223: p->enc = TERMENC_NROFF;
! 224: return(1);
! 225: } else if (0 == strcmp(arg, "ansi")) {
! 226: p->enc = TERMENC_ANSI;
! 227: return(1);
! 228: }
! 229:
! 230: warnx("unknown option: -O%s", arg);
! 231: return(0);
! 232: }
! 233:
! 234:
1.25 kristaps 235: /*
236: * Flush a line of text. A "line" is loosely defined as being something
237: * that should be followed by a newline, regardless of whether it's
238: * broken apart by newlines getting there. A line can also be a
239: * fragment of a columnar list.
240: *
241: * Specifically, a line is whatever's in p->buf of length p->col, which
242: * is zeroed after this function returns.
243: *
244: * The variables TERMP_NOLPAD, TERMP_LITERAL and TERMP_NOBREAK are of
245: * critical importance here. Their behaviour follows:
246: *
247: * - TERMP_NOLPAD: when beginning to write the line, don't left-pad the
248: * offset value. This is useful when doing columnar lists where the
249: * prior column has right-padded.
250: *
251: * - TERMP_NOBREAK: this is the most important and is used when making
252: * columns. In short: don't print a newline and instead pad to the
253: * right margin. Used in conjunction with TERMP_NOLPAD.
254: *
255: * In-line line breaking:
256: *
257: * If TERMP_NOBREAK is specified and the line overruns the right
258: * margin, it will break and pad-right to the right margin after
259: * writing. If maxrmargin is violated, it will break and continue
260: * writing from the right-margin, which will lead to the above
261: * scenario upon exit.
262: *
263: * Otherwise, the line will break at the right margin. Extremely long
264: * lines will cause the system to emit a warning (TODO: hyphenate, if
265: * possible).
266: */
1.3 kristaps 267: void
268: flushln(struct termp *p)
269: {
1.25 kristaps 270: size_t i, j, vsz, vis, maxvis, mmax, bp;
1.3 kristaps 271:
272: /*
273: * First, establish the maximum columns of "visible" content.
274: * This is usually the difference between the right-margin and
275: * an indentation, but can be, for tagged lists or columns, a
276: * small set of values.
277: */
278:
279: assert(p->offset < p->rmargin);
280: maxvis = p->rmargin - p->offset;
1.25 kristaps 281: mmax = p->maxrmargin - p->offset;
282: bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
1.3 kristaps 283: vis = 0;
284:
285: /*
286: * If in the standard case (left-justified), then begin with our
287: * indentation, otherwise (columns, etc.) just start spitting
288: * out text.
289: */
290:
291: if ( ! (p->flags & TERMP_NOLPAD))
292: /* LINTED */
293: for (j = 0; j < p->offset; j++)
294: putchar(' ');
295:
296: for (i = 0; i < p->col; i++) {
297: /*
298: * Count up visible word characters. Control sequences
1.23 kristaps 299: * (starting with the CSI) aren't counted. A space
300: * generates a non-printing word, which is valid (the
301: * space is printed according to regular spacing rules).
1.3 kristaps 302: */
303:
304: /* LINTED */
305: for (j = i, vsz = 0; j < p->col; j++) {
1.42 ! kristaps 306: if (isspace((u_char)p->buf[j])) {
1.3 kristaps 307: break;
1.42 ! kristaps 308: } else if (27 == p->buf[j]) {
! 309: assert(TERMENC_ANSI == p->enc);
! 310: assert(j + 5 <= p->col);
! 311: j += 4;
! 312: } else if (8 == p->buf[j]) {
! 313: assert(TERMENC_NROFF == p->enc);
! 314: assert(j + 2 <= p->col);
! 315: j += 1;
1.3 kristaps 316: } else
317: vsz++;
318: }
319:
320: /*
1.25 kristaps 321: * Do line-breaking. If we're greater than our
322: * break-point and already in-line, break to the next
323: * line and start writing. If we're at the line start,
324: * then write out the word (TODO: hyphenate) and break
325: * in a subsequent loop invocation.
1.3 kristaps 326: */
327:
1.22 kristaps 328: if ( ! (TERMP_NOBREAK & p->flags)) {
1.25 kristaps 329: if (vis && vis + vsz > bp) {
1.22 kristaps 330: putchar('\n');
331: for (j = 0; j < p->offset; j++)
332: putchar(' ');
333: vis = 0;
1.25 kristaps 334: } else if (vis + vsz > bp)
335: warnx("word breaks right margin");
336:
337: /* TODO: hyphenate. */
338:
339: } else {
340: if (vis && vis + vsz > bp) {
341: putchar('\n');
342: for (j = 0; j < p->rmargin; j++)
343: putchar(' ');
1.30 kristaps 344: vis = p->rmargin - p->offset;
1.25 kristaps 345: } else if (vis + vsz > bp)
346: warnx("word breaks right margin");
347:
348: /* TODO: hyphenate. */
1.24 kristaps 349: }
1.3 kristaps 350:
351: /*
352: * Write out the word and a trailing space. Omit the
1.25 kristaps 353: * space if we're the last word in the line or beyond
354: * our breakpoint.
1.3 kristaps 355: */
356:
357: for ( ; i < p->col; i++) {
1.33 kristaps 358: if (isspace((u_char)p->buf[i]))
1.3 kristaps 359: break;
360: putchar(p->buf[i]);
361: }
362: vis += vsz;
1.25 kristaps 363: if (i < p->col && vis <= bp) {
1.3 kristaps 364: putchar(' ');
365: vis++;
366: }
367: }
368:
1.25 kristaps 369: /*
370: * If we've overstepped our maximum visible no-break space, then
371: * cause a newline and offset at the right margin.
372: */
373:
1.22 kristaps 374: if ((TERMP_NOBREAK & p->flags) && vis >= maxvis) {
1.32 kristaps 375: if ( ! (TERMP_NONOBREAK & p->flags)) {
376: putchar('\n');
377: for (i = 0; i < p->rmargin; i++)
378: putchar(' ');
379: }
1.22 kristaps 380: p->col = 0;
381: return;
382: }
383:
1.3 kristaps 384: /*
385: * If we're not to right-marginalise it (newline), then instead
386: * pad to the right margin and stay off.
387: */
388:
1.32 kristaps 389: if (p->flags & TERMP_NOBREAK) {
390: if ( ! (TERMP_NONOBREAK & p->flags))
391: for ( ; vis < maxvis; vis++)
392: putchar(' ');
393: } else
1.3 kristaps 394: putchar('\n');
395:
396: p->col = 0;
397: }
398:
399:
1.25 kristaps 400: /*
401: * A newline only breaks an existing line; it won't assert vertical
402: * space. All data in the output buffer is flushed prior to the newline
403: * assertion.
404: */
1.3 kristaps 405: void
406: newln(struct termp *p)
407: {
1.1 kristaps 408:
1.3 kristaps 409: p->flags |= TERMP_NOSPACE;
1.12 kristaps 410: if (0 == p->col) {
411: p->flags &= ~TERMP_NOLPAD;
1.3 kristaps 412: return;
1.12 kristaps 413: }
1.3 kristaps 414: flushln(p);
1.11 kristaps 415: p->flags &= ~TERMP_NOLPAD;
1.3 kristaps 416: }
417:
418:
1.25 kristaps 419: /*
420: * Asserts a vertical space (a full, empty line-break between lines).
421: * Note that if used twice, this will cause two blank spaces and so on.
422: * All data in the output buffer is flushed prior to the newline
423: * assertion.
424: */
1.3 kristaps 425: void
426: vspace(struct termp *p)
427: {
428:
429: newln(p);
430: putchar('\n');
431: }
432:
433:
1.25 kristaps 434: /*
435: * Break apart a word into "pwords" (partial-words, usually from
436: * breaking up a phrase into individual words) and, eventually, put them
437: * into the output buffer. If we're a literal word, then don't break up
438: * the word and put it verbatim into the output buffer.
439: */
1.3 kristaps 440: void
441: word(struct termp *p, const char *word)
442: {
443: size_t i, j, len;
444:
445: if (p->flags & TERMP_LITERAL) {
446: pword(p, word, strlen(word));
447: return;
448: }
449:
1.40 kristaps 450: if (0 == (len = strlen(word)))
451: errx(1, "blank line not in literal context");
1.3 kristaps 452:
453: if (mdoc_isdelim(word)) {
454: if ( ! (p->flags & TERMP_IGNDELIM))
455: p->flags |= TERMP_NOSPACE;
456: p->flags &= ~TERMP_IGNDELIM;
457: }
458:
459: /* LINTED */
460: for (j = i = 0; i < len; i++) {
1.33 kristaps 461: if ( ! isspace((u_char)word[i])) {
1.3 kristaps 462: j++;
463: continue;
1.20 kristaps 464: }
465:
466: /* Escaped spaces don't delimit... */
1.33 kristaps 467: if (i > 0 && isspace((u_char)word[i]) &&
1.25 kristaps 468: '\\' == word[i - 1]) {
1.20 kristaps 469: j++;
470: continue;
1.1 kristaps 471: }
1.20 kristaps 472:
1.3 kristaps 473: if (0 == j)
474: continue;
475: assert(i >= j);
476: pword(p, &word[i - j], j);
477: j = 0;
478: }
479: if (j > 0) {
480: assert(i >= j);
481: pword(p, &word[i - j], j);
482: }
483: }
484:
485:
1.25 kristaps 486: /*
487: * This is the main function for printing out nodes. It's constituted
488: * of PRE and POST functions, which correspond to prefix and infix
489: * processing. The termpair structure allows data to persist between
490: * prefix and postfix invocations.
491: */
1.3 kristaps 492: static void
1.12 kristaps 493: body(struct termp *p, struct termpair *ppair,
494: const struct mdoc_meta *meta,
1.3 kristaps 495: const struct mdoc_node *node)
496: {
497: int dochild;
1.9 kristaps 498: struct termpair pair;
1.3 kristaps 499:
1.38 kristaps 500: /* Some quick sanity-checking. */
501:
502: sanity(node);
503:
1.3 kristaps 504: /* Pre-processing. */
505:
506: dochild = 1;
1.12 kristaps 507: pair.ppair = ppair;
1.9 kristaps 508: pair.type = 0;
1.11 kristaps 509: pair.offset = pair.rmargin = 0;
1.10 kristaps 510: pair.flag = 0;
1.12 kristaps 511: pair.count = 0;
1.3 kristaps 512:
513: if (MDOC_TEXT != node->type) {
514: if (termacts[node->tok].pre)
1.9 kristaps 515: if ( ! (*termacts[node->tok].pre)(p, &pair, meta, node))
1.3 kristaps 516: dochild = 0;
517: } else /* MDOC_TEXT == node->type */
1.35 kristaps 518: word(p, node->string);
1.3 kristaps 519:
520: /* Children. */
521:
1.10 kristaps 522: if (TERMPAIR_FLAG & pair.type)
523: p->flags |= pair.flag;
1.9 kristaps 524:
1.3 kristaps 525: if (dochild && node->child)
1.12 kristaps 526: body(p, &pair, meta, node->child);
1.3 kristaps 527:
1.10 kristaps 528: if (TERMPAIR_FLAG & pair.type)
529: p->flags &= ~pair.flag;
1.9 kristaps 530:
1.3 kristaps 531: /* Post-processing. */
532:
533: if (MDOC_TEXT != node->type)
534: if (termacts[node->tok].post)
1.9 kristaps 535: (*termacts[node->tok].post)(p, &pair, meta, node);
1.3 kristaps 536:
537: /* Siblings. */
1.1 kristaps 538:
1.3 kristaps 539: if (node->next)
1.12 kristaps 540: body(p, ppair, meta, node->next);
1.3 kristaps 541: }
542:
543:
544: static void
545: footer(struct termp *p, const struct mdoc_meta *meta)
546: {
547: struct tm *tm;
548: char *buf, *os;
549:
550: if (NULL == (buf = malloc(p->rmargin)))
551: err(1, "malloc");
552: if (NULL == (os = malloc(p->rmargin)))
553: err(1, "malloc");
554:
555: tm = localtime(&meta->date);
556:
1.7 kristaps 557: #ifdef __OpenBSD__
558: if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm))
559: #else
1.3 kristaps 560: if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
561: #endif
562: err(1, "strftime");
563:
1.15 kristaps 564: (void)strlcpy(os, meta->os, p->rmargin);
1.3 kristaps 565:
1.16 kristaps 566: /*
567: * This is /slightly/ different from regular groff output
568: * because we don't have page numbers. Print the following:
569: *
570: * OS MDOCDATE
571: */
572:
1.15 kristaps 573: vspace(p);
1.3 kristaps 574:
1.15 kristaps 575: p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
576: p->rmargin = p->maxrmargin - strlen(buf);
577: p->offset = 0;
1.3 kristaps 578:
1.15 kristaps 579: word(p, os);
580: flushln(p);
1.3 kristaps 581:
1.15 kristaps 582: p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
583: p->offset = p->rmargin;
584: p->rmargin = p->maxrmargin;
585: p->flags &= ~TERMP_NOBREAK;
586:
587: word(p, buf);
588: flushln(p);
1.1 kristaps 589:
1.3 kristaps 590: free(buf);
591: free(os);
1.1 kristaps 592: }
593:
594:
1.3 kristaps 595: static void
596: header(struct termp *p, const struct mdoc_meta *meta)
597: {
1.34 kristaps 598: char *buf, *title, *bufp;
1.18 kristaps 599:
600: p->rmargin = p->maxrmargin;
601: p->offset = 0;
1.3 kristaps 602:
603: if (NULL == (buf = malloc(p->rmargin)))
604: err(1, "malloc");
605: if (NULL == (title = malloc(p->rmargin)))
606: err(1, "malloc");
607:
1.16 kristaps 608: /*
609: * The header is strange. It has three components, which are
610: * really two with the first duplicated. It goes like this:
611: *
612: * IDENTIFIER TITLE IDENTIFIER
613: *
614: * The IDENTIFIER is NAME(SECTION), which is the command-name
615: * (if given, or "unknown" if not) followed by the manual page
616: * section. These are given in `Dt'. The TITLE is a free-form
617: * string depending on the manual volume. If not specified, it
618: * switches on the manual section.
619: */
620:
1.34 kristaps 621: assert(meta->vol);
622: (void)strlcpy(buf, meta->vol, p->rmargin);
1.13 kristaps 623:
1.34 kristaps 624: if (meta->arch) {
625: (void)strlcat(buf, " (", p->rmargin);
626: (void)strlcat(buf, meta->arch, p->rmargin);
627: (void)strlcat(buf, ")", p->rmargin);
628: }
1.13 kristaps 629:
1.34 kristaps 630: (void)snprintf(title, p->rmargin, "%s(%d)",
631: meta->title, meta->msec);
1.13 kristaps 632:
1.16 kristaps 633: for (bufp = title; *bufp; bufp++)
1.33 kristaps 634: *bufp = toupper((u_char)*bufp);
1.16 kristaps 635:
1.13 kristaps 636: p->offset = 0;
637: p->rmargin = (p->maxrmargin - strlen(buf)) / 2;
1.15 kristaps 638: p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
1.3 kristaps 639:
1.13 kristaps 640: word(p, title);
641: flushln(p);
1.3 kristaps 642:
1.15 kristaps 643: p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
1.13 kristaps 644: p->offset = p->rmargin;
1.15 kristaps 645: p->rmargin = p->maxrmargin - strlen(title);
1.3 kristaps 646:
1.13 kristaps 647: word(p, buf);
648: flushln(p);
1.3 kristaps 649:
1.13 kristaps 650: p->offset = p->rmargin;
651: p->rmargin = p->maxrmargin;
652: p->flags &= ~TERMP_NOBREAK;
1.15 kristaps 653: p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
1.3 kristaps 654:
1.13 kristaps 655: word(p, title);
656: flushln(p);
1.3 kristaps 657:
1.13 kristaps 658: p->rmargin = p->maxrmargin;
659: p->offset = 0;
660: p->flags &= ~TERMP_NOSPACE;
1.3 kristaps 661:
662: free(title);
663: free(buf);
664: }
1.25 kristaps 665:
666:
667: /*
668: * Determine the symbol indicated by an escape sequences, that is, one
669: * starting with a backslash. Once done, we pass this value into the
670: * output buffer by way of the symbol table.
671: */
672: static void
673: nescape(struct termp *p, const char *word, size_t len)
674: {
1.42 ! kristaps 675: struct termseq *enc;
1.25 kristaps 676:
677: switch (len) {
678: case (1):
1.27 kristaps 679: enc = termenc1;
1.25 kristaps 680: break;
681: case (2):
1.27 kristaps 682: enc = termenc2;
1.25 kristaps 683: break;
684: default:
1.27 kristaps 685: warnx("unsupported %zu-byte escape sequence", len);
686: return;
1.25 kristaps 687: }
1.27 kristaps 688:
689: for ( ; enc->enc; enc++)
690: if (0 == memcmp(enc->enc, word, len)) {
691: symbola(p, enc->sym);
692: return;
693: }
694:
695: warnx("unsupported %zu-byte escape sequence", len);
1.25 kristaps 696: }
697:
698:
699: /*
700: * Handle an escape sequence: determine its length and pass it to the
701: * escape-symbol look table. Note that we assume mdoc(3) has validated
702: * the escape sequence (we assert upon badly-formed escape sequences).
703: */
704: static void
705: pescape(struct termp *p, const char *word, size_t *i, size_t len)
706: {
707: size_t j;
708:
1.36 kristaps 709: if (++(*i) >= len) {
710: warnx("ignoring bad escape sequence");
711: return;
712: }
1.25 kristaps 713:
714: if ('(' == word[*i]) {
715: (*i)++;
1.36 kristaps 716: if (*i + 1 >= len) {
717: warnx("ignoring bad escape sequence");
718: return;
719: }
1.25 kristaps 720: nescape(p, &word[*i], 2);
721: (*i)++;
722: return;
723:
724: } else if ('*' == word[*i]) {
725: (*i)++;
1.36 kristaps 726: if (*i >= len) {
727: warnx("ignoring bad escape sequence");
728: return;
729: }
1.25 kristaps 730: switch (word[*i]) {
731: case ('('):
732: (*i)++;
1.36 kristaps 733: if (*i + 1 >= len) {
734: warnx("ignoring bad escape sequence");
735: return;
736: }
1.25 kristaps 737: nescape(p, &word[*i], 2);
738: (*i)++;
739: return;
740: case ('['):
741: break;
742: default:
743: nescape(p, &word[*i], 1);
744: return;
745: }
746:
747: } else if ('[' != word[*i]) {
748: nescape(p, &word[*i], 1);
749: return;
750: }
751:
752: (*i)++;
753: for (j = 0; word[*i] && ']' != word[*i]; (*i)++, j++)
754: /* Loop... */ ;
755:
1.36 kristaps 756: if (0 == word[*i]) {
757: warnx("ignoring bad escape sequence");
758: return;
759: }
1.25 kristaps 760: nescape(p, &word[*i - j], j);
761: }
762:
763:
764: /*
765: * Handle pwords, partial words, which may be either a single word or a
766: * phrase that cannot be broken down (such as a literal string). This
767: * handles word styling.
768: */
769: static void
770: pword(struct termp *p, const char *word, size_t len)
771: {
772: size_t i;
773:
774: if ( ! (TERMP_NOSPACE & p->flags) &&
775: ! (TERMP_LITERAL & p->flags))
776: chara(p, ' ');
777:
778: if ( ! (p->flags & TERMP_NONOSPACE))
779: p->flags &= ~TERMP_NOSPACE;
780:
781: /*
1.42 ! kristaps 782: * If ANSI (word-length styling), then apply our style now,
! 783: * before the word.
1.25 kristaps 784: */
785:
1.42 ! kristaps 786: if (TERMENC_ANSI == p->enc && TERMP_STYLE & p->flags) {
! 787: if (TERMP_BOLD & p->flags) {
! 788: chara(p, 27);
! 789: stringa(p, "[01m", 4);
! 790: }
! 791: if (TERMP_UNDER & p->flags) {
! 792: chara(p, 27);
! 793: stringa(p, "[04m", 4);
! 794: }
! 795: if (TERMP_RED & p->flags) {
! 796: chara(p, 27);
! 797: stringa(p, "[31m", 4);
! 798: }
! 799: if (TERMP_GREEN & p->flags) {
! 800: chara(p, 27);
! 801: stringa(p, "[32m", 4);
! 802: }
! 803: if (TERMP_YELLOW & p->flags) {
! 804: chara(p, 27);
! 805: stringa(p, "[33m", 4);
! 806: }
! 807: if (TERMP_BLUE & p->flags) {
! 808: chara(p, 27);
! 809: stringa(p, "[34m", 4);
! 810: }
! 811: if (TERMP_MAGENTA & p->flags) {
! 812: chara(p, 27);
! 813: stringa(p, "[35m", 4);
! 814: }
! 815: if (TERMP_CYAN & p->flags) {
! 816: chara(p, 27);
! 817: stringa(p, "[36m", 4);
! 818: }
! 819: }
1.25 kristaps 820:
821: for (i = 0; i < len; i++) {
822: if ('\\' == word[i]) {
823: pescape(p, word, &i, len);
824: continue;
825: }
1.42 ! kristaps 826:
! 827: if (TERMENC_NROFF == p->enc &&
! 828: TERMP_STYLE & p->flags) {
! 829: if (TERMP_BOLD & p->flags) {
! 830: chara(p, word[i]);
! 831: chara(p, 8);
! 832: }
! 833: if (TERMP_UNDER & p->flags) {
! 834: chara(p, '_');
! 835: chara(p, 8);
! 836: }
! 837: }
! 838:
1.25 kristaps 839: chara(p, word[i]);
840: }
841:
1.42 ! kristaps 842: if (TERMENC_ANSI == p->enc && TERMP_STYLE & p->flags) {
! 843: chara(p, 27);
! 844: stringa(p, "[00m", 4);
! 845: }
1.25 kristaps 846: }
847:
848:
849: /*
850: * Add a symbol to the output line buffer.
851: */
852: static void
853: symbola(struct termp *p, enum tsym sym)
854: {
855:
856: assert(p->symtab[sym].sym);
857: stringa(p, p->symtab[sym].sym, p->symtab[sym].sz);
858: }
859:
860:
861: /*
862: * Like chara() but for arbitrary-length buffers. Resize the buffer by
863: * a factor of two (if the buffer is less than that) or the buffer's
864: * size.
865: */
866: static void
867: stringa(struct termp *p, const char *c, size_t sz)
868: {
869: size_t s;
870:
1.27 kristaps 871: if (0 == sz)
872: return;
873:
1.25 kristaps 874: s = sz > p->maxcols * 2 ? sz : p->maxcols * 2;
875:
876: assert(c);
877: if (p->col + sz >= p->maxcols) {
878: p->buf = realloc(p->buf, s);
879: if (NULL == p->buf)
880: err(1, "realloc");
881: p->maxcols = s;
882: }
883:
884: (void)memcpy(&p->buf[p->col], c, sz);
885: p->col += sz;
886: }
887:
888:
889: /*
890: * Insert a single character into the line-buffer. If the buffer's
891: * space is exceeded, then allocate more space by doubling the buffer
892: * size.
893: */
894: static void
895: chara(struct termp *p, char c)
896: {
897:
898: if (p->col + 1 >= p->maxcols) {
899: p->buf = realloc(p->buf, p->maxcols * 2);
900: if (NULL == p->buf)
901: err(1, "malloc");
902: p->maxcols *= 2;
903: }
904: p->buf[(p->col)++] = c;
905: }
1.38 kristaps 906:
907:
908: static void
909: sanity(const struct mdoc_node *n)
910: {
911:
912: switch (n->type) {
913: case (MDOC_TEXT):
914: if (n->child)
915: errx(1, "regular form violated (1)");
916: if (NULL == n->parent)
917: errx(1, "regular form violated (2)");
918: if (NULL == n->string)
919: errx(1, "regular form violated (3)");
920: switch (n->parent->type) {
921: case (MDOC_TEXT):
922: /* FALLTHROUGH */
923: case (MDOC_ROOT):
924: errx(1, "regular form violated (4)");
925: /* NOTREACHED */
926: default:
927: break;
928: }
929: break;
930: case (MDOC_ELEM):
931: if (NULL == n->parent)
932: errx(1, "regular form violated (5)");
933: switch (n->parent->type) {
934: case (MDOC_TAIL):
935: /* FALLTHROUGH */
936: case (MDOC_BODY):
937: /* FALLTHROUGH */
938: case (MDOC_HEAD):
939: break;
940: default:
941: errx(1, "regular form violated (6)");
942: /* NOTREACHED */
943: }
944: if (n->child) switch (n->child->type) {
945: case (MDOC_TEXT):
946: break;
947: default:
948: errx(1, "regular form violated (7(");
949: /* NOTREACHED */
950: }
951: break;
952: case (MDOC_HEAD):
953: /* FALLTHROUGH */
954: case (MDOC_BODY):
955: /* FALLTHROUGH */
956: case (MDOC_TAIL):
957: if (NULL == n->parent)
958: errx(1, "regular form violated (8)");
959: if (MDOC_BLOCK != n->parent->type)
960: errx(1, "regular form violated (9)");
961: if (n->child) switch (n->child->type) {
962: case (MDOC_BLOCK):
963: /* FALLTHROUGH */
964: case (MDOC_ELEM):
965: /* FALLTHROUGH */
966: case (MDOC_TEXT):
967: break;
968: default:
969: errx(1, "regular form violated (a)");
970: /* NOTREACHED */
971: }
972: break;
973: case (MDOC_BLOCK):
974: if (NULL == n->parent)
975: errx(1, "regular form violated (b)");
976: if (NULL == n->child)
977: errx(1, "regular form violated (c)");
978: switch (n->parent->type) {
979: case (MDOC_ROOT):
980: /* FALLTHROUGH */
981: case (MDOC_HEAD):
982: /* FALLTHROUGH */
983: case (MDOC_BODY):
984: /* FALLTHROUGH */
985: case (MDOC_TAIL):
986: break;
987: default:
988: errx(1, "regular form violated (d)");
989: /* NOTREACHED */
990: }
991: switch (n->child->type) {
992: case (MDOC_ROOT):
993: /* FALLTHROUGH */
994: case (MDOC_ELEM):
995: errx(1, "regular form violated (e)");
996: /* NOTREACHED */
997: default:
998: break;
999: }
1000: break;
1001: case (MDOC_ROOT):
1002: if (n->parent)
1003: errx(1, "regular form violated (f)");
1004: if (NULL == n->child)
1005: errx(1, "regular form violated (10)");
1006: switch (n->child->type) {
1007: case (MDOC_BLOCK):
1008: break;
1009: default:
1010: errx(1, "regular form violated (11)");
1011: /* NOTREACHED */
1012: }
1013: break;
1014: }
1015: }
1016:
CVSweb