]> git.cameronkatri.com Git - mandoc.git/blob - term.c
Fixed `.Pf' handling.
[mandoc.git] / term.c
1 /* $Id: term.c,v 1.9 2009/02/22 19:23:48 kristaps Exp $ */
2 /*
3 * Copyright (c) 2008, 2009 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>
20 #include <ctype.h>
21 #include <err.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <unistd.h>
26
27 #ifdef __linux__
28 #include <time.h>
29 #endif
30
31 #include "term.h"
32
33 enum termstyle {
34 STYLE_CLEAR,
35 STYLE_BOLD,
36 STYLE_UNDERLINE
37 };
38
39 static void termprint_r(struct termp *,
40 const struct mdoc_meta *,
41 const struct mdoc_node *);
42 static void termprint_header(struct termp *,
43 const struct mdoc_meta *);
44 static void termprint_footer(struct termp *,
45 const struct mdoc_meta *);
46
47 static void pword(struct termp *, const char *, size_t);
48 static void pescape(struct termp *,
49 const char *, size_t *, size_t);
50 static void chara(struct termp *, char);
51 static void style(struct termp *, enum termstyle);
52
53 #ifdef __linux__
54 extern size_t strlcat(char *, const char *, size_t);
55 extern size_t strlcpy(char *, const char *, size_t);
56 #endif
57
58 void
59 flushln(struct termp *p)
60 {
61 size_t i, j, vsz, vis, maxvis;
62
63 /*
64 * First, establish the maximum columns of "visible" content.
65 * This is usually the difference between the right-margin and
66 * an indentation, but can be, for tagged lists or columns, a
67 * small set of values.
68 */
69
70 assert(p->offset < p->rmargin);
71 maxvis = p->rmargin - p->offset;
72 vis = 0;
73
74 /*
75 * If in the standard case (left-justified), then begin with our
76 * indentation, otherwise (columns, etc.) just start spitting
77 * out text.
78 */
79
80 if ( ! (p->flags & TERMP_NOLPAD))
81 /* LINTED */
82 for (j = 0; j < p->offset; j++)
83 putchar(' ');
84
85 /*
86 * If we're literal, print out verbatim.
87 */
88 if (p->flags & TERMP_LITERAL) {
89 /* FIXME: count non-printing chars. */
90 for (i = 0; i < p->col; i++)
91 putchar(p->buf[i]);
92 putchar('\n');
93 p->col = 0;
94 return;
95 }
96
97 for (i = 0; i < p->col; i++) {
98 /*
99 * Count up visible word characters. Control sequences
100 * (starting with the CSI) aren't counted.
101 */
102 assert( ! isspace(p->buf[i]));
103
104 /* LINTED */
105 for (j = i, vsz = 0; j < p->col; j++) {
106 if (isspace(p->buf[j]))
107 break;
108 else if (27 == p->buf[j]) {
109 assert(j + 4 <= p->col);
110 j += 3;
111 } else
112 vsz++;
113 }
114 assert(vsz > 0);
115
116 /*
117 * If a word is too long and we're within a line, put it
118 * on the next line. Puke if we're being asked to write
119 * something that will exceed the right margin (i.e.,
120 * from a fresh line or when we're not allowed to break
121 * the line with TERMP_NOBREAK).
122 */
123
124 if (vis && vis + vsz >= maxvis) {
125 /* FIXME */
126 if (p->flags & TERMP_NOBREAK)
127 errx(1, "word breaks right margin");
128 putchar('\n');
129 for (j = 0; j < p->offset; j++)
130 putchar(' ');
131 vis = 0;
132 } else if (vis + vsz >= maxvis) {
133 /* FIXME */
134 errx(1, "word breaks right margin");
135 }
136
137 /*
138 * Write out the word and a trailing space. Omit the
139 * space if we're the last word in the line.
140 */
141
142 for ( ; i < p->col; i++) {
143 if (isspace(p->buf[i]))
144 break;
145 putchar(p->buf[i]);
146 }
147 vis += vsz;
148 if (i < p->col) {
149 putchar(' ');
150 vis++;
151 }
152 }
153
154 /*
155 * If we're not to right-marginalise it (newline), then instead
156 * pad to the right margin and stay off.
157 */
158
159 if (p->flags & TERMP_NOBREAK) {
160 for ( ; vis <= maxvis; vis++)
161 putchar(' ');
162 } else
163 putchar('\n');
164
165 p->col = 0;
166 }
167
168
169 void
170 newln(struct termp *p)
171 {
172
173 /*
174 * A newline only breaks an existing line; it won't assert
175 * vertical space.
176 */
177 p->flags |= TERMP_NOSPACE;
178 if (0 == p->col)
179 return;
180 flushln(p);
181 }
182
183
184 void
185 vspace(struct termp *p)
186 {
187
188 /*
189 * Asserts a vertical space (a full, empty line-break between
190 * lines).
191 */
192 newln(p);
193 putchar('\n');
194 }
195
196
197 static void
198 chara(struct termp *p, char c)
199 {
200
201 /* TODO: dynamically expand the buffer. */
202 if (p->col + 1 >= p->maxcols)
203 errx(1, "line overrun");
204 p->buf[(p->col)++] = c;
205 }
206
207
208 static void
209 style(struct termp *p, enum termstyle esc)
210 {
211
212 if (p->col + 4 >= p->maxcols)
213 errx(1, "line overrun");
214
215 p->buf[(p->col)++] = 27;
216 p->buf[(p->col)++] = '[';
217 switch (esc) {
218 case (STYLE_CLEAR):
219 p->buf[(p->col)++] = '0';
220 break;
221 case (STYLE_BOLD):
222 p->buf[(p->col)++] = '1';
223 break;
224 case (STYLE_UNDERLINE):
225 p->buf[(p->col)++] = '4';
226 break;
227 default:
228 abort();
229 /* NOTREACHED */
230 }
231 p->buf[(p->col)++] = 'm';
232 }
233
234
235 static void
236 pescape(struct termp *p, const char *word, size_t *i, size_t len)
237 {
238
239 (*i)++;
240 assert(*i < len);
241
242 if ('(' == word[*i]) {
243 /* Two-character escapes. */
244 (*i)++;
245 assert(*i + 1 < len);
246
247 if ('r' == word[*i] && 'B' == word[*i + 1])
248 chara(p, ']');
249 else if ('l' == word[*i] && 'B' == word[*i + 1])
250 chara(p, '[');
251
252 (*i)++;
253 return;
254
255 } else if ('[' != word[*i]) {
256 /* One-character escapes. */
257 switch (word[*i]) {
258 case ('\\'):
259 /* FALLTHROUGH */
260 case ('\''):
261 /* FALLTHROUGH */
262 case ('`'):
263 /* FALLTHROUGH */
264 case ('-'):
265 /* FALLTHROUGH */
266 case ('.'):
267 chara(p, word[*i]);
268 default:
269 break;
270 }
271 return;
272 }
273 /* n-character escapes. */
274 }
275
276
277 static void
278 pword(struct termp *p, const char *word, size_t len)
279 {
280 size_t i;
281
282 /*assert(len > 0);*/ /* Can be, if literal. */
283
284 if ( ! (p->flags & TERMP_NOSPACE) &&
285 ! (p->flags & TERMP_LITERAL))
286 chara(p, ' ');
287
288 p->flags &= ~TERMP_NOSPACE;
289
290 if (p->flags & TERMP_BOLD)
291 style(p, STYLE_BOLD);
292 if (p->flags & TERMP_UNDERLINE)
293 style(p, STYLE_UNDERLINE);
294
295 for (i = 0; i < len; i++) {
296 if ('\\' == word[i]) {
297 pescape(p, word, &i, len);
298 continue;
299 }
300 chara(p, word[i]);
301 }
302
303 if (p->flags & TERMP_BOLD ||
304 p->flags & TERMP_UNDERLINE)
305 style(p, STYLE_CLEAR);
306 }
307
308
309 void
310 word(struct termp *p, const char *word)
311 {
312 size_t i, j, len;
313
314 if (p->flags & TERMP_LITERAL) {
315 pword(p, word, strlen(word));
316 return;
317 }
318
319 len = strlen(word);
320 assert(len > 0);
321
322 if (mdoc_isdelim(word)) {
323 if ( ! (p->flags & TERMP_IGNDELIM))
324 p->flags |= TERMP_NOSPACE;
325 p->flags &= ~TERMP_IGNDELIM;
326 }
327
328 /* LINTED */
329 for (j = i = 0; i < len; i++) {
330 if ( ! isspace(word[i])) {
331 j++;
332 continue;
333 }
334 if (0 == j)
335 continue;
336 assert(i >= j);
337 pword(p, &word[i - j], j);
338 j = 0;
339 }
340 if (j > 0) {
341 assert(i >= j);
342 pword(p, &word[i - j], j);
343 }
344 }
345
346
347 static void
348 termprint_r(struct termp *p, const struct mdoc_meta *meta,
349 const struct mdoc_node *node)
350 {
351 int dochild;
352
353 /* Pre-processing. */
354
355 dochild = 1;
356
357 if (MDOC_TEXT != node->type) {
358 if (termacts[node->tok].pre)
359 if ( ! (*termacts[node->tok].pre)(p, meta, node))
360 dochild = 0;
361 } else /* MDOC_TEXT == node->type */
362 word(p, node->data.text.string);
363
364 /* Children. */
365
366 if (dochild && node->child)
367 termprint_r(p, meta, node->child);
368
369 /* Post-processing. */
370
371 if (MDOC_TEXT != node->type)
372 if (termacts[node->tok].post)
373 (*termacts[node->tok].post)(p, meta, node);
374
375 /* Siblings. */
376
377 if (node->next)
378 termprint_r(p, meta, node->next);
379 }
380
381
382 static void
383 termprint_footer(struct termp *p, const struct mdoc_meta *meta)
384 {
385 struct tm *tm;
386 char *buf, *os;
387 size_t sz, osz, ssz, i;
388
389 if (NULL == (buf = malloc(p->rmargin)))
390 err(1, "malloc");
391 if (NULL == (os = malloc(p->rmargin)))
392 err(1, "malloc");
393
394 tm = localtime(&meta->date);
395
396 #ifdef __linux__
397 if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
398 #else
399 if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm))
400 #endif
401 err(1, "strftime");
402
403 osz = strlcpy(os, meta->os, p->rmargin);
404
405 sz = strlen(buf);
406 ssz = sz + osz + 1;
407
408 if (ssz > p->rmargin) {
409 ssz -= p->rmargin;
410 assert(ssz <= osz);
411 os[osz - ssz] = 0;
412 ssz = 1;
413 } else
414 ssz = p->rmargin - ssz + 1;
415
416 printf("\n");
417 printf("%s", os);
418 for (i = 0; i < ssz; i++)
419 printf(" ");
420
421 printf("%s\n", buf);
422 fflush(stdout);
423
424 free(buf);
425 free(os);
426 }
427
428
429 static void
430 termprint_header(struct termp *p, const struct mdoc_meta *meta)
431 {
432 char *buf, *title;
433 const char *pp, *msec;
434 size_t ssz, tsz, ttsz, i;;
435
436 if (NULL == (buf = malloc(p->rmargin)))
437 err(1, "malloc");
438 if (NULL == (title = malloc(p->rmargin)))
439 err(1, "malloc");
440
441 if (NULL == (pp = mdoc_vol2a(meta->vol)))
442 switch (meta->msec) {
443 case (MSEC_1):
444 /* FALLTHROUGH */
445 case (MSEC_6):
446 /* FALLTHROUGH */
447 case (MSEC_7):
448 pp = mdoc_vol2a(VOL_URM);
449 break;
450 case (MSEC_8):
451 pp = mdoc_vol2a(VOL_SMM);
452 break;
453 case (MSEC_2):
454 /* FALLTHROUGH */
455 case (MSEC_3):
456 /* FALLTHROUGH */
457 case (MSEC_4):
458 /* FALLTHROUGH */
459 case (MSEC_5):
460 pp = mdoc_vol2a(VOL_PRM);
461 break;
462 case (MSEC_9):
463 pp = mdoc_vol2a(VOL_KM);
464 break;
465 default:
466 /* FIXME: capitalise. */
467 if (NULL == (pp = mdoc_msec2a(meta->msec)))
468 pp = mdoc_msec2a(MSEC_local);
469 break;
470 }
471 assert(pp);
472
473 tsz = strlcpy(buf, pp, p->rmargin);
474 assert(tsz < p->rmargin);
475
476 if ((pp = mdoc_arch2a(meta->arch))) {
477 tsz = strlcat(buf, " (", p->rmargin);
478 assert(tsz < p->rmargin);
479 tsz = strlcat(buf, pp, p->rmargin);
480 assert(tsz < p->rmargin);
481 tsz = strlcat(buf, ")", p->rmargin);
482 assert(tsz < p->rmargin);
483 }
484
485 ttsz = strlcpy(title, meta->title, p->rmargin);
486
487 if (NULL == (msec = mdoc_msec2a(meta->msec)))
488 msec = "";
489
490 ssz = (2 * (ttsz + 2 + strlen(msec))) + tsz + 2;
491
492 if (ssz > p->rmargin) {
493 if ((ssz -= p->rmargin) % 2)
494 ssz++;
495 ssz /= 2;
496
497 assert(ssz <= ttsz);
498 title[ttsz - ssz] = 0;
499 ssz = 1;
500 } else
501 ssz = ((p->rmargin - ssz) / 2) + 1;
502
503 printf("%s(%s)", title, msec);
504
505 for (i = 0; i < ssz; i++)
506 printf(" ");
507
508 printf("%s", buf);
509
510 for (i = 0; i < ssz; i++)
511 printf(" ");
512
513 printf("%s(%s)\n", title, msec);
514 fflush(stdout);
515
516 free(title);
517 free(buf);
518 }
519
520
521 void
522 termprint(const struct mdoc_node *node,
523 const struct mdoc_meta *meta)
524 {
525 struct termp p;
526
527 p.maxrmargin = 80; /* XXX */
528 p.rmargin = p.maxrmargin;
529 p.maxcols = 1024;
530 p.offset = p.col = 0;
531 p.flags = TERMP_NOSPACE;
532
533 if (NULL == (p.buf = malloc(p.maxcols)))
534 err(1, "malloc");
535
536 termprint_header(&p, meta);
537 termprint_r(&p, meta, node);
538 termprint_footer(&p, meta);
539
540 free(p.buf);
541 }
542
543