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