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