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