]>
git.cameronkatri.com Git - mandoc.git/blob - term.c
f8e7c395e2ba83d3a115d525b2955890615bb558
1 /* $Id: term.c,v 1.142 2010/06/07 20:57:09 kristaps Exp $ */
3 * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 #include <sys/types.h>
39 #define PS_CHAR_WIDTH 6
40 #define PS_CHAR_HEIGHT 12
41 #define PS_CHAR_TOPMARG (792 - 24)
42 #define PS_CHAR_TOP (PS_CHAR_TOPMARG - 36)
43 #define PS_CHAR_LEFT 36
44 #define PS_CHAR_BOTMARG 24
45 #define PS_CHAR_BOT (PS_CHAR_BOTMARG + 36)
47 static struct termp
*alloc(char *, enum termenc
, enum termtype
);
48 static void term_free(struct termp
*);
49 static void spec(struct termp
*, const char *, size_t);
50 static void res(struct termp
*, const char *, size_t);
51 static void buffera(struct termp
*, const char *, size_t);
52 static void bufferc(struct termp
*, char);
53 static void adjbuf(struct termp
*p
, size_t);
54 static void encode(struct termp
*, const char *, size_t);
55 static void advance(struct termp
*, size_t);
56 static void endline(struct termp
*);
57 static void letter(struct termp
*, char);
58 static void pageopen(struct termp
*);
62 ascii_alloc(char *outopts
)
65 return(alloc(outopts
, TERMENC_ASCII
, TERMTYPE_CHAR
));
73 return(alloc(NULL
, TERMENC_ASCII
, TERMTYPE_PS
));
78 terminal_free(void *arg
)
81 term_free((struct termp
*)arg
);
86 term_free(struct termp
*p
)
92 chars_free(p
->symtab
);
98 * Push a single letter into our output engine.
101 letter(struct termp
*p
, char c
)
104 if (TERMTYPE_CHAR
== p
->type
) {
106 * If using the terminal device, just push the letter
107 * out into the screen.
113 if ( ! (PS_INLINE
& p
->psstate
)) {
115 * If we're not in a PostScript "word" context, then
116 * open one now at the current cursor.
118 printf("%zu %zu moveto\n", p
->pscol
, p
->psrow
);
120 p
->psstate
|= PS_INLINE
;
124 * We need to escape these characters as per the PostScript
125 * specification. We would also escape non-graphable characters
126 * (like tabs), but none of them would get to this point and
127 * it's superfluous to abort() on them.
142 /* Write the character and adjust where we are on the page. */
144 p
->pscol
+= PS_CHAR_WIDTH
;
149 * Begin a "terminal" context. Since terminal encompasses PostScript,
150 * the actual terminal, etc., there are a few things we can do here.
153 term_begin(struct termp
*p
, term_margin head
,
154 term_margin foot
, const void *arg
)
161 if (TERMTYPE_CHAR
== p
->type
) {
162 /* Emit the header and be done. */
163 (*p
->headf
)(p
, p
->argf
);
168 * Emit the standard PostScript prologue, set our initial page
169 * position, then run pageopen() on the initial page.
172 printf("%s\n", "%!PS");
173 printf("%s\n", "/Courier");
174 printf("%s\n", "10 selectfont");
183 * Open a page. This is only used for -Tps at the moment. It opens a
184 * page context, printing the header and the footer. THE OUTPUT BUFFER
185 * MUST BE EMPTY. If it is not, output will ghost on the next line and
186 * we'll be all gross and out of state.
189 pageopen(struct termp
*p
)
192 assert(TERMTYPE_PS
== p
->type
);
193 assert(0 == p
->psstate
);
195 p
->pscol
= PS_CHAR_LEFT
;
196 p
->psrow
= PS_CHAR_TOPMARG
;
197 p
->psstate
|= PS_MARGINS
;
199 (*p
->headf
)(p
, p
->argf
);
202 p
->psstate
&= ~PS_MARGINS
;
203 assert(0 == p
->psstate
);
205 p
->pscol
= PS_CHAR_LEFT
;
206 p
->psrow
= PS_CHAR_BOTMARG
;
207 p
->psstate
|= PS_MARGINS
;
209 (*p
->footf
)(p
, p
->argf
);
212 p
->psstate
&= ~PS_MARGINS
;
213 assert(0 == p
->psstate
);
215 p
->pscol
= PS_CHAR_LEFT
;
216 p
->psrow
= PS_CHAR_TOP
;
222 term_end(struct termp
*p
)
225 if (TERMTYPE_CHAR
== p
->type
) {
226 (*p
->footf
)(p
, p
->argf
);
230 printf("%s\n", "%%END");
235 endline(struct termp
*p
)
238 if (TERMTYPE_CHAR
== p
->type
) {
243 if (PS_INLINE
& p
->psstate
) {
245 p
->psstate
&= ~PS_INLINE
;
248 if (PS_MARGINS
& p
->psstate
)
251 p
->pscol
= PS_CHAR_LEFT
;
252 if (p
->psrow
>= PS_CHAR_HEIGHT
+ PS_CHAR_BOT
) {
253 p
->psrow
-= PS_CHAR_HEIGHT
;
258 * XXX: can't run pageopen() until we're certain a flushln() has
259 * occured, else the buf will reopen in an awkward state on the
262 printf("showpage\n");
263 p
->psrow
= PS_CHAR_TOP
;
268 * Advance the output engine by a certain amount of whitespace.
271 advance(struct termp
*p
, size_t len
)
275 if (TERMTYPE_CHAR
== p
->type
) {
276 /* Just print whitespace on the terminal. */
277 for (i
= 0; i
< len
; i
++)
282 if (PS_INLINE
& p
->psstate
) {
283 /* Dump out any existing line scope. */
285 p
->psstate
&= ~PS_INLINE
;
288 p
->pscol
+= len
? len
* PS_CHAR_WIDTH
: 0;
292 static struct termp
*
293 alloc(char *outopts
, enum termenc enc
, enum termtype type
)
303 p
= calloc(1, sizeof(struct termp
));
315 while (outopts
&& *outopts
)
316 switch (getsubopt(&outopts
, UNCONST(toks
), &v
)) {
324 /* Enforce some lower boundary. */
327 p
->defrmargin
= width
- 2;
333 * Flush a line of text. A "line" is loosely defined as being something
334 * that should be followed by a newline, regardless of whether it's
335 * broken apart by newlines getting there. A line can also be a
336 * fragment of a columnar list (`Bl -tag' or `Bl -column'), which does
337 * not have a trailing newline.
339 * The following flags may be specified:
341 * - TERMP_NOLPAD: when beginning to write the line, don't left-pad the
342 * offset value. This is useful when doing columnar lists where the
343 * prior column has right-padded.
345 * - TERMP_NOBREAK: this is the most important and is used when making
346 * columns. In short: don't print a newline and instead pad to the
347 * right margin. Used in conjunction with TERMP_NOLPAD.
349 * - TERMP_TWOSPACE: when padding, make sure there are at least two
350 * space characters of padding. Otherwise, rather break the line.
352 * - TERMP_DANGLE: don't newline when TERMP_NOBREAK is specified and
353 * the line is overrun, and don't pad-right if it's underrun.
355 * - TERMP_HANG: like TERMP_DANGLE, but doesn't newline when
356 * overruning, instead save the position and continue at that point
357 * when the next invocation.
359 * In-line line breaking:
361 * If TERMP_NOBREAK is specified and the line overruns the right
362 * margin, it will break and pad-right to the right margin after
363 * writing. If maxrmargin is violated, it will break and continue
364 * writing from the right-margin, which will lead to the above scenario
365 * upon exit. Otherwise, the line will break at the right margin.
368 term_flushln(struct termp
*p
)
370 int i
; /* current input position in p->buf */
371 size_t vis
; /* current visual position on output */
372 size_t vbl
; /* number of blanks to prepend to output */
373 size_t vend
; /* end of word visual position on output */
374 size_t bp
; /* visual right border position */
375 int j
; /* temporary loop index */
376 int jhy
; /* last hyphen before line overflow */
380 * First, establish the maximum columns of "visible" content.
381 * This is usually the difference between the right-margin and
382 * an indentation, but can be, for tagged lists or columns, a
383 * small set of values.
386 assert(p
->offset
< p
->rmargin
);
388 maxvis
= (int)(p
->rmargin
- p
->offset
) - p
->overstep
< 0 ?
390 0 : p
->rmargin
- p
->offset
- p
->overstep
;
391 mmax
= (int)(p
->maxrmargin
- p
->offset
) - p
->overstep
< 0 ?
393 0 : p
->maxrmargin
- p
->offset
- p
->overstep
;
395 bp
= TERMP_NOBREAK
& p
->flags
? mmax
: maxvis
;
398 * Indent the first line of a paragraph.
400 vbl
= p
->flags
& TERMP_NOLPAD
? 0 : p
->offset
;
403 * FIXME: if bp is zero, we still output the first word before
408 while (i
< (int)p
->col
) {
411 * Handle literal tab characters.
413 for (j
= i
; j
< (int)p
->col
; j
++) {
414 if ('\t' != p
->buf
[j
])
416 vend
= (vis
/p
->tabwidth
+1)*p
->tabwidth
;
422 * Count up visible word characters. Control sequences
423 * (starting with the CSI) aren't counted. A space
424 * generates a non-printing word, which is valid (the
425 * space is printed according to regular spacing rules).
429 for (jhy
= 0; j
< (int)p
->col
; j
++) {
430 if ((j
&& ' ' == p
->buf
[j
]) || '\t' == p
->buf
[j
])
432 if (8 != p
->buf
[j
]) {
433 if (vend
> vis
&& vend
< bp
&&
434 ASCII_HYPH
== p
->buf
[j
])
442 * Find out whether we would exceed the right margin.
443 * If so, break to the next line.
445 if (vend
> bp
&& 0 == jhy
&& vis
> 0) {
448 if (TERMP_NOBREAK
& p
->flags
) {
449 p
->viscol
= p
->rmargin
;
450 advance(p
, p
->rmargin
);
451 vend
+= p
->rmargin
- p
->offset
;
457 /* Remove the p->overstep width. */
459 bp
+= (int)/* LINTED */
465 * Skip leading tabs, they were handled above.
467 while (i
< (int)p
->col
&& '\t' == p
->buf
[i
])
470 /* Write out the [remaining] word. */
471 for ( ; i
< (int)p
->col
; i
++) {
472 if (vend
> bp
&& jhy
> 0 && i
> jhy
)
474 if ('\t' == p
->buf
[i
])
476 if (' ' == p
->buf
[i
]) {
477 while (' ' == p
->buf
[i
]) {
483 if (ASCII_NBRSP
== p
->buf
[i
]) {
489 * Now we definitely know there will be
490 * printable characters to output,
491 * so write preceding white space now.
499 if (ASCII_HYPH
== p
->buf
[i
])
502 letter(p
, p
->buf
[i
]);
513 if ( ! (TERMP_NOBREAK
& p
->flags
)) {
519 if (TERMP_HANG
& p
->flags
) {
520 /* We need one blank after the tag. */
521 p
->overstep
= /* LINTED */
525 * Behave exactly the same way as groff:
526 * If we have overstepped the margin, temporarily move
527 * it to the right and flag the rest of the line to be
529 * If we landed right at the margin, be happy.
530 * If we are one step before the margin, temporarily
531 * move it one step LEFT and flag the rest of the line
534 if (p
->overstep
>= -1) {
535 assert((int)maxvis
+ p
->overstep
>= 0);
537 maxvis
+= p
->overstep
;
541 } else if (TERMP_DANGLE
& p
->flags
)
545 if (maxvis
> vis
+ /* LINTED */
546 ((TERMP_TWOSPACE
& p
->flags
) ? 1 : 0)) {
547 p
->viscol
+= maxvis
- vis
;
548 advance(p
, maxvis
- vis
);
549 vis
+= (maxvis
- vis
);
550 } else { /* ...or newline break. */
552 p
->viscol
= p
->rmargin
;
553 advance(p
, p
->rmargin
);
559 * A newline only breaks an existing line; it won't assert vertical
560 * space. All data in the output buffer is flushed prior to the newline
564 term_newln(struct termp
*p
)
567 p
->flags
|= TERMP_NOSPACE
;
568 if (0 == p
->col
&& 0 == p
->viscol
) {
569 p
->flags
&= ~TERMP_NOLPAD
;
573 p
->flags
&= ~TERMP_NOLPAD
;
578 * Asserts a vertical space (a full, empty line-break between lines).
579 * Note that if used twice, this will cause two blank spaces and so on.
580 * All data in the output buffer is flushed prior to the newline
584 term_vspace(struct termp
*p
)
594 spec(struct termp
*p
, const char *word
, size_t len
)
599 rhs
= chars_a2ascii(p
->symtab
, word
, len
, &sz
);
606 res(struct termp
*p
, const char *word
, size_t len
)
611 rhs
= chars_a2res(p
->symtab
, word
, len
, &sz
);
618 term_fontlast(struct termp
*p
)
623 p
->fontl
= p
->fontq
[p
->fonti
];
624 p
->fontq
[p
->fonti
] = f
;
629 term_fontrepl(struct termp
*p
, enum termfont f
)
632 p
->fontl
= p
->fontq
[p
->fonti
];
633 p
->fontq
[p
->fonti
] = f
;
638 term_fontpush(struct termp
*p
, enum termfont f
)
641 assert(p
->fonti
+ 1 < 10);
642 p
->fontl
= p
->fontq
[p
->fonti
];
643 p
->fontq
[++p
->fonti
] = f
;
648 term_fontq(struct termp
*p
)
651 return(&p
->fontq
[p
->fonti
]);
656 term_fonttop(struct termp
*p
)
659 return(p
->fontq
[p
->fonti
]);
664 term_fontpopq(struct termp
*p
, const void *key
)
667 while (p
->fonti
>= 0 && key
!= &p
->fontq
[p
->fonti
])
669 assert(p
->fonti
>= 0);
674 term_fontpop(struct termp
*p
)
683 * Handle pwords, partial words, which may be either a single word or a
684 * phrase that cannot be broken down (such as a literal string). This
685 * handles word styling.
688 term_word(struct termp
*p
, const char *word
)
690 const char *sv
, *seq
;
697 if (word
[0] && '\0' == word
[1])
714 if ( ! (TERMP_IGNDELIM
& p
->flags
))
715 p
->flags
|= TERMP_NOSPACE
;
721 if ( ! (TERMP_NOSPACE
& p
->flags
)) {
723 if (TERMP_SENTENCE
& p
->flags
)
727 if ( ! (p
->flags
& TERMP_NONOSPACE
))
728 p
->flags
&= ~TERMP_NOSPACE
;
730 p
->flags
&= ~TERMP_SENTENCE
;
732 /* FIXME: use strcspn. */
742 sz
= a2roffdeco(&deco
, &seq
, &ssz
);
745 case (DECO_RESERVED
):
752 term_fontrepl(p
, TERMFONT_BOLD
);
755 term_fontrepl(p
, TERMFONT_UNDER
);
758 term_fontrepl(p
, TERMFONT_NONE
);
760 case (DECO_PREVIOUS
):
768 if (DECO_NOSPACE
== deco
&& '\0' == *word
)
769 p
->flags
|= TERMP_NOSPACE
;
773 * Note that we don't process the pipe: the parser sees it as
774 * punctuation, but we don't in terms of typography.
776 if (sv
[0] && 0 == sv
[1])
781 p
->flags
|= TERMP_NOSPACE
;
790 adjbuf(struct termp
*p
, size_t sz
)
795 while (sz
>= p
->maxcols
)
798 p
->buf
= realloc(p
->buf
, p
->maxcols
);
799 if (NULL
== p
->buf
) {
807 buffera(struct termp
*p
, const char *word
, size_t sz
)
810 if (p
->col
+ sz
>= p
->maxcols
)
811 adjbuf(p
, p
->col
+ sz
);
813 memcpy(&p
->buf
[(int)p
->col
], word
, sz
);
819 bufferc(struct termp
*p
, char c
)
822 if (p
->col
+ 1 >= p
->maxcols
)
823 adjbuf(p
, p
->col
+ 1);
825 p
->buf
[(int)p
->col
++] = c
;
830 encode(struct termp
*p
, const char *word
, size_t sz
)
836 * Encode and buffer a string of characters. If the current
837 * font mode is unset, buffer directly, else encode then buffer
838 * character by character.
841 if (TERMTYPE_PS
== p
->type
) {
842 buffera(p
, word
, sz
);
844 } else if (TERMFONT_NONE
== (f
= term_fonttop(p
))) {
845 buffera(p
, word
, sz
);
849 for (i
= 0; i
< (int)sz
; i
++) {
850 if ( ! isgraph((u_char
)word
[i
])) {
855 if (TERMFONT_UNDER
== f
)
867 term_vspan(const struct roffsu
*su
)
885 r
= su
->scale
/ 1000;
897 return(/* LINTED */(size_t)
903 term_hspan(const struct roffsu
*su
)
907 /* XXX: CM, IN, and PT are approximations. */
914 /* XXX: this is an approximation. */
918 r
= (10 * su
->scale
) / 6;
921 r
= (10 * su
->scale
) / 72;
924 r
= su
->scale
/ 1000; /* FIXME: double-check. */
927 r
= su
->scale
* 2 - 1; /* FIXME: double-check. */
936 return((size_t)/* LINTED */