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