Annotation of mandoc/mdocterm.c, Revision 1.27
1.27 ! kristaps 1: /* $Id: mdocterm.c,v 1.26 2009/03/02 17:29:16 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.19 kristaps 192: termp.maxrmargin = 78; /* XXX */
1.3 kristaps 193: termp.rmargin = termp.maxrmargin;
194: termp.maxcols = 1024;
195: termp.offset = termp.col = 0;
196: termp.flags = TERMP_NOSPACE;
1.25 kristaps 197: termp.symtab = termsym_ansi;
198: termp.styletab = termstyle_ansi;
1.3 kristaps 199:
200: if (NULL == (termp.buf = malloc(termp.maxcols)))
201: err(1, "malloc");
202:
1.15 kristaps 203: header(&termp, mdoc_meta(mdoc));
1.12 kristaps 204: body(&termp, NULL, mdoc_meta(mdoc), mdoc_node(mdoc));
1.3 kristaps 205: footer(&termp, mdoc_meta(mdoc));
206:
207: free(termp.buf);
208:
209: mmain_exit(p, 0);
210: /* NOTREACHED */
211: }
212:
213:
1.25 kristaps 214: /*
215: * Flush a line of text. A "line" is loosely defined as being something
216: * that should be followed by a newline, regardless of whether it's
217: * broken apart by newlines getting there. A line can also be a
218: * fragment of a columnar list.
219: *
220: * Specifically, a line is whatever's in p->buf of length p->col, which
221: * is zeroed after this function returns.
222: *
223: * The variables TERMP_NOLPAD, TERMP_LITERAL and TERMP_NOBREAK are of
224: * critical importance here. Their behaviour follows:
225: *
226: * - TERMP_NOLPAD: when beginning to write the line, don't left-pad the
227: * offset value. This is useful when doing columnar lists where the
228: * prior column has right-padded.
229: *
230: * - TERMP_LITERAL: don't break apart words. Note that a long literal
231: * word will violate the right margin.
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.25 kristaps 290: if (isspace((int)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(' ');
323: vis = p->rmargin;
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.25 kristaps 337: if (isspace((int)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) {
354: putchar('\n');
355: for (i = 0; i < p->rmargin; i++)
356: putchar(' ');
357: p->col = 0;
358: return;
359: }
360:
1.3 kristaps 361: /*
362: * If we're not to right-marginalise it (newline), then instead
363: * pad to the right margin and stay off.
364: */
365:
366: if (p->flags & TERMP_NOBREAK) {
1.22 kristaps 367: for ( ; vis < maxvis; vis++)
368: putchar(' ');
1.3 kristaps 369: } else
370: putchar('\n');
371:
372: p->col = 0;
373: }
374:
375:
1.25 kristaps 376: /*
377: * A newline only breaks an existing line; it won't assert vertical
378: * space. All data in the output buffer is flushed prior to the newline
379: * assertion.
380: */
1.3 kristaps 381: void
382: newln(struct termp *p)
383: {
1.1 kristaps 384:
1.3 kristaps 385: p->flags |= TERMP_NOSPACE;
1.12 kristaps 386: if (0 == p->col) {
387: p->flags &= ~TERMP_NOLPAD;
1.3 kristaps 388: return;
1.12 kristaps 389: }
1.3 kristaps 390: flushln(p);
1.11 kristaps 391: p->flags &= ~TERMP_NOLPAD;
1.3 kristaps 392: }
393:
394:
1.25 kristaps 395: /*
396: * Asserts a vertical space (a full, empty line-break between lines).
397: * Note that if used twice, this will cause two blank spaces and so on.
398: * All data in the output buffer is flushed prior to the newline
399: * assertion.
400: */
1.3 kristaps 401: void
402: vspace(struct termp *p)
403: {
404:
405: newln(p);
406: putchar('\n');
407: }
408:
409:
1.25 kristaps 410: /*
411: * Break apart a word into "pwords" (partial-words, usually from
412: * breaking up a phrase into individual words) and, eventually, put them
413: * into the output buffer. If we're a literal word, then don't break up
414: * the word and put it verbatim into the output buffer.
415: */
1.3 kristaps 416: void
417: word(struct termp *p, const char *word)
418: {
419: size_t i, j, len;
420:
421: if (p->flags & TERMP_LITERAL) {
422: pword(p, word, strlen(word));
423: return;
424: }
425:
426: len = strlen(word);
427: assert(len > 0);
428:
429: if (mdoc_isdelim(word)) {
430: if ( ! (p->flags & TERMP_IGNDELIM))
431: p->flags |= TERMP_NOSPACE;
432: p->flags &= ~TERMP_IGNDELIM;
433: }
434:
435: /* LINTED */
436: for (j = i = 0; i < len; i++) {
1.25 kristaps 437: if ( ! isspace((int)word[i])) {
1.3 kristaps 438: j++;
439: continue;
1.20 kristaps 440: }
441:
442: /* Escaped spaces don't delimit... */
1.25 kristaps 443: if (i > 0 && isspace((int)word[i]) &&
444: '\\' == word[i - 1]) {
1.20 kristaps 445: j++;
446: continue;
1.1 kristaps 447: }
1.20 kristaps 448:
1.3 kristaps 449: if (0 == j)
450: continue;
451: assert(i >= j);
452: pword(p, &word[i - j], j);
453: j = 0;
454: }
455: if (j > 0) {
456: assert(i >= j);
457: pword(p, &word[i - j], j);
458: }
459: }
460:
461:
1.25 kristaps 462: /*
463: * This is the main function for printing out nodes. It's constituted
464: * of PRE and POST functions, which correspond to prefix and infix
465: * processing. The termpair structure allows data to persist between
466: * prefix and postfix invocations.
467: */
1.3 kristaps 468: static void
1.12 kristaps 469: body(struct termp *p, struct termpair *ppair,
470: const struct mdoc_meta *meta,
1.3 kristaps 471: const struct mdoc_node *node)
472: {
473: int dochild;
1.9 kristaps 474: struct termpair pair;
1.3 kristaps 475:
476: /* Pre-processing. */
477:
478: dochild = 1;
1.12 kristaps 479: pair.ppair = ppair;
1.9 kristaps 480: pair.type = 0;
1.11 kristaps 481: pair.offset = pair.rmargin = 0;
1.10 kristaps 482: pair.flag = 0;
1.12 kristaps 483: pair.count = 0;
1.3 kristaps 484:
485: if (MDOC_TEXT != node->type) {
486: if (termacts[node->tok].pre)
1.9 kristaps 487: if ( ! (*termacts[node->tok].pre)(p, &pair, meta, node))
1.3 kristaps 488: dochild = 0;
489: } else /* MDOC_TEXT == node->type */
490: word(p, node->data.text.string);
491:
492: /* Children. */
493:
1.10 kristaps 494: if (TERMPAIR_FLAG & pair.type)
495: p->flags |= pair.flag;
1.9 kristaps 496:
1.3 kristaps 497: if (dochild && node->child)
1.12 kristaps 498: body(p, &pair, meta, node->child);
1.3 kristaps 499:
1.10 kristaps 500: if (TERMPAIR_FLAG & pair.type)
501: p->flags &= ~pair.flag;
1.9 kristaps 502:
1.3 kristaps 503: /* Post-processing. */
504:
505: if (MDOC_TEXT != node->type)
506: if (termacts[node->tok].post)
1.9 kristaps 507: (*termacts[node->tok].post)(p, &pair, meta, node);
1.3 kristaps 508:
509: /* Siblings. */
1.1 kristaps 510:
1.3 kristaps 511: if (node->next)
1.12 kristaps 512: body(p, ppair, meta, node->next);
1.3 kristaps 513: }
514:
515:
516: static void
517: footer(struct termp *p, const struct mdoc_meta *meta)
518: {
519: struct tm *tm;
520: char *buf, *os;
521:
522: if (NULL == (buf = malloc(p->rmargin)))
523: err(1, "malloc");
524: if (NULL == (os = malloc(p->rmargin)))
525: err(1, "malloc");
526:
527: tm = localtime(&meta->date);
528:
1.7 kristaps 529: #ifdef __OpenBSD__
530: if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm))
531: #else
1.3 kristaps 532: if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
533: #endif
534: err(1, "strftime");
535:
1.15 kristaps 536: (void)strlcpy(os, meta->os, p->rmargin);
1.3 kristaps 537:
1.16 kristaps 538: /*
539: * This is /slightly/ different from regular groff output
540: * because we don't have page numbers. Print the following:
541: *
542: * OS MDOCDATE
543: */
544:
1.15 kristaps 545: vspace(p);
1.3 kristaps 546:
1.15 kristaps 547: p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
548: p->rmargin = p->maxrmargin - strlen(buf);
549: p->offset = 0;
1.3 kristaps 550:
1.15 kristaps 551: word(p, os);
552: flushln(p);
1.3 kristaps 553:
1.15 kristaps 554: p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
555: p->offset = p->rmargin;
556: p->rmargin = p->maxrmargin;
557: p->flags &= ~TERMP_NOBREAK;
558:
559: word(p, buf);
560: flushln(p);
1.1 kristaps 561:
1.3 kristaps 562: free(buf);
563: free(os);
1.1 kristaps 564: }
565:
566:
1.3 kristaps 567: static void
568: header(struct termp *p, const struct mdoc_meta *meta)
569: {
1.18 kristaps 570: char *buf, *title, *bufp, *vbuf;
1.13 kristaps 571: const char *pp;
1.18 kristaps 572: struct utsname uts;
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");
1.18 kristaps 581: if (NULL == (vbuf = malloc(p->rmargin)))
582: err(1, "malloc");
1.3 kristaps 583:
1.18 kristaps 584: if (NULL == (pp = mdoc_vol2a(meta->vol))) {
1.3 kristaps 585: switch (meta->msec) {
586: case (MSEC_1):
587: /* FALLTHROUGH */
588: case (MSEC_6):
589: /* FALLTHROUGH */
590: case (MSEC_7):
591: pp = mdoc_vol2a(VOL_URM);
592: break;
593: case (MSEC_8):
594: pp = mdoc_vol2a(VOL_SMM);
595: break;
596: case (MSEC_2):
597: /* FALLTHROUGH */
598: case (MSEC_3):
599: /* FALLTHROUGH */
600: case (MSEC_4):
601: /* FALLTHROUGH */
602: case (MSEC_5):
603: pp = mdoc_vol2a(VOL_PRM);
604: break;
605: case (MSEC_9):
606: pp = mdoc_vol2a(VOL_KM);
607: break;
608: default:
609: break;
610: }
1.18 kristaps 611: }
612: vbuf[0] = 0;
613:
614: if (pp) {
615: if (-1 == uname(&uts))
616: err(1, "uname");
617: (void)strlcat(vbuf, uts.sysname, p->rmargin);
618: (void)strlcat(vbuf, " ", p->rmargin);
619: } else if (NULL == (pp = mdoc_msec2a(meta->msec)))
620: pp = mdoc_msec2a(MSEC_local);
621:
622: (void)strlcat(vbuf, pp, p->rmargin);
1.3 kristaps 623:
1.16 kristaps 624: /*
625: * The header is strange. It has three components, which are
626: * really two with the first duplicated. It goes like this:
627: *
628: * IDENTIFIER TITLE IDENTIFIER
629: *
630: * The IDENTIFIER is NAME(SECTION), which is the command-name
631: * (if given, or "unknown" if not) followed by the manual page
632: * section. These are given in `Dt'. The TITLE is a free-form
633: * string depending on the manual volume. If not specified, it
634: * switches on the manual section.
635: */
636:
1.13 kristaps 637: if (mdoc_arch2a(meta->arch))
1.16 kristaps 638: (void)snprintf(buf, p->rmargin, "%s (%s)",
1.18 kristaps 639: vbuf, mdoc_arch2a(meta->arch));
1.13 kristaps 640: else
1.18 kristaps 641: (void)strlcpy(buf, vbuf, p->rmargin);
1.13 kristaps 642:
643: pp = mdoc_msec2a(meta->msec);
644:
645: (void)snprintf(title, p->rmargin, "%s(%s)",
646: meta->title, pp ? pp : "");
647:
1.16 kristaps 648: for (bufp = title; *bufp; bufp++)
649: *bufp = toupper(*bufp);
650:
1.13 kristaps 651: p->offset = 0;
652: p->rmargin = (p->maxrmargin - strlen(buf)) / 2;
1.15 kristaps 653: p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
1.3 kristaps 654:
1.13 kristaps 655: word(p, title);
656: flushln(p);
1.3 kristaps 657:
1.15 kristaps 658: p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
1.13 kristaps 659: p->offset = p->rmargin;
1.15 kristaps 660: p->rmargin = p->maxrmargin - strlen(title);
1.3 kristaps 661:
1.13 kristaps 662: word(p, buf);
663: flushln(p);
1.3 kristaps 664:
1.13 kristaps 665: p->offset = p->rmargin;
666: p->rmargin = p->maxrmargin;
667: p->flags &= ~TERMP_NOBREAK;
1.15 kristaps 668: p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
1.3 kristaps 669:
1.13 kristaps 670: word(p, title);
671: flushln(p);
1.3 kristaps 672:
1.13 kristaps 673: p->rmargin = p->maxrmargin;
674: p->offset = 0;
675: p->flags &= ~TERMP_NOSPACE;
1.3 kristaps 676:
677: free(title);
1.18 kristaps 678: free(vbuf);
1.3 kristaps 679: free(buf);
680: }
1.25 kristaps 681:
682:
683: /*
684: * Determine the symbol indicated by an escape sequences, that is, one
685: * starting with a backslash. Once done, we pass this value into the
686: * output buffer by way of the symbol table.
687: */
688: static void
689: nescape(struct termp *p, const char *word, size_t len)
690: {
1.27 ! kristaps 691: struct termenc *enc;
1.25 kristaps 692:
693: switch (len) {
694: case (1):
1.27 ! kristaps 695: enc = termenc1;
1.25 kristaps 696: break;
697: case (2):
1.27 ! kristaps 698: enc = termenc2;
1.25 kristaps 699: break;
700: default:
1.27 ! kristaps 701: warnx("unsupported %zu-byte escape sequence", len);
! 702: return;
1.25 kristaps 703: }
1.27 ! kristaps 704:
! 705: for ( ; enc->enc; enc++)
! 706: if (0 == memcmp(enc->enc, word, len)) {
! 707: symbola(p, enc->sym);
! 708: return;
! 709: }
! 710:
! 711: warnx("unsupported %zu-byte escape sequence", len);
1.25 kristaps 712: }
713:
714:
715: /*
716: * Handle an escape sequence: determine its length and pass it to the
717: * escape-symbol look table. Note that we assume mdoc(3) has validated
718: * the escape sequence (we assert upon badly-formed escape sequences).
719: */
720: static void
721: pescape(struct termp *p, const char *word, size_t *i, size_t len)
722: {
723: size_t j;
724:
725: (*i)++;
726: assert(*i < len);
727:
728: if ('(' == word[*i]) {
729: (*i)++;
730: assert(*i + 1 < len);
731: nescape(p, &word[*i], 2);
732: (*i)++;
733: return;
734:
735: } else if ('*' == word[*i]) {
736: /* XXX - deprecated! */
737: (*i)++;
738: assert(*i < len);
739: switch (word[*i]) {
740: case ('('):
741: (*i)++;
742: assert(*i + 1 < len);
743: nescape(p, &word[*i], 2);
744: (*i)++;
745: return;
746: case ('['):
747: break;
748: default:
749: nescape(p, &word[*i], 1);
750: return;
751: }
752:
753: } else if ('[' != word[*i]) {
754: nescape(p, &word[*i], 1);
755: return;
756: }
757:
758: (*i)++;
759: for (j = 0; word[*i] && ']' != word[*i]; (*i)++, j++)
760: /* Loop... */ ;
761:
762: assert(word[*i]);
763: nescape(p, &word[*i - j], j);
764: }
765:
766:
767: /*
768: * Handle pwords, partial words, which may be either a single word or a
769: * phrase that cannot be broken down (such as a literal string). This
770: * handles word styling.
771: */
772: static void
773: pword(struct termp *p, const char *word, size_t len)
774: {
775: size_t i;
776:
777: if ( ! (TERMP_NOSPACE & p->flags) &&
778: ! (TERMP_LITERAL & p->flags))
779: chara(p, ' ');
780:
781: if ( ! (p->flags & TERMP_NONOSPACE))
782: p->flags &= ~TERMP_NOSPACE;
783:
784: /*
785: * XXX - if literal and underlining, this will underline the
786: * spaces between literal words.
787: */
788:
789: if (p->flags & TERMP_BOLD)
1.26 kristaps 790: stylea(p, TERMSTYLE_BOLD);
1.25 kristaps 791: if (p->flags & TERMP_UNDERLINE)
1.26 kristaps 792: stylea(p, TERMSTYLE_UNDER);
1.25 kristaps 793:
794: for (i = 0; i < len; i++) {
795: if ('\\' == word[i]) {
796: pescape(p, word, &i, len);
797: continue;
798: }
799: chara(p, word[i]);
800: }
801:
802: if (p->flags & TERMP_BOLD ||
803: p->flags & TERMP_UNDERLINE)
1.26 kristaps 804: stylea(p, TERMSTYLE_CLEAR);
1.25 kristaps 805: }
806:
807:
808: /*
809: * Add a symbol to the output line buffer.
810: */
811: static void
812: symbola(struct termp *p, enum tsym sym)
813: {
814:
815: assert(p->symtab[sym].sym);
816: stringa(p, p->symtab[sym].sym, p->symtab[sym].sz);
817: }
818:
819:
820: /*
1.26 kristaps 821: * Add a style to the output line buffer.
822: */
823: static void
824: stylea(struct termp *p, enum tstyle style)
825: {
826:
827: assert(p->styletab[style].sym);
828: stringa(p, p->styletab[style].sym, p->styletab[style].sz);
829: }
830:
831:
832: /*
1.25 kristaps 833: * Like chara() but for arbitrary-length buffers. Resize the buffer by
834: * a factor of two (if the buffer is less than that) or the buffer's
835: * size.
836: */
837: static void
838: stringa(struct termp *p, const char *c, size_t sz)
839: {
840: size_t s;
841:
1.27 ! kristaps 842: if (0 == sz)
! 843: return;
! 844:
1.25 kristaps 845: s = sz > p->maxcols * 2 ? sz : p->maxcols * 2;
846:
847: assert(c);
848: if (p->col + sz >= p->maxcols) {
849: p->buf = realloc(p->buf, s);
850: if (NULL == p->buf)
851: err(1, "realloc");
852: p->maxcols = s;
853: }
854:
855: (void)memcpy(&p->buf[p->col], c, sz);
856: p->col += sz;
857: }
858:
859:
860: /*
861: * Insert a single character into the line-buffer. If the buffer's
862: * space is exceeded, then allocate more space by doubling the buffer
863: * size.
864: */
865: static void
866: chara(struct termp *p, char c)
867: {
868:
869: if (p->col + 1 >= p->maxcols) {
870: p->buf = realloc(p->buf, p->maxcols * 2);
871: if (NULL == p->buf)
872: err(1, "malloc");
873: p->maxcols *= 2;
874: }
875: p->buf[(p->col)++] = c;
876: }
CVSweb