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