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