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