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