]> git.cameronkatri.com Git - mandoc.git/blob - mdocterm.c
9149a6d96d8a35a75f8eb60d5c1dd29321df292b
[mandoc.git] / mdocterm.c
1 /* $Id: mdocterm.c,v 1.28 2009/03/03 22:17:19 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 { "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
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 */
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 */
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
176
177 int
178 main(int argc, char *argv[])
179 {
180 struct mmain *p;
181 const struct mdoc *mdoc;
182 struct termp termp;
183
184 p = mmain_alloc();
185
186 if ( ! mmain_getopt(p, argc, argv, NULL, NULL, NULL, NULL))
187 mmain_exit(p, 1);
188
189 if (NULL == (mdoc = mmain_mdoc(p)))
190 mmain_exit(p, 1);
191
192 termp.maxrmargin = 78; /* XXX */
193 termp.rmargin = termp.maxrmargin;
194 termp.maxcols = 1024;
195 termp.offset = termp.col = 0;
196 termp.flags = TERMP_NOSPACE;
197 termp.symtab = termsym_ansi;
198 termp.styletab = termstyle_ansi;
199
200 if (NULL == (termp.buf = malloc(termp.maxcols)))
201 err(1, "malloc");
202
203 header(&termp, mdoc_meta(mdoc));
204 body(&termp, NULL, mdoc_meta(mdoc), mdoc_node(mdoc));
205 footer(&termp, mdoc_meta(mdoc));
206
207 free(termp.buf);
208
209 mmain_exit(p, 0);
210 /* NOTREACHED */
211 }
212
213
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_NOBREAK: this is the most important and is used when making
231 * columns. In short: don't print a newline and instead pad to the
232 * right margin. Used in conjunction with TERMP_NOLPAD.
233 *
234 * In-line line breaking:
235 *
236 * If TERMP_NOBREAK is specified and the line overruns the right
237 * margin, it will break and pad-right to the right margin after
238 * writing. If maxrmargin is violated, it will break and continue
239 * writing from the right-margin, which will lead to the above
240 * scenario upon exit.
241 *
242 * Otherwise, the line will break at the right margin. Extremely long
243 * lines will cause the system to emit a warning (TODO: hyphenate, if
244 * possible).
245 */
246 void
247 flushln(struct termp *p)
248 {
249 size_t i, j, vsz, vis, maxvis, mmax, bp;
250
251 /*
252 * First, establish the maximum columns of "visible" content.
253 * This is usually the difference between the right-margin and
254 * an indentation, but can be, for tagged lists or columns, a
255 * small set of values.
256 */
257
258 assert(p->offset < p->rmargin);
259 maxvis = p->rmargin - p->offset;
260 mmax = p->maxrmargin - p->offset;
261 bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
262 vis = 0;
263
264 /*
265 * If in the standard case (left-justified), then begin with our
266 * indentation, otherwise (columns, etc.) just start spitting
267 * out text.
268 */
269
270 if ( ! (p->flags & TERMP_NOLPAD))
271 /* LINTED */
272 for (j = 0; j < p->offset; j++)
273 putchar(' ');
274
275 for (i = 0; i < p->col; i++) {
276 /*
277 * Count up visible word characters. Control sequences
278 * (starting with the CSI) aren't counted. A space
279 * generates a non-printing word, which is valid (the
280 * space is printed according to regular spacing rules).
281 */
282
283 /* FIXME: make non-ANSI friendly. */
284
285 /* LINTED */
286 for (j = i, vsz = 0; j < p->col; j++) {
287 if (isspace((int)p->buf[j]))
288 break;
289 else if (27 == p->buf[j]) {
290 assert(j + 4 <= p->col);
291 j += 3;
292 } else
293 vsz++;
294 }
295
296 /*
297 * Do line-breaking. If we're greater than our
298 * break-point and already in-line, break to the next
299 * line and start writing. If we're at the line start,
300 * then write out the word (TODO: hyphenate) and break
301 * in a subsequent loop invocation.
302 */
303
304 if ( ! (TERMP_NOBREAK & p->flags)) {
305 if (vis && vis + vsz > bp) {
306 putchar('\n');
307 for (j = 0; j < p->offset; j++)
308 putchar(' ');
309 vis = 0;
310 } else if (vis + vsz > bp)
311 warnx("word breaks right margin");
312
313 /* TODO: hyphenate. */
314
315 } else {
316 if (vis && vis + vsz > bp) {
317 putchar('\n');
318 for (j = 0; j < p->rmargin; j++)
319 putchar(' ');
320 vis = p->rmargin;
321 } else if (vis + vsz > bp)
322 warnx("word breaks right margin");
323
324 /* TODO: hyphenate. */
325 }
326
327 /*
328 * Write out the word and a trailing space. Omit the
329 * space if we're the last word in the line or beyond
330 * our breakpoint.
331 */
332
333 for ( ; i < p->col; i++) {
334 if (isspace((int)p->buf[i]))
335 break;
336 putchar(p->buf[i]);
337 }
338 vis += vsz;
339 if (i < p->col && vis <= bp) {
340 putchar(' ');
341 vis++;
342 }
343 }
344
345 /*
346 * If we've overstepped our maximum visible no-break space, then
347 * cause a newline and offset at the right margin.
348 */
349
350 if ((TERMP_NOBREAK & p->flags) && vis >= maxvis) {
351 putchar('\n');
352 for (i = 0; i < p->rmargin; i++)
353 putchar(' ');
354 p->col = 0;
355 return;
356 }
357
358 /*
359 * If we're not to right-marginalise it (newline), then instead
360 * pad to the right margin and stay off.
361 */
362
363 if (p->flags & TERMP_NOBREAK)
364 for ( ; vis < maxvis; vis++)
365 putchar(' ');
366 else
367 putchar('\n');
368
369 p->col = 0;
370 }
371
372
373 /*
374 * A newline only breaks an existing line; it won't assert vertical
375 * space. All data in the output buffer is flushed prior to the newline
376 * assertion.
377 */
378 void
379 newln(struct termp *p)
380 {
381
382 p->flags |= TERMP_NOSPACE;
383 if (0 == p->col) {
384 p->flags &= ~TERMP_NOLPAD;
385 return;
386 }
387 flushln(p);
388 p->flags &= ~TERMP_NOLPAD;
389 }
390
391
392 /*
393 * Asserts a vertical space (a full, empty line-break between lines).
394 * Note that if used twice, this will cause two blank spaces and so on.
395 * All data in the output buffer is flushed prior to the newline
396 * assertion.
397 */
398 void
399 vspace(struct termp *p)
400 {
401
402 newln(p);
403 putchar('\n');
404 }
405
406
407 /*
408 * Break apart a word into "pwords" (partial-words, usually from
409 * breaking up a phrase into individual words) and, eventually, put them
410 * into the output buffer. If we're a literal word, then don't break up
411 * the word and put it verbatim into the output buffer.
412 */
413 void
414 word(struct termp *p, const char *word)
415 {
416 size_t i, j, len;
417
418 if (p->flags & TERMP_LITERAL) {
419 pword(p, word, strlen(word));
420 return;
421 }
422
423 len = strlen(word);
424 assert(len > 0);
425
426 if (mdoc_isdelim(word)) {
427 if ( ! (p->flags & TERMP_IGNDELIM))
428 p->flags |= TERMP_NOSPACE;
429 p->flags &= ~TERMP_IGNDELIM;
430 }
431
432 /* LINTED */
433 for (j = i = 0; i < len; i++) {
434 if ( ! isspace((int)word[i])) {
435 j++;
436 continue;
437 }
438
439 /* Escaped spaces don't delimit... */
440 if (i > 0 && isspace((int)word[i]) &&
441 '\\' == word[i - 1]) {
442 j++;
443 continue;
444 }
445
446 if (0 == j)
447 continue;
448 assert(i >= j);
449 pword(p, &word[i - j], j);
450 j = 0;
451 }
452 if (j > 0) {
453 assert(i >= j);
454 pword(p, &word[i - j], j);
455 }
456 }
457
458
459 /*
460 * This is the main function for printing out nodes. It's constituted
461 * of PRE and POST functions, which correspond to prefix and infix
462 * processing. The termpair structure allows data to persist between
463 * prefix and postfix invocations.
464 */
465 static void
466 body(struct termp *p, struct termpair *ppair,
467 const struct mdoc_meta *meta,
468 const struct mdoc_node *node)
469 {
470 int dochild;
471 struct termpair pair;
472
473 /* Pre-processing. */
474
475 dochild = 1;
476 pair.ppair = ppair;
477 pair.type = 0;
478 pair.offset = pair.rmargin = 0;
479 pair.flag = 0;
480 pair.count = 0;
481
482 if (MDOC_TEXT != node->type) {
483 if (termacts[node->tok].pre)
484 if ( ! (*termacts[node->tok].pre)(p, &pair, meta, node))
485 dochild = 0;
486 } else /* MDOC_TEXT == node->type */
487 word(p, node->data.text.string);
488
489 /* Children. */
490
491 if (TERMPAIR_FLAG & pair.type)
492 p->flags |= pair.flag;
493
494 if (dochild && node->child)
495 body(p, &pair, meta, node->child);
496
497 if (TERMPAIR_FLAG & pair.type)
498 p->flags &= ~pair.flag;
499
500 /* Post-processing. */
501
502 if (MDOC_TEXT != node->type)
503 if (termacts[node->tok].post)
504 (*termacts[node->tok].post)(p, &pair, meta, node);
505
506 /* Siblings. */
507
508 if (node->next)
509 body(p, ppair, meta, node->next);
510 }
511
512
513 static void
514 footer(struct termp *p, const struct mdoc_meta *meta)
515 {
516 struct tm *tm;
517 char *buf, *os;
518
519 if (NULL == (buf = malloc(p->rmargin)))
520 err(1, "malloc");
521 if (NULL == (os = malloc(p->rmargin)))
522 err(1, "malloc");
523
524 tm = localtime(&meta->date);
525
526 #ifdef __OpenBSD__
527 if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm))
528 #else
529 if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
530 #endif
531 err(1, "strftime");
532
533 (void)strlcpy(os, meta->os, p->rmargin);
534
535 /*
536 * This is /slightly/ different from regular groff output
537 * because we don't have page numbers. Print the following:
538 *
539 * OS MDOCDATE
540 */
541
542 vspace(p);
543
544 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
545 p->rmargin = p->maxrmargin - strlen(buf);
546 p->offset = 0;
547
548 word(p, os);
549 flushln(p);
550
551 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
552 p->offset = p->rmargin;
553 p->rmargin = p->maxrmargin;
554 p->flags &= ~TERMP_NOBREAK;
555
556 word(p, buf);
557 flushln(p);
558
559 free(buf);
560 free(os);
561 }
562
563
564 static void
565 header(struct termp *p, const struct mdoc_meta *meta)
566 {
567 char *buf, *title, *bufp, *vbuf;
568 const char *pp;
569 struct utsname uts;
570
571 p->rmargin = p->maxrmargin;
572 p->offset = 0;
573
574 if (NULL == (buf = malloc(p->rmargin)))
575 err(1, "malloc");
576 if (NULL == (title = malloc(p->rmargin)))
577 err(1, "malloc");
578 if (NULL == (vbuf = malloc(p->rmargin)))
579 err(1, "malloc");
580
581 if (NULL == (pp = mdoc_vol2a(meta->vol))) {
582 switch (meta->msec) {
583 case (MSEC_1):
584 /* FALLTHROUGH */
585 case (MSEC_6):
586 /* FALLTHROUGH */
587 case (MSEC_7):
588 pp = mdoc_vol2a(VOL_URM);
589 break;
590 case (MSEC_8):
591 pp = mdoc_vol2a(VOL_SMM);
592 break;
593 case (MSEC_2):
594 /* FALLTHROUGH */
595 case (MSEC_3):
596 /* FALLTHROUGH */
597 case (MSEC_4):
598 /* FALLTHROUGH */
599 case (MSEC_5):
600 pp = mdoc_vol2a(VOL_PRM);
601 break;
602 case (MSEC_9):
603 pp = mdoc_vol2a(VOL_KM);
604 break;
605 default:
606 break;
607 }
608 }
609 vbuf[0] = 0;
610
611 if (pp) {
612 if (-1 == uname(&uts))
613 err(1, "uname");
614 (void)strlcat(vbuf, uts.sysname, p->rmargin);
615 (void)strlcat(vbuf, " ", p->rmargin);
616 } else if (NULL == (pp = mdoc_msec2a(meta->msec)))
617 pp = mdoc_msec2a(MSEC_local);
618
619 (void)strlcat(vbuf, pp, p->rmargin);
620
621 /*
622 * The header is strange. It has three components, which are
623 * really two with the first duplicated. It goes like this:
624 *
625 * IDENTIFIER TITLE IDENTIFIER
626 *
627 * The IDENTIFIER is NAME(SECTION), which is the command-name
628 * (if given, or "unknown" if not) followed by the manual page
629 * section. These are given in `Dt'. The TITLE is a free-form
630 * string depending on the manual volume. If not specified, it
631 * switches on the manual section.
632 */
633
634 if (mdoc_arch2a(meta->arch))
635 (void)snprintf(buf, p->rmargin, "%s (%s)",
636 vbuf, mdoc_arch2a(meta->arch));
637 else
638 (void)strlcpy(buf, vbuf, p->rmargin);
639
640 pp = mdoc_msec2a(meta->msec);
641
642 (void)snprintf(title, p->rmargin, "%s(%s)",
643 meta->title, pp ? pp : "");
644
645 for (bufp = title; *bufp; bufp++)
646 *bufp = toupper(*bufp);
647
648 p->offset = 0;
649 p->rmargin = (p->maxrmargin - strlen(buf)) / 2;
650 p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
651
652 word(p, title);
653 flushln(p);
654
655 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
656 p->offset = p->rmargin;
657 p->rmargin = p->maxrmargin - strlen(title);
658
659 word(p, buf);
660 flushln(p);
661
662 p->offset = p->rmargin;
663 p->rmargin = p->maxrmargin;
664 p->flags &= ~TERMP_NOBREAK;
665 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
666
667 word(p, title);
668 flushln(p);
669
670 p->rmargin = p->maxrmargin;
671 p->offset = 0;
672 p->flags &= ~TERMP_NOSPACE;
673
674 free(title);
675 free(vbuf);
676 free(buf);
677 }
678
679
680 /*
681 * Determine the symbol indicated by an escape sequences, that is, one
682 * starting with a backslash. Once done, we pass this value into the
683 * output buffer by way of the symbol table.
684 */
685 static void
686 nescape(struct termp *p, const char *word, size_t len)
687 {
688 struct termenc *enc;
689
690 switch (len) {
691 case (1):
692 enc = termenc1;
693 break;
694 case (2):
695 enc = termenc2;
696 break;
697 default:
698 warnx("unsupported %zu-byte escape sequence", len);
699 return;
700 }
701
702 for ( ; enc->enc; enc++)
703 if (0 == memcmp(enc->enc, word, len)) {
704 symbola(p, enc->sym);
705 return;
706 }
707
708 warnx("unsupported %zu-byte escape sequence", len);
709 }
710
711
712 /*
713 * Handle an escape sequence: determine its length and pass it to the
714 * escape-symbol look table. Note that we assume mdoc(3) has validated
715 * the escape sequence (we assert upon badly-formed escape sequences).
716 */
717 static void
718 pescape(struct termp *p, const char *word, size_t *i, size_t len)
719 {
720 size_t j;
721
722 (*i)++;
723 assert(*i < len);
724
725 if ('(' == word[*i]) {
726 (*i)++;
727 assert(*i + 1 < len);
728 nescape(p, &word[*i], 2);
729 (*i)++;
730 return;
731
732 } else if ('*' == word[*i]) {
733 /* XXX - deprecated! */
734 (*i)++;
735 assert(*i < len);
736 switch (word[*i]) {
737 case ('('):
738 (*i)++;
739 assert(*i + 1 < len);
740 nescape(p, &word[*i], 2);
741 (*i)++;
742 return;
743 case ('['):
744 break;
745 default:
746 nescape(p, &word[*i], 1);
747 return;
748 }
749
750 } else if ('[' != word[*i]) {
751 nescape(p, &word[*i], 1);
752 return;
753 }
754
755 (*i)++;
756 for (j = 0; word[*i] && ']' != word[*i]; (*i)++, j++)
757 /* Loop... */ ;
758
759 assert(word[*i]);
760 nescape(p, &word[*i - j], j);
761 }
762
763
764 /*
765 * Handle pwords, partial words, which may be either a single word or a
766 * phrase that cannot be broken down (such as a literal string). This
767 * handles word styling.
768 */
769 static void
770 pword(struct termp *p, const char *word, size_t len)
771 {
772 size_t i;
773
774 if ( ! (TERMP_NOSPACE & p->flags) &&
775 ! (TERMP_LITERAL & p->flags))
776 chara(p, ' ');
777
778 if ( ! (p->flags & TERMP_NONOSPACE))
779 p->flags &= ~TERMP_NOSPACE;
780
781 /*
782 * XXX - if literal and underlining, this will underline the
783 * spaces between literal words.
784 */
785
786 if (p->flags & TERMP_BOLD)
787 stylea(p, TERMSTYLE_BOLD);
788 if (p->flags & TERMP_UNDERLINE)
789 stylea(p, TERMSTYLE_UNDER);
790
791 for (i = 0; i < len; i++) {
792 if ('\\' == word[i]) {
793 pescape(p, word, &i, len);
794 continue;
795 }
796 chara(p, word[i]);
797 }
798
799 if (p->flags & TERMP_BOLD ||
800 p->flags & TERMP_UNDERLINE)
801 stylea(p, TERMSTYLE_CLEAR);
802 }
803
804
805 /*
806 * Add a symbol to the output line buffer.
807 */
808 static void
809 symbola(struct termp *p, enum tsym sym)
810 {
811
812 assert(p->symtab[sym].sym);
813 stringa(p, p->symtab[sym].sym, p->symtab[sym].sz);
814 }
815
816
817 /*
818 * Add a style to the output line buffer.
819 */
820 static void
821 stylea(struct termp *p, enum tstyle style)
822 {
823
824 assert(p->styletab[style].sym);
825 stringa(p, p->styletab[style].sym, p->styletab[style].sz);
826 }
827
828
829 /*
830 * Like chara() but for arbitrary-length buffers. Resize the buffer by
831 * a factor of two (if the buffer is less than that) or the buffer's
832 * size.
833 */
834 static void
835 stringa(struct termp *p, const char *c, size_t sz)
836 {
837 size_t s;
838
839 if (0 == sz)
840 return;
841
842 s = sz > p->maxcols * 2 ? sz : p->maxcols * 2;
843
844 assert(c);
845 if (p->col + sz >= p->maxcols) {
846 p->buf = realloc(p->buf, s);
847 if (NULL == p->buf)
848 err(1, "realloc");
849 p->maxcols = s;
850 }
851
852 (void)memcpy(&p->buf[p->col], c, sz);
853 p->col += sz;
854 }
855
856
857 /*
858 * Insert a single character into the line-buffer. If the buffer's
859 * space is exceeded, then allocate more space by doubling the buffer
860 * size.
861 */
862 static void
863 chara(struct termp *p, char c)
864 {
865
866 if (p->col + 1 >= p->maxcols) {
867 p->buf = realloc(p->buf, p->maxcols * 2);
868 if (NULL == p->buf)
869 err(1, "malloc");
870 p->maxcols *= 2;
871 }
872 p->buf[(p->col)++] = c;
873 }