]> git.cameronkatri.com Git - mandoc.git/blob - mdocterm.c
7fbd94665c2b2e9fa7f9ace2a376e9b748387f50
[mandoc.git] / mdocterm.c
1 /* $Id: mdocterm.c,v 1.31 2009/03/04 14:13:05 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 struct termenc {
36 const char *enc;
37 int sym;
38 };
39
40 static void body(struct termp *,
41 struct termpair *,
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);
50 static void pescape(struct termp *, const char *,
51 size_t *, size_t);
52 static void nescape(struct termp *,
53 const char *, size_t);
54 static void chara(struct termp *, char);
55 static void stringa(struct termp *,
56 const char *, size_t);
57 static void symbola(struct termp *, enum tsym);
58 static void stylea(struct termp *, enum tstyle);
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
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 { "ra", TERMSYM_RANGLE },
82 { "la", TERMSYM_LANGLE },
83 { "Lq", TERMSYM_LDQUOTE },
84 { "lq", TERMSYM_LDQUOTE },
85 { "Rq", TERMSYM_RDQUOTE },
86 { "rq", TERMSYM_RDQUOTE },
87 { "oq", TERMSYM_LSQUOTE },
88 { "aq", TERMSYM_RSQUOTE },
89
90 { "<-", TERMSYM_LARROW },
91 { "->", TERMSYM_RARROW },
92 { "ua", TERMSYM_UARROW },
93 { "da", TERMSYM_DARROW },
94
95 { "bu", TERMSYM_BULLET },
96 { "Ba", TERMSYM_BAR },
97 { "ba", TERMSYM_BAR },
98 { "co", TERMSYM_COPY },
99 { "Am", TERMSYM_AMP },
100
101 { "Le", TERMSYM_LE },
102 { "<=", TERMSYM_LE },
103 { "Ge", TERMSYM_GE },
104 { ">=", TERMSYM_GE },
105 { "==", TERMSYM_EQ },
106 { "Ne", TERMSYM_NEQ },
107 { "!=", TERMSYM_NEQ },
108 { "Pm", TERMSYM_PLUSMINUS },
109 { "+-", TERMSYM_PLUSMINUS },
110 { "If", TERMSYM_INF2 },
111 { "if", TERMSYM_INF },
112 { "Na", TERMSYM_NAN },
113 { "na", TERMSYM_NAN },
114 { "**", TERMSYM_ASTERISK },
115 { "Gt", TERMSYM_GT },
116 { "Lt", TERMSYM_LT },
117
118 { "aa", TERMSYM_ACUTE },
119 { "ga", TERMSYM_GRAVE },
120
121 { "en", TERMSYM_EN },
122 { "em", TERMSYM_EM },
123
124 { "Pi", TERMSYM_PI },
125 { NULL, 0 }
126 };
127
128 static struct termsym termsym_ansi[] = {
129 { "]", 1 }, /* TERMSYM_RBRACK */
130 { "[", 1 }, /* TERMSYM_LBRACK */
131 { "<-", 2 }, /* TERMSYM_LARROW */
132 { "->", 2 }, /* TERMSYM_RARROW */
133 { "^", 1 }, /* TERMSYM_UARROW */
134 { "v", 1 }, /* TERMSYM_DARROW */
135 { "`", 1 }, /* TERMSYM_LSQUOTE */
136 { "\'", 1 }, /* TERMSYM_RSQUOTE */
137 { "\'", 1 }, /* TERMSYM_SQUOTE */
138 { "``", 2 }, /* TERMSYM_LDQUOTE */
139 { "\'\'", 2 }, /* TERMSYM_RDQUOTE */
140 { "\"", 1 }, /* TERMSYM_DQUOTE */
141 { "<", 1 }, /* TERMSYM_LT */
142 { ">", 1 }, /* TERMSYM_GT */
143 { "<=", 2 }, /* TERMSYM_LE */
144 { ">=", 2 }, /* TERMSYM_GE */
145 { "==", 2 }, /* TERMSYM_EQ */
146 { "!=", 2 }, /* TERMSYM_NEQ */
147 { "\'", 1 }, /* TERMSYM_ACUTE */
148 { "`", 1 }, /* TERMSYM_GRAVE */
149 { "pi", 2 }, /* TERMSYM_PI */
150 { "+=", 2 }, /* TERMSYM_PLUSMINUS */
151 { "oo", 2 }, /* TERMSYM_INF */
152 { "infinity", 8 }, /* TERMSYM_INF2 */
153 { "NaN", 3 }, /* TERMSYM_NAN */
154 { "|", 1 }, /* TERMSYM_BAR */
155 { "o", 1 }, /* TERMSYM_BULLET */
156 { "&", 1 }, /* TERMSYM_AMP */
157 { "--", 2 }, /* TERMSYM_EM */
158 { "-", 1 }, /* TERMSYM_EN */
159 { "(C)", 3 }, /* TERMSYM_COPY */
160 { "*", 1 }, /* TERMSYM_ASTERISK */
161 { "\\", 1 }, /* TERMSYM_SLASH */
162 { "-", 1 }, /* TERMSYM_HYPHEN */
163 { " ", 1 }, /* TERMSYM_SPACE */
164 { ".", 1 }, /* TERMSYM_PERIOD */
165 { "", 0 }, /* TERMSYM_BREAK */
166 { "<", 1 }, /* TERMSYM_LANGLE */
167 { ">", 1 }, /* TERMSYM_RANGLE */
168 };
169
170 static const char ansi_clear[] = { 27, '[', '0', 'm' };
171 static const char ansi_bold[] = { 27, '[', '1', 'm' };
172 static const char ansi_under[] = { 27, '[', '4', 'm' };
173
174 static struct termsym termstyle_ansi[] = {
175 { ansi_clear, 4 },
176 { ansi_bold, 4 },
177 { ansi_under, 4 }
178 };
179
180
181 int
182 main(int argc, char *argv[])
183 {
184 struct mmain *p;
185 const struct mdoc *mdoc;
186 struct termp termp;
187
188 p = mmain_alloc();
189
190 if ( ! mmain_getopt(p, argc, argv, NULL, NULL, NULL, NULL))
191 mmain_exit(p, 1);
192
193 if (NULL == (mdoc = mmain_mdoc(p)))
194 mmain_exit(p, 1);
195
196 termp.maxrmargin = termp.rmargin = 78; /* XXX */
197 termp.maxcols = 1024;
198 termp.offset = termp.col = 0;
199 termp.flags = TERMP_NOSPACE;
200 termp.symtab = termsym_ansi;
201 termp.styletab = termstyle_ansi;
202
203 if (NULL == (termp.buf = malloc(termp.maxcols)))
204 err(1, "malloc");
205
206 header(&termp, mdoc_meta(mdoc));
207 body(&termp, NULL, mdoc_meta(mdoc), mdoc_node(mdoc));
208 footer(&termp, mdoc_meta(mdoc));
209
210 free(termp.buf);
211
212 mmain_exit(p, 0);
213 /* NOTREACHED */
214 }
215
216
217 /*
218 * Flush a line of text. A "line" is loosely defined as being something
219 * that should be followed by a newline, regardless of whether it's
220 * broken apart by newlines getting there. A line can also be a
221 * fragment of a columnar list.
222 *
223 * Specifically, a line is whatever's in p->buf of length p->col, which
224 * is zeroed after this function returns.
225 *
226 * The variables TERMP_NOLPAD, TERMP_LITERAL and TERMP_NOBREAK are of
227 * critical importance here. Their behaviour follows:
228 *
229 * - TERMP_NOLPAD: when beginning to write the line, don't left-pad the
230 * offset value. This is useful when doing columnar lists where the
231 * prior column has right-padded.
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 */
249 void
250 flushln(struct termp *p)
251 {
252 size_t i, j, vsz, vis, maxvis, mmax, bp;
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;
263 mmax = p->maxrmargin - p->offset;
264 bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
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
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).
284 */
285
286 /* FIXME: make non-ANSI friendly. */
287
288 /* LINTED */
289 for (j = i, vsz = 0; j < p->col; j++) {
290 if (isspace((int)p->buf[j]))
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 /*
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.
305 */
306
307 if ( ! (TERMP_NOBREAK & p->flags)) {
308 if (vis && vis + vsz > bp) {
309 putchar('\n');
310 for (j = 0; j < p->offset; j++)
311 putchar(' ');
312 vis = 0;
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 - p->offset;
324 } else if (vis + vsz > bp)
325 warnx("word breaks right margin");
326
327 /* TODO: hyphenate. */
328 }
329
330 /*
331 * Write out the word and a trailing space. Omit the
332 * space if we're the last word in the line or beyond
333 * our breakpoint.
334 */
335
336 for ( ; i < p->col; i++) {
337 if (isspace((int)p->buf[i]))
338 break;
339 putchar(p->buf[i]);
340 }
341 vis += vsz;
342 if (i < p->col && vis <= bp) {
343 putchar(' ');
344 vis++;
345 }
346 }
347
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
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
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)
367 for ( ; vis < maxvis; vis++)
368 putchar(' ');
369 else
370 putchar('\n');
371
372 p->col = 0;
373 }
374
375
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 */
381 void
382 newln(struct termp *p)
383 {
384
385 p->flags |= TERMP_NOSPACE;
386 if (0 == p->col) {
387 p->flags &= ~TERMP_NOLPAD;
388 return;
389 }
390 flushln(p);
391 p->flags &= ~TERMP_NOLPAD;
392 }
393
394
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 */
401 void
402 vspace(struct termp *p)
403 {
404
405 newln(p);
406 putchar('\n');
407 }
408
409
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 */
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++) {
437 if ( ! isspace((int)word[i])) {
438 j++;
439 continue;
440 }
441
442 /* Escaped spaces don't delimit... */
443 if (i > 0 && isspace((int)word[i]) &&
444 '\\' == word[i - 1]) {
445 j++;
446 continue;
447 }
448
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
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 */
468 static void
469 body(struct termp *p, struct termpair *ppair,
470 const struct mdoc_meta *meta,
471 const struct mdoc_node *node)
472 {
473 int dochild;
474 struct termpair pair;
475
476 /* Pre-processing. */
477
478 dochild = 1;
479 pair.ppair = ppair;
480 pair.type = 0;
481 pair.offset = pair.rmargin = 0;
482 pair.flag = 0;
483 pair.count = 0;
484
485 if (MDOC_TEXT != node->type) {
486 if (termacts[node->tok].pre)
487 if ( ! (*termacts[node->tok].pre)(p, &pair, meta, node))
488 dochild = 0;
489 } else /* MDOC_TEXT == node->type */
490 word(p, node->data.text.string);
491
492 /* Children. */
493
494 if (TERMPAIR_FLAG & pair.type)
495 p->flags |= pair.flag;
496
497 if (dochild && node->child)
498 body(p, &pair, meta, node->child);
499
500 if (TERMPAIR_FLAG & pair.type)
501 p->flags &= ~pair.flag;
502
503 /* Post-processing. */
504
505 if (MDOC_TEXT != node->type)
506 if (termacts[node->tok].post)
507 (*termacts[node->tok].post)(p, &pair, meta, node);
508
509 /* Siblings. */
510
511 if (node->next)
512 body(p, ppair, meta, node->next);
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
529 #ifdef __OpenBSD__
530 if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm))
531 #else
532 if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
533 #endif
534 err(1, "strftime");
535
536 (void)strlcpy(os, meta->os, p->rmargin);
537
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
545 vspace(p);
546
547 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
548 p->rmargin = p->maxrmargin - strlen(buf);
549 p->offset = 0;
550
551 word(p, os);
552 flushln(p);
553
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);
561
562 free(buf);
563 free(os);
564 }
565
566
567 static void
568 header(struct termp *p, const struct mdoc_meta *meta)
569 {
570 char *buf, *title, *bufp, *vbuf;
571 const char *pp;
572 struct utsname uts;
573
574 p->rmargin = p->maxrmargin;
575 p->offset = 0;
576
577 if (NULL == (buf = malloc(p->rmargin)))
578 err(1, "malloc");
579 if (NULL == (title = malloc(p->rmargin)))
580 err(1, "malloc");
581 if (NULL == (vbuf = malloc(p->rmargin)))
582 err(1, "malloc");
583
584 if (NULL == (pp = mdoc_vol2a(meta->vol))) {
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 }
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);
623
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
637 if (mdoc_arch2a(meta->arch))
638 (void)snprintf(buf, p->rmargin, "%s (%s)",
639 vbuf, mdoc_arch2a(meta->arch));
640 else
641 (void)strlcpy(buf, vbuf, p->rmargin);
642
643 pp = mdoc_msec2a(meta->msec);
644
645 (void)snprintf(title, p->rmargin, "%s(%s)",
646 meta->title, pp ? pp : "");
647
648 for (bufp = title; *bufp; bufp++)
649 *bufp = toupper(*bufp);
650
651 p->offset = 0;
652 p->rmargin = (p->maxrmargin - strlen(buf)) / 2;
653 p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
654
655 word(p, title);
656 flushln(p);
657
658 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
659 p->offset = p->rmargin;
660 p->rmargin = p->maxrmargin - strlen(title);
661
662 word(p, buf);
663 flushln(p);
664
665 p->offset = p->rmargin;
666 p->rmargin = p->maxrmargin;
667 p->flags &= ~TERMP_NOBREAK;
668 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
669
670 word(p, title);
671 flushln(p);
672
673 p->rmargin = p->maxrmargin;
674 p->offset = 0;
675 p->flags &= ~TERMP_NOSPACE;
676
677 free(title);
678 free(vbuf);
679 free(buf);
680 }
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 {
691 struct termenc *enc;
692
693 switch (len) {
694 case (1):
695 enc = termenc1;
696 break;
697 case (2):
698 enc = termenc2;
699 break;
700 default:
701 warnx("unsupported %zu-byte escape sequence", len);
702 return;
703 }
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);
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)
790 stylea(p, TERMSTYLE_BOLD);
791 if (p->flags & TERMP_UNDERLINE)
792 stylea(p, TERMSTYLE_UNDER);
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)
804 stylea(p, TERMSTYLE_CLEAR);
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 /*
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 /*
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
842 if (0 == sz)
843 return;
844
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 }