]> git.cameronkatri.com Git - mandoc.git/blob - term.c
Add a new term_flushln() flag TERMP_BRIND (if break, then indent)
[mandoc.git] / term.c
1 /* $Id: term.c,v 1.221 2014/04/08 07:13:12 schwarze Exp $ */
2 /*
3 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010-2014 Ingo Schwarze <schwarze@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21
22 #include <sys/types.h>
23
24 #include <assert.h>
25 #include <ctype.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #include "mandoc.h"
31 #include "mandoc_aux.h"
32 #include "out.h"
33 #include "term.h"
34 #include "main.h"
35
36 static size_t cond_width(const struct termp *, int, int *);
37 static void adjbuf(struct termp *p, size_t);
38 static void bufferc(struct termp *, char);
39 static void encode(struct termp *, const char *, size_t);
40 static void encode1(struct termp *, int);
41
42 void
43 term_free(struct termp *p)
44 {
45
46 if (p->buf)
47 free(p->buf);
48 if (p->symtab)
49 mchars_free(p->symtab);
50
51 free(p);
52 }
53
54
55 void
56 term_begin(struct termp *p, term_margin head,
57 term_margin foot, const void *arg)
58 {
59
60 p->headf = head;
61 p->footf = foot;
62 p->argf = arg;
63 (*p->begin)(p);
64 }
65
66
67 void
68 term_end(struct termp *p)
69 {
70
71 (*p->end)(p);
72 }
73
74 /*
75 * Flush a chunk of text. By default, break the output line each time
76 * the right margin is reached, and continue output on the next line
77 * at the same offset as the chunk itself. By default, also break the
78 * output line at the end of the chunk.
79 * The following flags may be specified:
80 *
81 * - TERMP_NOBREAK: Do not break the output line at the right margin,
82 * but only at the max right margin. Also, do not break the output
83 * line at the end of the chunk, such that the next call can pad to
84 * the next column. However, if less than p->trailspace blanks,
85 * which can be 0, 1, or 2, remain to the right margin, the line
86 * will be broken.
87 * - TERMP_BRIND: If the chunk does not fit and the output line has
88 * to be broken, start the next line at the right margin instead
89 * of at the offset. Used together with TERMP_NOBREAK for the tags
90 * in various kinds of tagged lists.
91 * - TERMP_DANGLE: Do not break the output line at the right margin,
92 * append the next chunk after it even if this one is too long.
93 * To be used together with TERMP_NOBREAK.
94 * - TERMP_HANG: Like TERMP_DANGLE, and also suppress padding before
95 * the next chunk if this column is not full.
96 */
97 void
98 term_flushln(struct termp *p)
99 {
100 size_t i; /* current input position in p->buf */
101 int ntab; /* number of tabs to prepend */
102 size_t vis; /* current visual position on output */
103 size_t vbl; /* number of blanks to prepend to output */
104 size_t vend; /* end of word visual position on output */
105 size_t bp; /* visual right border position */
106 size_t dv; /* temporary for visual pos calculations */
107 size_t j; /* temporary loop index for p->buf */
108 size_t jhy; /* last hyph before overflow w/r/t j */
109 size_t maxvis; /* output position of visible boundary */
110 size_t mmax; /* used in calculating bp */
111
112 /*
113 * First, establish the maximum columns of "visible" content.
114 * This is usually the difference between the right-margin and
115 * an indentation, but can be, for tagged lists or columns, a
116 * small set of values.
117 *
118 * The following unsigned-signed subtractions look strange,
119 * but they are actually correct. If the int p->overstep
120 * is negative, it gets sign extended. Subtracting that
121 * very large size_t effectively adds a small number to dv.
122 */
123 assert (p->rmargin >= p->offset);
124 dv = p->rmargin - p->offset;
125 maxvis = (int)dv > p->overstep ? dv - (size_t)p->overstep : 0;
126 dv = p->maxrmargin - p->offset;
127 mmax = (int)dv > p->overstep ? dv - (size_t)p->overstep : 0;
128
129 bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
130
131 /*
132 * Calculate the required amount of padding.
133 */
134 vbl = p->offset + p->overstep > p->viscol ?
135 p->offset + p->overstep - p->viscol : 0;
136
137 vis = vend = 0;
138 i = 0;
139
140 while (i < p->col) {
141 /*
142 * Handle literal tab characters: collapse all
143 * subsequent tabs into a single huge set of spaces.
144 */
145 ntab = 0;
146 while (i < p->col && '\t' == p->buf[i]) {
147 vend = (vis / p->tabwidth + 1) * p->tabwidth;
148 vbl += vend - vis;
149 vis = vend;
150 ntab++;
151 i++;
152 }
153
154 /*
155 * Count up visible word characters. Control sequences
156 * (starting with the CSI) aren't counted. A space
157 * generates a non-printing word, which is valid (the
158 * space is printed according to regular spacing rules).
159 */
160
161 for (j = i, jhy = 0; j < p->col; j++) {
162 if (' ' == p->buf[j] || '\t' == p->buf[j])
163 break;
164
165 /* Back over the the last printed character. */
166 if (8 == p->buf[j]) {
167 assert(j);
168 vend -= (*p->width)(p, p->buf[j - 1]);
169 continue;
170 }
171
172 /* Regular word. */
173 /* Break at the hyphen point if we overrun. */
174 if (vend > vis && vend < bp &&
175 (ASCII_HYPH == p->buf[j] ||
176 ASCII_BREAK == p->buf[j]))
177 jhy = j;
178
179 /*
180 * Hyphenation now decided, put back a real
181 * hyphen such that we get the correct width.
182 */
183 if (ASCII_HYPH == p->buf[j])
184 p->buf[j] = '-';
185
186 vend += (*p->width)(p, p->buf[j]);
187 }
188
189 /*
190 * Find out whether we would exceed the right margin.
191 * If so, break to the next line.
192 */
193 if (vend > bp && 0 == jhy && vis > 0) {
194 vend -= vis;
195 (*p->endline)(p);
196 p->viscol = 0;
197 if (TERMP_BRIND & p->flags) {
198 vbl = p->rmargin;
199 vend += p->rmargin - p->offset;
200 } else
201 vbl = p->offset;
202
203 /* use pending tabs on the new line */
204
205 if (0 < ntab)
206 vbl += ntab * p->tabwidth;
207
208 /*
209 * Remove the p->overstep width.
210 * Again, if p->overstep is negative,
211 * sign extension does the right thing.
212 */
213
214 bp += (size_t)p->overstep;
215 p->overstep = 0;
216 }
217
218 /* Write out the [remaining] word. */
219 for ( ; i < p->col; i++) {
220 if (vend > bp && jhy > 0 && i > jhy)
221 break;
222 if ('\t' == p->buf[i])
223 break;
224 if (' ' == p->buf[i]) {
225 j = i;
226 while (' ' == p->buf[i])
227 i++;
228 dv = (i - j) * (*p->width)(p, ' ');
229 vbl += dv;
230 vend += dv;
231 break;
232 }
233 if (ASCII_NBRSP == p->buf[i]) {
234 vbl += (*p->width)(p, ' ');
235 continue;
236 }
237 if (ASCII_BREAK == p->buf[i])
238 continue;
239
240 /*
241 * Now we definitely know there will be
242 * printable characters to output,
243 * so write preceding white space now.
244 */
245 if (vbl) {
246 (*p->advance)(p, vbl);
247 p->viscol += vbl;
248 vbl = 0;
249 }
250
251 (*p->letter)(p, p->buf[i]);
252 if (8 == p->buf[i])
253 p->viscol -= (*p->width)(p, p->buf[i-1]);
254 else
255 p->viscol += (*p->width)(p, p->buf[i]);
256 }
257 vis = vend;
258 }
259
260 /*
261 * If there was trailing white space, it was not printed;
262 * so reset the cursor position accordingly.
263 */
264 if (vis)
265 vis -= vbl;
266
267 p->col = 0;
268 p->overstep = 0;
269
270 if ( ! (TERMP_NOBREAK & p->flags)) {
271 p->viscol = 0;
272 (*p->endline)(p);
273 return;
274 }
275
276 if (TERMP_HANG & p->flags) {
277 p->overstep = (int)(vis - maxvis +
278 p->trailspace * (*p->width)(p, ' '));
279
280 /*
281 * If we have overstepped the margin, temporarily move
282 * it to the right and flag the rest of the line to be
283 * shorter.
284 * If there is a request to keep the columns together,
285 * allow negative overstep when the column is not full.
286 */
287 if (p->trailspace && p->overstep < 0)
288 p->overstep = 0;
289 return;
290
291 } else if (TERMP_DANGLE & p->flags)
292 return;
293
294 /* If the column was overrun, break the line. */
295 if (maxvis < vis + p->trailspace * (*p->width)(p, ' ')) {
296 (*p->endline)(p);
297 p->viscol = 0;
298 }
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 term_newln(struct termp *p)
309 {
310
311 p->flags |= TERMP_NOSPACE;
312 if (p->col || p->viscol)
313 term_flushln(p);
314 }
315
316
317 /*
318 * Asserts a vertical space (a full, empty line-break between lines).
319 * Note that if used twice, this will cause two blank spaces and so on.
320 * All data in the output buffer is flushed prior to the newline
321 * assertion.
322 */
323 void
324 term_vspace(struct termp *p)
325 {
326
327 term_newln(p);
328 p->viscol = 0;
329 if (0 < p->skipvsp)
330 p->skipvsp--;
331 else
332 (*p->endline)(p);
333 }
334
335 void
336 term_fontlast(struct termp *p)
337 {
338 enum termfont f;
339
340 f = p->fontl;
341 p->fontl = p->fontq[p->fonti];
342 p->fontq[p->fonti] = f;
343 }
344
345
346 void
347 term_fontrepl(struct termp *p, enum termfont f)
348 {
349
350 p->fontl = p->fontq[p->fonti];
351 p->fontq[p->fonti] = f;
352 }
353
354
355 void
356 term_fontpush(struct termp *p, enum termfont f)
357 {
358
359 assert(p->fonti + 1 < 10);
360 p->fontl = p->fontq[p->fonti];
361 p->fontq[++p->fonti] = f;
362 }
363
364
365 const void *
366 term_fontq(struct termp *p)
367 {
368
369 return(&p->fontq[p->fonti]);
370 }
371
372
373 enum termfont
374 term_fonttop(struct termp *p)
375 {
376
377 return(p->fontq[p->fonti]);
378 }
379
380
381 void
382 term_fontpopq(struct termp *p, const void *key)
383 {
384
385 while (p->fonti >= 0 && key < (void *)(p->fontq + p->fonti))
386 p->fonti--;
387 assert(p->fonti >= 0);
388 }
389
390
391 void
392 term_fontpop(struct termp *p)
393 {
394
395 assert(p->fonti);
396 p->fonti--;
397 }
398
399 /*
400 * Handle pwords, partial words, which may be either a single word or a
401 * phrase that cannot be broken down (such as a literal string). This
402 * handles word styling.
403 */
404 void
405 term_word(struct termp *p, const char *word)
406 {
407 const char nbrsp[2] = { ASCII_NBRSP, 0 };
408 const char *seq, *cp;
409 char c;
410 int sz, uc;
411 size_t ssz;
412 enum mandoc_esc esc;
413
414 if ( ! (TERMP_NOSPACE & p->flags)) {
415 if ( ! (TERMP_KEEP & p->flags)) {
416 bufferc(p, ' ');
417 if (TERMP_SENTENCE & p->flags)
418 bufferc(p, ' ');
419 } else
420 bufferc(p, ASCII_NBRSP);
421 }
422 if (TERMP_PREKEEP & p->flags)
423 p->flags |= TERMP_KEEP;
424
425 if ( ! (p->flags & TERMP_NONOSPACE))
426 p->flags &= ~TERMP_NOSPACE;
427 else
428 p->flags |= TERMP_NOSPACE;
429
430 p->flags &= ~TERMP_SENTENCE;
431
432 while ('\0' != *word) {
433 if ('\\' != *word) {
434 if (TERMP_SKIPCHAR & p->flags) {
435 p->flags &= ~TERMP_SKIPCHAR;
436 word++;
437 continue;
438 }
439 if (TERMP_NBRWORD & p->flags) {
440 if (' ' == *word) {
441 encode(p, nbrsp, 1);
442 word++;
443 continue;
444 }
445 ssz = strcspn(word, "\\ ");
446 } else
447 ssz = strcspn(word, "\\");
448 encode(p, word, ssz);
449 word += (int)ssz;
450 continue;
451 }
452
453 word++;
454 esc = mandoc_escape(&word, &seq, &sz);
455 if (ESCAPE_ERROR == esc)
456 break;
457
458 if (TERMENC_ASCII != p->enc)
459 switch (esc) {
460 case (ESCAPE_UNICODE):
461 uc = mchars_num2uc(seq + 1, sz - 1);
462 if ('\0' == uc)
463 break;
464 encode1(p, uc);
465 continue;
466 case (ESCAPE_SPECIAL):
467 uc = mchars_spec2cp(p->symtab, seq, sz);
468 if (uc <= 0)
469 break;
470 encode1(p, uc);
471 continue;
472 default:
473 break;
474 }
475
476 switch (esc) {
477 case (ESCAPE_UNICODE):
478 encode1(p, '?');
479 break;
480 case (ESCAPE_NUMBERED):
481 c = mchars_num2char(seq, sz);
482 if ('\0' != c)
483 encode(p, &c, 1);
484 break;
485 case (ESCAPE_SPECIAL):
486 cp = mchars_spec2str(p->symtab, seq, sz, &ssz);
487 if (NULL != cp)
488 encode(p, cp, ssz);
489 else if (1 == ssz)
490 encode(p, seq, sz);
491 break;
492 case (ESCAPE_FONTBOLD):
493 term_fontrepl(p, TERMFONT_BOLD);
494 break;
495 case (ESCAPE_FONTITALIC):
496 term_fontrepl(p, TERMFONT_UNDER);
497 break;
498 case (ESCAPE_FONTBI):
499 term_fontrepl(p, TERMFONT_BI);
500 break;
501 case (ESCAPE_FONT):
502 /* FALLTHROUGH */
503 case (ESCAPE_FONTROMAN):
504 term_fontrepl(p, TERMFONT_NONE);
505 break;
506 case (ESCAPE_FONTPREV):
507 term_fontlast(p);
508 break;
509 case (ESCAPE_NOSPACE):
510 if (TERMP_SKIPCHAR & p->flags)
511 p->flags &= ~TERMP_SKIPCHAR;
512 else if ('\0' == *word)
513 p->flags |= TERMP_NOSPACE;
514 break;
515 case (ESCAPE_SKIPCHAR):
516 p->flags |= TERMP_SKIPCHAR;
517 break;
518 default:
519 break;
520 }
521 }
522 p->flags &= ~TERMP_NBRWORD;
523 }
524
525 static void
526 adjbuf(struct termp *p, size_t sz)
527 {
528
529 if (0 == p->maxcols)
530 p->maxcols = 1024;
531 while (sz >= p->maxcols)
532 p->maxcols <<= 2;
533
534 p->buf = mandoc_realloc(p->buf, sizeof(int) * p->maxcols);
535 }
536
537 static void
538 bufferc(struct termp *p, char c)
539 {
540
541 if (p->col + 1 >= p->maxcols)
542 adjbuf(p, p->col + 1);
543
544 p->buf[p->col++] = c;
545 }
546
547 /*
548 * See encode().
549 * Do this for a single (probably unicode) value.
550 * Does not check for non-decorated glyphs.
551 */
552 static void
553 encode1(struct termp *p, int c)
554 {
555 enum termfont f;
556
557 if (TERMP_SKIPCHAR & p->flags) {
558 p->flags &= ~TERMP_SKIPCHAR;
559 return;
560 }
561
562 if (p->col + 6 >= p->maxcols)
563 adjbuf(p, p->col + 6);
564
565 f = term_fonttop(p);
566
567 if (TERMFONT_UNDER == f || TERMFONT_BI == f) {
568 p->buf[p->col++] = '_';
569 p->buf[p->col++] = 8;
570 }
571 if (TERMFONT_BOLD == f || TERMFONT_BI == f) {
572 if (ASCII_HYPH == c)
573 p->buf[p->col++] = '-';
574 else
575 p->buf[p->col++] = c;
576 p->buf[p->col++] = 8;
577 }
578 p->buf[p->col++] = c;
579 }
580
581 static void
582 encode(struct termp *p, const char *word, size_t sz)
583 {
584 size_t i;
585
586 if (TERMP_SKIPCHAR & p->flags) {
587 p->flags &= ~TERMP_SKIPCHAR;
588 return;
589 }
590
591 /*
592 * Encode and buffer a string of characters. If the current
593 * font mode is unset, buffer directly, else encode then buffer
594 * character by character.
595 */
596
597 if (TERMFONT_NONE == term_fonttop(p)) {
598 if (p->col + sz >= p->maxcols)
599 adjbuf(p, p->col + sz);
600 for (i = 0; i < sz; i++)
601 p->buf[p->col++] = word[i];
602 return;
603 }
604
605 /* Pre-buffer, assuming worst-case. */
606
607 if (p->col + 1 + (sz * 5) >= p->maxcols)
608 adjbuf(p, p->col + 1 + (sz * 5));
609
610 for (i = 0; i < sz; i++) {
611 if (ASCII_HYPH == word[i] ||
612 isgraph((unsigned char)word[i]))
613 encode1(p, word[i]);
614 else
615 p->buf[p->col++] = word[i];
616 }
617 }
618
619 void
620 term_setwidth(struct termp *p, const char *wstr)
621 {
622 struct roffsu su;
623 size_t width;
624 int iop;
625
626 iop = 0;
627 width = 0;
628 if (NULL != wstr) {
629 switch (*wstr) {
630 case ('+'):
631 iop = 1;
632 wstr++;
633 break;
634 case ('-'):
635 iop = -1;
636 wstr++;
637 break;
638 default:
639 break;
640 }
641 if (a2roffsu(wstr, &su, SCALE_MAX))
642 width = term_hspan(p, &su);
643 else
644 iop = 0;
645 }
646 (*p->setwidth)(p, iop, width);
647 }
648
649 size_t
650 term_len(const struct termp *p, size_t sz)
651 {
652
653 return((*p->width)(p, ' ') * sz);
654 }
655
656 static size_t
657 cond_width(const struct termp *p, int c, int *skip)
658 {
659
660 if (*skip) {
661 (*skip) = 0;
662 return(0);
663 } else
664 return((*p->width)(p, c));
665 }
666
667 size_t
668 term_strlen(const struct termp *p, const char *cp)
669 {
670 size_t sz, rsz, i;
671 int ssz, skip, c;
672 const char *seq, *rhs;
673 enum mandoc_esc esc;
674 static const char rej[] = { '\\', ASCII_NBRSP, ASCII_HYPH,
675 ASCII_BREAK, '\0' };
676
677 /*
678 * Account for escaped sequences within string length
679 * calculations. This follows the logic in term_word() as we
680 * must calculate the width of produced strings.
681 */
682
683 sz = 0;
684 skip = 0;
685 while ('\0' != *cp) {
686 rsz = strcspn(cp, rej);
687 for (i = 0; i < rsz; i++)
688 sz += cond_width(p, *cp++, &skip);
689
690 switch (*cp) {
691 case ('\\'):
692 cp++;
693 esc = mandoc_escape(&cp, &seq, &ssz);
694 if (ESCAPE_ERROR == esc)
695 return(sz);
696
697 if (TERMENC_ASCII != p->enc)
698 switch (esc) {
699 case (ESCAPE_UNICODE):
700 c = mchars_num2uc
701 (seq + 1, ssz - 1);
702 if ('\0' == c)
703 break;
704 sz += cond_width(p, c, &skip);
705 continue;
706 case (ESCAPE_SPECIAL):
707 c = mchars_spec2cp
708 (p->symtab, seq, ssz);
709 if (c <= 0)
710 break;
711 sz += cond_width(p, c, &skip);
712 continue;
713 default:
714 break;
715 }
716
717 rhs = NULL;
718
719 switch (esc) {
720 case (ESCAPE_UNICODE):
721 sz += cond_width(p, '?', &skip);
722 break;
723 case (ESCAPE_NUMBERED):
724 c = mchars_num2char(seq, ssz);
725 if ('\0' != c)
726 sz += cond_width(p, c, &skip);
727 break;
728 case (ESCAPE_SPECIAL):
729 rhs = mchars_spec2str
730 (p->symtab, seq, ssz, &rsz);
731
732 if (ssz != 1 || rhs)
733 break;
734
735 rhs = seq;
736 rsz = ssz;
737 break;
738 case (ESCAPE_SKIPCHAR):
739 skip = 1;
740 break;
741 default:
742 break;
743 }
744
745 if (NULL == rhs)
746 break;
747
748 if (skip) {
749 skip = 0;
750 break;
751 }
752
753 for (i = 0; i < rsz; i++)
754 sz += (*p->width)(p, *rhs++);
755 break;
756 case (ASCII_NBRSP):
757 sz += cond_width(p, ' ', &skip);
758 cp++;
759 break;
760 case (ASCII_HYPH):
761 sz += cond_width(p, '-', &skip);
762 cp++;
763 /* FALLTHROUGH */
764 case (ASCII_BREAK):
765 break;
766 default:
767 break;
768 }
769 }
770
771 return(sz);
772 }
773
774 /* ARGSUSED */
775 size_t
776 term_vspan(const struct termp *p, const struct roffsu *su)
777 {
778 double r;
779
780 switch (su->unit) {
781 case (SCALE_CM):
782 r = su->scale * 2;
783 break;
784 case (SCALE_IN):
785 r = su->scale * 6;
786 break;
787 case (SCALE_PC):
788 r = su->scale;
789 break;
790 case (SCALE_PT):
791 r = su->scale / 8;
792 break;
793 case (SCALE_MM):
794 r = su->scale / 1000;
795 break;
796 case (SCALE_VS):
797 r = su->scale;
798 break;
799 default:
800 r = su->scale - 1;
801 break;
802 }
803
804 if (r < 0.0)
805 r = 0.0;
806 return(/* LINTED */(size_t)
807 r);
808 }
809
810 size_t
811 term_hspan(const struct termp *p, const struct roffsu *su)
812 {
813 double v;
814
815 v = ((*p->hspan)(p, su));
816 if (v < 0.0)
817 v = 0.0;
818 return((size_t) /* LINTED */
819 v);
820 }