Annotation of mandoc/mdocterm.c, Revision 1.16
1.16 ! kristaps 1: /* $Id: mdocterm.c,v 1.15 2009/02/26 17:11:38 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: */
19: #include <assert.h>
1.3 kristaps 20: #include <ctype.h>
1.1 kristaps 21: #include <err.h>
22: #include <getopt.h>
1.3 kristaps 23: #include <stdio.h>
1.1 kristaps 24: #include <stdlib.h>
1.3 kristaps 25: #include <string.h>
1.1 kristaps 26:
1.7 kristaps 27: #ifndef __OpenBSD__
1.5 kristaps 28: #include <time.h>
29: #endif
30:
1.2 kristaps 31: #include "mmain.h"
1.1 kristaps 32: #include "term.h"
33:
1.16 ! kristaps 34: #define TERMSYM_RBRACK "]"
! 35: #define TERMSYM_LBRACK "["
! 36: #define TERMSYM_LARROW "<-"
! 37: #define TERMSYM_RARROW "->"
! 38: #define TERMSYM_UARROW "^"
! 39: #define TERMSYM_LSQUOTE "`"
! 40: #define TERMSYM_RSQUOTE "\'"
! 41: #define TERMSYM_SQUOTE "\'"
! 42: #define TERMSYM_LDQUOTE "``"
! 43: #define TERMSYM_RDQUOTE "\'\'"
! 44: #define TERMSYM_DQUOTE "\""
! 45: #define TERMSYM_LT "<"
! 46: #define TERMSYM_GT ">"
! 47: #define TERMSYM_LE "<="
! 48: #define TERMSYM_GE ">="
! 49: #define TERMSYM_EQ "=="
! 50: #define TERMSYM_NEQ "!="
! 51: #define TERMSYM_ACUTE "\'"
! 52: #define TERMSYM_GRAVE "`"
! 53: #define TERMSYM_PI "pi"
! 54: #define TERMSYM_PLUSMINUS "+="
! 55: #define TERMSYM_INFINITY "infinity"
! 56: #define TERMSYM_NAN "NaN"
! 57: #define TERMSYM_BAR "|"
! 58: #define TERMSYM_BULLET "o"
! 59:
1.7 kristaps 60: #ifdef __NetBSD__
61: #define xisspace(x) isspace((int)(x))
62: #else
63: #define xisspace(x) isspace((x))
64: #endif
65:
1.3 kristaps 66: enum termstyle {
67: STYLE_CLEAR,
68: STYLE_BOLD,
69: STYLE_UNDERLINE
70: };
71:
72: static void body(struct termp *,
1.12 kristaps 73: struct termpair *,
1.3 kristaps 74: const struct mdoc_meta *,
75: const struct mdoc_node *);
76: static void header(struct termp *,
77: const struct mdoc_meta *);
78: static void footer(struct termp *,
79: const struct mdoc_meta *);
80:
81: static void pword(struct termp *, const char *, size_t);
82: static void pescape(struct termp *,
83: const char *, size_t *, size_t);
1.12 kristaps 84: static void nescape(struct termp *,
85: const char *, size_t);
1.3 kristaps 86: static void chara(struct termp *, char);
1.4 kristaps 87: static void stringa(struct termp *, const char *);
1.3 kristaps 88: static void style(struct termp *, enum termstyle);
89:
90: #ifdef __linux__
91: extern size_t strlcat(char *, const char *, size_t);
92: extern size_t strlcpy(char *, const char *, size_t);
93: #endif
94:
95:
1.1 kristaps 96: int
97: main(int argc, char *argv[])
98: {
1.2 kristaps 99: struct mmain *p;
100: const struct mdoc *mdoc;
1.3 kristaps 101: struct termp termp;
1.2 kristaps 102:
103: p = mmain_alloc();
1.1 kristaps 104:
1.3 kristaps 105: if ( ! mmain_getopt(p, argc, argv, NULL, NULL, NULL, NULL))
1.2 kristaps 106: mmain_exit(p, 1);
1.1 kristaps 107:
1.3 kristaps 108: if (NULL == (mdoc = mmain_mdoc(p)))
109: mmain_exit(p, 1);
110:
111: termp.maxrmargin = 80; /* XXX */
112: termp.rmargin = termp.maxrmargin;
113: termp.maxcols = 1024;
114: termp.offset = termp.col = 0;
115: termp.flags = TERMP_NOSPACE;
116:
117: if (NULL == (termp.buf = malloc(termp.maxcols)))
118: err(1, "malloc");
119:
1.15 kristaps 120: header(&termp, mdoc_meta(mdoc));
1.12 kristaps 121: body(&termp, NULL, mdoc_meta(mdoc), mdoc_node(mdoc));
1.3 kristaps 122: footer(&termp, mdoc_meta(mdoc));
123:
124: free(termp.buf);
125:
126: mmain_exit(p, 0);
127: /* NOTREACHED */
128: }
129:
130:
131: void
132: flushln(struct termp *p)
133: {
134: size_t i, j, vsz, vis, maxvis;
135:
136: /*
137: * First, establish the maximum columns of "visible" content.
138: * This is usually the difference between the right-margin and
139: * an indentation, but can be, for tagged lists or columns, a
140: * small set of values.
141: */
142:
143: assert(p->offset < p->rmargin);
144: maxvis = p->rmargin - p->offset;
145: vis = 0;
146:
147: /*
148: * If in the standard case (left-justified), then begin with our
149: * indentation, otherwise (columns, etc.) just start spitting
150: * out text.
151: */
152:
153: if ( ! (p->flags & TERMP_NOLPAD))
154: /* LINTED */
155: for (j = 0; j < p->offset; j++)
156: putchar(' ');
157:
158: /*
159: * If we're literal, print out verbatim.
160: */
161: if (p->flags & TERMP_LITERAL) {
162: for (i = 0; i < p->col; i++)
163: putchar(p->buf[i]);
164: putchar('\n');
165: p->col = 0;
166: return;
167: }
168:
169: for (i = 0; i < p->col; i++) {
170: /*
171: * Count up visible word characters. Control sequences
172: * (starting with the CSI) aren't counted.
173: */
1.7 kristaps 174: assert( ! xisspace(p->buf[i]));
1.3 kristaps 175:
176: /* LINTED */
177: for (j = i, vsz = 0; j < p->col; j++) {
1.7 kristaps 178: if (xisspace(p->buf[j]))
1.3 kristaps 179: break;
180: else if (27 == p->buf[j]) {
181: assert(j + 4 <= p->col);
182: j += 3;
183: } else
184: vsz++;
185: }
186: assert(vsz > 0);
187:
188: /*
189: * If a word is too long and we're within a line, put it
190: * on the next line. Puke if we're being asked to write
191: * something that will exceed the right margin (i.e.,
192: * from a fresh line or when we're not allowed to break
193: * the line with TERMP_NOBREAK).
194: */
195:
1.16 ! kristaps 196: /* FIXME: allow selective right-margin breaking. */
! 197:
1.14 kristaps 198: if (vis && vis + vsz > maxvis) {
1.3 kristaps 199: if (p->flags & TERMP_NOBREAK)
200: errx(1, "word breaks right margin");
201: putchar('\n');
202: for (j = 0; j < p->offset; j++)
203: putchar(' ');
204: vis = 0;
1.14 kristaps 205: } else if (vis + vsz > maxvis)
1.3 kristaps 206: errx(1, "word breaks right margin");
207:
208: /*
209: * Write out the word and a trailing space. Omit the
210: * space if we're the last word in the line.
211: */
212:
213: for ( ; i < p->col; i++) {
1.7 kristaps 214: if (xisspace(p->buf[i]))
1.3 kristaps 215: break;
216: putchar(p->buf[i]);
217: }
218: vis += vsz;
219: if (i < p->col) {
220: putchar(' ');
221: vis++;
222: }
223: }
224:
225: /*
226: * If we're not to right-marginalise it (newline), then instead
227: * pad to the right margin and stay off.
228: */
229:
230: if (p->flags & TERMP_NOBREAK) {
1.14 kristaps 231: if ( ! (p->flags & TERMP_NORPAD))
232: for ( ; vis < maxvis; vis++)
233: putchar(' ');
1.3 kristaps 234: } else
235: putchar('\n');
236:
237: p->col = 0;
238: }
239:
240:
241: void
242: newln(struct termp *p)
243: {
1.1 kristaps 244:
1.3 kristaps 245: /*
246: * A newline only breaks an existing line; it won't assert
247: * vertical space.
248: */
249: p->flags |= TERMP_NOSPACE;
1.12 kristaps 250: if (0 == p->col) {
251: p->flags &= ~TERMP_NOLPAD;
1.3 kristaps 252: return;
1.12 kristaps 253: }
1.3 kristaps 254: flushln(p);
1.11 kristaps 255: p->flags &= ~TERMP_NOLPAD;
1.3 kristaps 256: }
257:
258:
259: void
260: vspace(struct termp *p)
261: {
262:
263: /*
264: * Asserts a vertical space (a full, empty line-break between
265: * lines).
266: */
267: newln(p);
268: putchar('\n');
269: }
270:
271:
272: static void
1.4 kristaps 273: stringa(struct termp *p, const char *s)
274: {
275:
276: /* XXX - speed up if not passing to chara. */
277: for ( ; *s; s++)
278: chara(p, *s);
279: }
280:
281:
282: static void
1.3 kristaps 283: chara(struct termp *p, char c)
284: {
285:
1.16 ! kristaps 286: /*
! 287: * Insert a single character into the line-buffer. If the
! 288: * buffer's space is exceeded, then allocate more space.
! 289: */
! 290: if (p->col + 1 >= p->maxcols) {
! 291: p->buf = realloc(p->buf, p->maxcols * 2);
! 292: if (NULL == p->buf)
! 293: err(1, "malloc");
! 294: p->maxcols *= 2;
! 295: }
1.3 kristaps 296: p->buf[(p->col)++] = c;
297: }
298:
299:
300: static void
301: style(struct termp *p, enum termstyle esc)
302: {
303:
304: if (p->col + 4 >= p->maxcols)
305: errx(1, "line overrun");
306:
307: p->buf[(p->col)++] = 27;
308: p->buf[(p->col)++] = '[';
309: switch (esc) {
310: case (STYLE_CLEAR):
311: p->buf[(p->col)++] = '0';
312: break;
313: case (STYLE_BOLD):
314: p->buf[(p->col)++] = '1';
315: break;
316: case (STYLE_UNDERLINE):
317: p->buf[(p->col)++] = '4';
318: break;
319: default:
320: abort();
321: /* NOTREACHED */
322: }
323: p->buf[(p->col)++] = 'm';
324: }
325:
326:
327: static void
1.12 kristaps 328: nescape(struct termp *p, const char *word, size_t len)
329: {
330:
331: switch (len) {
1.16 ! kristaps 332: case (1):
! 333: if ('q' == word[0])
! 334: stringa(p, TERMSYM_DQUOTE);
! 335: break;
1.12 kristaps 336: case (2):
337: if ('r' == word[0] && 'B' == word[1])
1.16 ! kristaps 338: stringa(p, TERMSYM_RBRACK);
1.12 kristaps 339: else if ('l' == word[0] && 'B' == word[1])
1.16 ! kristaps 340: stringa(p, TERMSYM_LBRACK);
1.12 kristaps 341: else if ('<' == word[0] && '-' == word[1])
1.16 ! kristaps 342: stringa(p, TERMSYM_LARROW);
1.12 kristaps 343: else if ('-' == word[0] && '>' == word[1])
1.16 ! kristaps 344: stringa(p, TERMSYM_RARROW);
1.12 kristaps 345: else if ('l' == word[0] && 'q' == word[1])
1.16 ! kristaps 346: stringa(p, TERMSYM_DQUOTE);
1.12 kristaps 347: else if ('r' == word[0] && 'q' == word[1])
1.16 ! kristaps 348: stringa(p, TERMSYM_DQUOTE);
1.12 kristaps 349: else if ('b' == word[0] && 'u' == word[1])
1.16 ! kristaps 350: stringa(p, TERMSYM_BULLET);
! 351: else if ('L' == word[0] && 'e' == word[1])
! 352: stringa(p, TERMSYM_LE);
! 353: else if ('<' == word[0] && '=' == word[1])
! 354: stringa(p, TERMSYM_LE);
! 355: else if ('G' == word[0] && 'e' == word[1])
! 356: stringa(p, TERMSYM_GE);
! 357: else if ('>' == word[0] && '=' == word[1])
! 358: stringa(p, TERMSYM_GE);
! 359: else if ('R' == word[0] && 'q' == word[1])
! 360: stringa(p, TERMSYM_RDQUOTE);
! 361: else if ('L' == word[0] && 'q' == word[1])
! 362: stringa(p, TERMSYM_LDQUOTE);
! 363: else if ('u' == word[0] && 'a' == word[1])
! 364: stringa(p, TERMSYM_UARROW);
! 365: else if ('a' == word[0] && 'a' == word[1])
! 366: stringa(p, TERMSYM_ACUTE);
! 367: else if ('g' == word[0] && 'a' == word[1])
! 368: stringa(p, TERMSYM_GRAVE);
! 369: else if ('P' == word[0] && 'i' == word[1])
! 370: stringa(p, TERMSYM_PI);
! 371: else if ('N' == word[0] && 'e' == word[1])
! 372: stringa(p, TERMSYM_NEQ);
! 373: else if ('L' == word[0] && 't' == word[1])
! 374: stringa(p, TERMSYM_LT);
! 375: else if ('G' == word[0] && 't' == word[1])
! 376: stringa(p, TERMSYM_GT);
! 377: else if ('P' == word[0] && 'm' == word[1])
! 378: stringa(p, TERMSYM_PLUSMINUS);
! 379: else if ('I' == word[0] && 'f' == word[1])
! 380: stringa(p, TERMSYM_INFINITY);
! 381: else if ('N' == word[0] && 'a' == word[1])
! 382: stringa(p, TERMSYM_NAN);
! 383: else if ('B' == word[0] && 'a' == word[1])
! 384: stringa(p, TERMSYM_BAR);
1.12 kristaps 385: break;
386: default:
387: break;
388: }
389: }
390:
391:
392: static void
1.3 kristaps 393: pescape(struct termp *p, const char *word, size_t *i, size_t len)
394: {
1.12 kristaps 395: size_t j;
1.3 kristaps 396:
397: (*i)++;
398: assert(*i < len);
399:
1.16 ! kristaps 400: /*
! 401: * Handle an escape sequence. This must manage both groff-style
! 402: * escapes and mdoc-style escapes.
! 403: */
! 404:
1.3 kristaps 405: if ('(' == word[*i]) {
406: /* Two-character escapes. */
407: (*i)++;
408: assert(*i + 1 < len);
1.12 kristaps 409: nescape(p, &word[*i], 2);
1.3 kristaps 410: (*i)++;
411: return;
412:
1.16 ! kristaps 413: } else if ('*' == word[*i]) {
! 414: (*i)++;
! 415: assert(*i < len);
! 416: switch (word[*i]) {
! 417: case ('('):
! 418: (*i)++;
! 419: assert(*i + 1 < len);
! 420: nescape(p, &word[*i], 2);
! 421: (*i)++;
! 422: return;
! 423: default:
! 424: break;
! 425: }
! 426: nescape(p, &word[*i], 1);
! 427: return;
! 428:
1.3 kristaps 429: } else if ('[' != word[*i]) {
430: /* One-character escapes. */
431: switch (word[*i]) {
432: case ('\\'):
433: /* FALLTHROUGH */
434: case ('\''):
435: /* FALLTHROUGH */
436: case ('`'):
437: /* FALLTHROUGH */
438: case ('-'):
439: /* FALLTHROUGH */
1.13 kristaps 440: case (' '):
441: /* FALLTHROUGH */
1.3 kristaps 442: case ('.'):
443: chara(p, word[*i]);
1.1 kristaps 444: default:
1.3 kristaps 445: break;
446: }
447: return;
448: }
1.12 kristaps 449:
450: (*i)++;
451: for (j = 0; word[*i] && ']' != word[*i]; (*i)++, j++)
452: /* Loop... */ ;
453:
454: nescape(p, &word[*i - j], j);
1.3 kristaps 455: }
456:
457:
458: static void
459: pword(struct termp *p, const char *word, size_t len)
460: {
461: size_t i;
462:
463: /*assert(len > 0);*/ /* Can be, if literal. */
464:
1.16 ! kristaps 465: /*
! 466: * Handle pwords, partial words, which may be either a single
! 467: * word or a phrase that cannot be broken down (such as a
! 468: * literal string). This handles word styling.
! 469: */
! 470:
1.3 kristaps 471: if ( ! (p->flags & TERMP_NOSPACE) &&
472: ! (p->flags & TERMP_LITERAL))
473: chara(p, ' ');
474:
1.13 kristaps 475: if ( ! (p->flags & TERMP_NONOSPACE))
476: p->flags &= ~TERMP_NOSPACE;
1.3 kristaps 477:
1.16 ! kristaps 478: /*
! 479: * XXX - if literal and underlining, this will underline the
! 480: * spaces between literal words.
! 481: */
! 482:
1.3 kristaps 483: if (p->flags & TERMP_BOLD)
484: style(p, STYLE_BOLD);
485: if (p->flags & TERMP_UNDERLINE)
486: style(p, STYLE_UNDERLINE);
487:
488: for (i = 0; i < len; i++) {
489: if ('\\' == word[i]) {
490: pescape(p, word, &i, len);
491: continue;
492: }
493: chara(p, word[i]);
494: }
495:
496: if (p->flags & TERMP_BOLD ||
497: p->flags & TERMP_UNDERLINE)
498: style(p, STYLE_CLEAR);
499: }
500:
501:
502: void
503: word(struct termp *p, const char *word)
504: {
505: size_t i, j, len;
506:
1.16 ! kristaps 507: /*
! 508: * Break apart a word into tokens. If we're a literal word,
! 509: * then don't. This doesn't handle zero-length words (there
! 510: * should be none) and makes sure that pword doesn't get spaces
! 511: * or nil words unless literal.
! 512: */
! 513:
1.3 kristaps 514: if (p->flags & TERMP_LITERAL) {
515: pword(p, word, strlen(word));
516: return;
517: }
518:
519: len = strlen(word);
520: assert(len > 0);
521:
522: if (mdoc_isdelim(word)) {
523: if ( ! (p->flags & TERMP_IGNDELIM))
524: p->flags |= TERMP_NOSPACE;
525: p->flags &= ~TERMP_IGNDELIM;
526: }
527:
528: /* LINTED */
529: for (j = i = 0; i < len; i++) {
1.7 kristaps 530: if ( ! xisspace(word[i])) {
1.3 kristaps 531: j++;
532: continue;
1.1 kristaps 533: }
1.3 kristaps 534: if (0 == j)
535: continue;
536: assert(i >= j);
537: pword(p, &word[i - j], j);
538: j = 0;
539: }
540: if (j > 0) {
541: assert(i >= j);
542: pword(p, &word[i - j], j);
543: }
544: }
545:
546:
547: static void
1.12 kristaps 548: body(struct termp *p, struct termpair *ppair,
549: const struct mdoc_meta *meta,
1.3 kristaps 550: const struct mdoc_node *node)
551: {
552: int dochild;
1.9 kristaps 553: struct termpair pair;
1.3 kristaps 554:
1.16 ! kristaps 555: /*
! 556: * This is the main function for printing out nodes. It's
! 557: * constituted of PRE and POST functions, which correspond to
! 558: * prefix and infix processing.
! 559: */
! 560:
1.3 kristaps 561: /* Pre-processing. */
562:
563: dochild = 1;
1.12 kristaps 564: pair.ppair = ppair;
1.9 kristaps 565: pair.type = 0;
1.11 kristaps 566: pair.offset = pair.rmargin = 0;
1.10 kristaps 567: pair.flag = 0;
1.12 kristaps 568: pair.count = 0;
1.3 kristaps 569:
570: if (MDOC_TEXT != node->type) {
571: if (termacts[node->tok].pre)
1.9 kristaps 572: if ( ! (*termacts[node->tok].pre)(p, &pair, meta, node))
1.3 kristaps 573: dochild = 0;
574: } else /* MDOC_TEXT == node->type */
575: word(p, node->data.text.string);
576:
577: /* Children. */
578:
1.10 kristaps 579: if (TERMPAIR_FLAG & pair.type)
580: p->flags |= pair.flag;
1.9 kristaps 581:
1.3 kristaps 582: if (dochild && node->child)
1.12 kristaps 583: body(p, &pair, meta, node->child);
1.3 kristaps 584:
1.10 kristaps 585: if (TERMPAIR_FLAG & pair.type)
586: p->flags &= ~pair.flag;
1.9 kristaps 587:
1.3 kristaps 588: /* Post-processing. */
589:
590: if (MDOC_TEXT != node->type)
591: if (termacts[node->tok].post)
1.9 kristaps 592: (*termacts[node->tok].post)(p, &pair, meta, node);
1.3 kristaps 593:
594: /* Siblings. */
1.1 kristaps 595:
1.3 kristaps 596: if (node->next)
1.12 kristaps 597: body(p, ppair, meta, node->next);
1.3 kristaps 598: }
599:
600:
601: static void
602: footer(struct termp *p, const struct mdoc_meta *meta)
603: {
604: struct tm *tm;
605: char *buf, *os;
606:
607: if (NULL == (buf = malloc(p->rmargin)))
608: err(1, "malloc");
609: if (NULL == (os = malloc(p->rmargin)))
610: err(1, "malloc");
611:
612: tm = localtime(&meta->date);
613:
1.7 kristaps 614: #ifdef __OpenBSD__
615: if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm))
616: #else
1.3 kristaps 617: if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
618: #endif
619: err(1, "strftime");
620:
1.15 kristaps 621: (void)strlcpy(os, meta->os, p->rmargin);
1.3 kristaps 622:
1.16 ! kristaps 623: /*
! 624: * This is /slightly/ different from regular groff output
! 625: * because we don't have page numbers. Print the following:
! 626: *
! 627: * OS MDOCDATE
! 628: */
! 629:
1.15 kristaps 630: vspace(p);
1.3 kristaps 631:
1.15 kristaps 632: p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
633: p->rmargin = p->maxrmargin - strlen(buf);
634: p->offset = 0;
1.3 kristaps 635:
1.15 kristaps 636: word(p, os);
637: flushln(p);
1.3 kristaps 638:
1.15 kristaps 639: p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
640: p->offset = p->rmargin;
641: p->rmargin = p->maxrmargin;
642: p->flags &= ~TERMP_NOBREAK;
643:
644: word(p, buf);
645: flushln(p);
1.1 kristaps 646:
1.3 kristaps 647: free(buf);
648: free(os);
1.1 kristaps 649: }
650:
651:
1.3 kristaps 652: static void
653: header(struct termp *p, const struct mdoc_meta *meta)
654: {
1.16 ! kristaps 655: char *buf, *title, *bufp;
1.13 kristaps 656: const char *pp;
1.3 kristaps 657:
658: if (NULL == (buf = malloc(p->rmargin)))
659: err(1, "malloc");
660: if (NULL == (title = malloc(p->rmargin)))
661: err(1, "malloc");
662:
663: if (NULL == (pp = mdoc_vol2a(meta->vol)))
664: switch (meta->msec) {
665: case (MSEC_1):
666: /* FALLTHROUGH */
667: case (MSEC_6):
668: /* FALLTHROUGH */
669: case (MSEC_7):
670: pp = mdoc_vol2a(VOL_URM);
671: break;
672: case (MSEC_8):
673: pp = mdoc_vol2a(VOL_SMM);
674: break;
675: case (MSEC_2):
676: /* FALLTHROUGH */
677: case (MSEC_3):
678: /* FALLTHROUGH */
679: case (MSEC_4):
680: /* FALLTHROUGH */
681: case (MSEC_5):
682: pp = mdoc_vol2a(VOL_PRM);
683: break;
684: case (MSEC_9):
685: pp = mdoc_vol2a(VOL_KM);
686: break;
687: default:
688: /* FIXME: capitalise. */
689: if (NULL == (pp = mdoc_msec2a(meta->msec)))
690: pp = mdoc_msec2a(MSEC_local);
691: break;
692: }
693:
1.16 ! kristaps 694: /*
! 695: * The header is strange. It has three components, which are
! 696: * really two with the first duplicated. It goes like this:
! 697: *
! 698: * IDENTIFIER TITLE IDENTIFIER
! 699: *
! 700: * The IDENTIFIER is NAME(SECTION), which is the command-name
! 701: * (if given, or "unknown" if not) followed by the manual page
! 702: * section. These are given in `Dt'. The TITLE is a free-form
! 703: * string depending on the manual volume. If not specified, it
! 704: * switches on the manual section.
! 705: */
! 706:
1.13 kristaps 707: if (mdoc_arch2a(meta->arch))
1.16 ! kristaps 708: (void)snprintf(buf, p->rmargin, "%s (%s)",
1.13 kristaps 709: pp, mdoc_arch2a(meta->arch));
710: else
711: (void)strlcpy(buf, pp, p->rmargin);
712:
713: pp = mdoc_msec2a(meta->msec);
714:
715: (void)snprintf(title, p->rmargin, "%s(%s)",
716: meta->title, pp ? pp : "");
717:
1.16 ! kristaps 718: for (bufp = title; *bufp; bufp++)
! 719: *bufp = toupper(*bufp);
! 720:
1.13 kristaps 721: p->offset = 0;
722: p->rmargin = (p->maxrmargin - strlen(buf)) / 2;
1.15 kristaps 723: p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
1.3 kristaps 724:
1.13 kristaps 725: word(p, title);
726: flushln(p);
1.3 kristaps 727:
1.15 kristaps 728: p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
1.13 kristaps 729: p->offset = p->rmargin;
1.15 kristaps 730: p->rmargin = p->maxrmargin - strlen(title);
1.3 kristaps 731:
1.13 kristaps 732: word(p, buf);
733: flushln(p);
1.3 kristaps 734:
1.13 kristaps 735: p->offset = p->rmargin;
736: p->rmargin = p->maxrmargin;
737: p->flags &= ~TERMP_NOBREAK;
1.15 kristaps 738: p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
1.3 kristaps 739:
1.13 kristaps 740: word(p, title);
741: flushln(p);
1.3 kristaps 742:
1.13 kristaps 743: p->rmargin = p->maxrmargin;
744: p->offset = 0;
745: p->flags &= ~TERMP_NOSPACE;
1.3 kristaps 746:
747: free(title);
748: free(buf);
749: }
CVSweb