Return to term.c CVS log | Up to [cvsweb.bsd.lv] / mandoc |
version 1.122, 2009/11/05 08:40:16 | version 1.167, 2010/08/20 01:02:07 | ||
---|---|---|---|
|
|
||
/* $Id$ */ | /* $Id$ */ | ||
/* | /* | ||
* Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se> | * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons <kristaps@bsd.lv> | ||
* Copyright (c) 2010 Ingo Schwarze <schwarze@openbsd.org> | |||
* | * | ||
* Permission to use, copy, modify, and distribute this software for any | * Permission to use, copy, modify, and distribute this software for any | ||
* purpose with or without fee is hereby granted, provided that the above | * purpose with or without fee is hereby granted, provided that the above | ||
|
|
||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
*/ | */ | ||
#ifdef HAVE_CONFIG_H | |||
#include "config.h" | |||
#endif | |||
#include <sys/types.h> | |||
#include <assert.h> | #include <assert.h> | ||
#include <ctype.h> | #include <ctype.h> | ||
#include <stdint.h> | |||
#include <stdio.h> | #include <stdio.h> | ||
#include <stdlib.h> | #include <stdlib.h> | ||
#include <string.h> | #include <string.h> | ||
#include <time.h> | |||
#include "mandoc.h" | |||
#include "chars.h" | #include "chars.h" | ||
#include "out.h" | #include "out.h" | ||
#include "term.h" | #include "term.h" | ||
#include "man.h" | |||
#include "mdoc.h" | |||
#include "main.h" | #include "main.h" | ||
/* FIXME: accomodate non-breaking, non-collapsing white-space. */ | static void spec(struct termp *, enum roffdeco, | ||
/* FIXME: accomodate non-breaking, collapsing white-space. */ | |||
static struct termp *term_alloc(enum termenc); | |||
static void term_free(struct termp *); | |||
static void do_escaped(struct termp *, const char **); | |||
static void do_special(struct termp *, | |||
const char *, size_t); | const char *, size_t); | ||
static void do_reserved(struct termp *, | static void res(struct termp *, const char *, size_t); | ||
const char *, size_t); | static void bufferc(struct termp *, char); | ||
static void buffer(struct termp *, char); | static void adjbuf(struct termp *p, size_t); | ||
static void encode(struct termp *, char); | static void encode(struct termp *, const char *, size_t); | ||
void * | void | ||
ascii_alloc(void) | term_free(struct termp *p) | ||
{ | { | ||
return(term_alloc(TERMENC_ASCII)); | if (p->buf) | ||
free(p->buf); | |||
if (p->symtab) | |||
chars_free(p->symtab); | |||
free(p); | |||
} | } | ||
void | void | ||
terminal_free(void *arg) | term_begin(struct termp *p, term_margin head, | ||
term_margin foot, const void *arg) | |||
{ | { | ||
term_free((struct termp *)arg); | p->headf = head; | ||
p->footf = foot; | |||
p->argf = arg; | |||
(*p->begin)(p); | |||
} | } | ||
static void | void | ||
term_free(struct termp *p) | term_end(struct termp *p) | ||
{ | { | ||
if (p->buf) | (*p->end)(p); | ||
free(p->buf); | |||
if (p->symtab) | |||
chars_free(p->symtab); | |||
free(p); | |||
} | } | ||
static struct termp * | struct termp * | ||
term_alloc(enum termenc enc) | term_alloc(enum termenc enc) | ||
{ | { | ||
struct termp *p; | struct termp *p; | ||
p = calloc(1, sizeof(struct termp)); | p = calloc(1, sizeof(struct termp)); | ||
if (NULL == p) { | if (NULL == p) { | ||
perror(NULL); | perror(NULL); | ||
exit(EXIT_FAILURE); | exit(MANDOCLEVEL_SYSERR); | ||
} | } | ||
p->maxrmargin = 78; | |||
p->enc = enc; | p->enc = enc; | ||
return(p); | return(p); | ||
} | } | ||
|
|
||
* Flush a line of text. A "line" is loosely defined as being something | * Flush a line of text. A "line" is loosely defined as being something | ||
* that should be followed by a newline, regardless of whether it's | * that should be followed by a newline, regardless of whether it's | ||
* broken apart by newlines getting there. A line can also be a | * broken apart by newlines getting there. A line can also be a | ||
* fragment of a columnar list. | * fragment of a columnar list (`Bl -tag' or `Bl -column'), which does | ||
* not have a trailing newline. | |||
* | * | ||
* Specifically, a line is whatever's in p->buf of length p->col, which | * The following flags may be specified: | ||
* is zeroed after this function returns. | |||
* | * | ||
* The usage of termp:flags is as follows: | |||
* | |||
* - TERMP_NOLPAD: when beginning to write the line, don't left-pad the | * - TERMP_NOLPAD: when beginning to write the line, don't left-pad the | ||
* offset value. This is useful when doing columnar lists where the | * offset value. This is useful when doing columnar lists where the | ||
* prior column has right-padded. | * prior column has right-padded. | ||
|
|
||
int i; /* current input position in p->buf */ | int i; /* current input position in p->buf */ | ||
size_t vis; /* current visual position on output */ | size_t vis; /* current visual position on output */ | ||
size_t vbl; /* number of blanks to prepend to output */ | size_t vbl; /* number of blanks to prepend to output */ | ||
size_t vsz; /* visual characters to write to output */ | size_t vend; /* end of word visual position on output */ | ||
size_t bp; /* visual right border position */ | size_t bp; /* visual right border position */ | ||
int j; /* temporary loop index */ | int j; /* temporary loop index for p->buf */ | ||
size_t maxvis, mmax; | int jhy; /* last hyph before overflow w/r/t j */ | ||
static int overstep = 0; | size_t maxvis; /* output position of visible boundary */ | ||
size_t mmax; /* used in calculating bp */ | |||
/* | /* | ||
* First, establish the maximum columns of "visible" content. | * First, establish the maximum columns of "visible" content. | ||
|
|
||
assert(p->offset < p->rmargin); | assert(p->offset < p->rmargin); | ||
maxvis = (int)(p->rmargin - p->offset) - overstep < 0 ? | maxvis = (int)(p->rmargin - p->offset) - p->overstep < 0 ? | ||
/* LINTED */ | /* LINTED */ | ||
0 : p->rmargin - p->offset - overstep; | 0 : p->rmargin - p->offset - p->overstep; | ||
mmax = (int)(p->maxrmargin - p->offset) - overstep < 0 ? | mmax = (int)(p->maxrmargin - p->offset) - p->overstep < 0 ? | ||
/* LINTED */ | /* LINTED */ | ||
0 : p->maxrmargin - p->offset - overstep; | 0 : p->maxrmargin - p->offset - p->overstep; | ||
bp = TERMP_NOBREAK & p->flags ? mmax : maxvis; | bp = TERMP_NOBREAK & p->flags ? mmax : maxvis; | ||
/* | |||
* FIXME: if bp is zero, we still output the first word before | |||
* breaking the line. | |||
*/ | |||
vis = 0; | |||
/* | /* | ||
* If in the standard case (left-justified), then begin with our | * Indent the first line of a paragraph. | ||
* indentation, otherwise (columns, etc.) just start spitting | |||
* out text. | |||
*/ | */ | ||
vbl = p->flags & TERMP_NOLPAD ? 0 : p->offset; | |||
if ( ! (p->flags & TERMP_NOLPAD)) | vis = vend = i = 0; | ||
/* LINTED */ | |||
for (j = 0; j < (int)p->offset; j++) | |||
putchar(' '); | |||
for (i = 0; i < (int)p->col; i++) { | while (i < (int)p->col) { | ||
/* | /* | ||
* Handle literal tab characters: collapse all | |||
* subsequent tabs into a single huge set of spaces. | |||
*/ | |||
for (j = i; j < (int)p->col; j++) { | |||
if ('\t' != p->buf[j]) | |||
break; | |||
vend = (vis / p->tabwidth + 1) * p->tabwidth; | |||
vbl += vend - vis; | |||
vis = vend; | |||
} | |||
/* | |||
* Count up visible word characters. Control sequences | * Count up visible word characters. Control sequences | ||
* (starting with the CSI) aren't counted. A space | * (starting with the CSI) aren't counted. A space | ||
* generates a non-printing word, which is valid (the | * generates a non-printing word, which is valid (the | ||
|
|
||
*/ | */ | ||
/* LINTED */ | /* LINTED */ | ||
for (j = i, vsz = 0; j < (int)p->col; j++) { | for (jhy = 0; j < (int)p->col; j++) { | ||
if (j && ' ' == p->buf[j]) | if ((j && ' ' == p->buf[j]) || '\t' == p->buf[j]) | ||
break; | break; | ||
else if (8 == p->buf[j]) | |||
vsz--; | /* Back over the the last printed character. */ | ||
else | if (8 == p->buf[j]) { | ||
vsz++; | assert(j); | ||
vend -= (*p->width)(p, p->buf[j - 1]); | |||
continue; | |||
} | |||
/* Regular word. */ | |||
/* Break at the hyphen point if we overrun. */ | |||
if (vend > vis && vend < bp && | |||
ASCII_HYPH == p->buf[j]) | |||
jhy = j; | |||
vend += (*p->width)(p, p->buf[j]); | |||
} | } | ||
/* | /* | ||
* Choose the number of blanks to prepend: no blank at the | |||
* beginning of a line, one between words -- but do not | |||
* actually write them yet. | |||
*/ | |||
vbl = (size_t)(0 == vis ? 0 : 1); | |||
/* | |||
* Find out whether we would exceed the right margin. | * Find out whether we would exceed the right margin. | ||
* If so, break to the next line. (TODO: hyphenate) | * If so, break to the next line. | ||
* Otherwise, write the chosen number of blanks now. | |||
*/ | */ | ||
if (vis && vis + vbl + vsz > bp) { | if (vend > bp && 0 == jhy && vis > 0) { | ||
putchar('\n'); | vend -= vis; | ||
(*p->endline)(p); | |||
if (TERMP_NOBREAK & p->flags) { | if (TERMP_NOBREAK & p->flags) { | ||
for (j = 0; j < (int)p->rmargin; j++) | p->viscol = p->rmargin; | ||
putchar(' '); | (*p->advance)(p, p->rmargin); | ||
vis = p->rmargin - p->offset; | vend += p->rmargin - p->offset; | ||
} else { | } else { | ||
for (j = 0; j < (int)p->offset; j++) | p->viscol = 0; | ||
putchar(' '); | vbl = p->offset; | ||
vis = 0; | |||
} | } | ||
/* Remove the overstep width. */ | |||
/* Remove the p->overstep width. */ | |||
bp += (int)/* LINTED */ | bp += (int)/* LINTED */ | ||
overstep; | p->overstep; | ||
overstep = 0; | p->overstep = 0; | ||
} else { | |||
for (j = 0; j < (int)vbl; j++) | |||
putchar(' '); | |||
vis += vbl; | |||
} | } | ||
/* | /* | ||
* Finally, write out the word. | * Skip leading tabs, they were handled above. | ||
*/ | */ | ||
while (i < (int)p->col && '\t' == p->buf[i]) | |||
i++; | |||
/* Write out the [remaining] word. */ | |||
for ( ; i < (int)p->col; i++) { | for ( ; i < (int)p->col; i++) { | ||
if (' ' == p->buf[i]) | if (vend > bp && jhy > 0 && i > jhy) | ||
break; | break; | ||
if ('\t' == p->buf[i]) | |||
break; | |||
if (' ' == p->buf[i]) { | |||
j = i; | |||
while (' ' == p->buf[i]) | |||
i++; | |||
vbl += (i - j) * (*p->width)(p, ' '); | |||
break; | |||
} | |||
if (ASCII_NBRSP == p->buf[i]) { | |||
vbl += (*p->width)(p, ' '); | |||
continue; | |||
} | |||
/* The unit sep. is a non-breaking space. */ | /* | ||
if (31 == p->buf[i]) | * Now we definitely know there will be | ||
putchar(' '); | * printable characters to output, | ||
else | * so write preceding white space now. | ||
putchar(p->buf[i]); | */ | ||
if (vbl) { | |||
(*p->advance)(p, vbl); | |||
p->viscol += vbl; | |||
vbl = 0; | |||
} | |||
if (ASCII_HYPH == p->buf[i]) { | |||
(*p->letter)(p, '-'); | |||
p->viscol += (*p->width)(p, '-'); | |||
} else { | |||
(*p->letter)(p, p->buf[i]); | |||
p->viscol += (*p->width)(p, p->buf[i]); | |||
} | |||
} | } | ||
vis += vsz; | vend += vbl; | ||
vis = vend; | |||
} | } | ||
p->col = 0; | p->col = 0; | ||
overstep = 0; | p->overstep = 0; | ||
if ( ! (TERMP_NOBREAK & p->flags)) { | if ( ! (TERMP_NOBREAK & p->flags)) { | ||
putchar('\n'); | p->viscol = 0; | ||
(*p->endline)(p); | |||
return; | return; | ||
} | } | ||
if (TERMP_HANG & p->flags) { | if (TERMP_HANG & p->flags) { | ||
/* We need one blank after the tag. */ | /* We need one blank after the tag. */ | ||
overstep = /* LINTED */ | p->overstep = /* LINTED */ | ||
vis - maxvis + 1; | vis - maxvis + (*p->width)(p, ' '); | ||
/* | /* | ||
* Behave exactly the same way as groff: | * Behave exactly the same way as groff: | ||
|
|
||
* move it one step LEFT and flag the rest of the line | * move it one step LEFT and flag the rest of the line | ||
* to be longer. | * to be longer. | ||
*/ | */ | ||
if (overstep >= -1) { | if (p->overstep >= -1) { | ||
assert((int)maxvis + overstep >= 0); | assert((int)maxvis + p->overstep >= 0); | ||
/* LINTED */ | /* LINTED */ | ||
maxvis += overstep; | maxvis += p->overstep; | ||
} else | } else | ||
overstep = 0; | p->overstep = 0; | ||
} else if (TERMP_DANGLE & p->flags) | } else if (TERMP_DANGLE & p->flags) | ||
return; | return; | ||
/* Right-pad. */ | /* Right-pad. */ | ||
if (maxvis > vis + /* LINTED */ | if (maxvis > vis + /* LINTED */ | ||
((TERMP_TWOSPACE & p->flags) ? 1 : 0)) | ((TERMP_TWOSPACE & p->flags) ? | ||
for ( ; vis < maxvis; vis++) | (*p->width)(p, ' ') : 0)) { | ||
putchar(' '); | p->viscol += maxvis - vis; | ||
else { /* ...or newline break. */ | (*p->advance)(p, maxvis - vis); | ||
putchar('\n'); | vis += (maxvis - vis); | ||
for (i = 0; i < (int)p->rmargin; i++) | } else { /* ...or newline break. */ | ||
putchar(' '); | (*p->endline)(p); | ||
p->viscol = p->rmargin; | |||
(*p->advance)(p, p->rmargin); | |||
} | } | ||
} | } | ||
|
|
||
{ | { | ||
p->flags |= TERMP_NOSPACE; | p->flags |= TERMP_NOSPACE; | ||
if (0 == p->col) { | if (0 == p->col && 0 == p->viscol) { | ||
p->flags &= ~TERMP_NOLPAD; | p->flags &= ~TERMP_NOLPAD; | ||
return; | return; | ||
} | } | ||
|
|
||
{ | { | ||
term_newln(p); | term_newln(p); | ||
putchar('\n'); | p->viscol = 0; | ||
(*p->endline)(p); | |||
} | } | ||
static void | static void | ||
do_special(struct termp *p, const char *word, size_t len) | spec(struct termp *p, enum roffdeco d, const char *word, size_t len) | ||
{ | { | ||
const char *rhs; | const char *rhs; | ||
size_t sz; | size_t sz; | ||
int i; | |||
rhs = chars_a2ascii(p->symtab, word, len, &sz); | rhs = chars_spec2str(p->symtab, word, len, &sz); | ||
if (rhs) | |||
if (NULL == rhs) { | encode(p, rhs, sz); | ||
#if 0 | else if (DECO_SSPECIAL == d) | ||
fputs("Unknown special character: ", stderr); | encode(p, word, len); | ||
for (i = 0; i < (int)len; i++) | |||
fputc(word[i], stderr); | |||
fputc('\n', stderr); | |||
#endif | |||
return; | |||
} | |||
for (i = 0; i < (int)sz; i++) | |||
encode(p, rhs[i]); | |||
} | } | ||
static void | static void | ||
do_reserved(struct termp *p, const char *word, size_t len) | res(struct termp *p, const char *word, size_t len) | ||
{ | { | ||
const char *rhs; | const char *rhs; | ||
size_t sz; | size_t sz; | ||
int i; | |||
rhs = chars_a2res(p->symtab, word, len, &sz); | rhs = chars_res2str(p->symtab, word, len, &sz); | ||
if (rhs) | |||
encode(p, rhs, sz); | |||
} | |||
if (NULL == rhs) { | |||
#if 0 | void | ||
fputs("Unknown reserved word: ", stderr); | term_fontlast(struct termp *p) | ||
for (i = 0; i < (int)len; i++) | { | ||
fputc(word[i], stderr); | enum termfont f; | ||
fputc('\n', stderr); | |||
#endif | f = p->fontl; | ||
return; | p->fontl = p->fontq[p->fonti]; | ||
} | p->fontq[p->fonti] = f; | ||
for (i = 0; i < (int)sz; i++) | |||
encode(p, rhs[i]); | |||
} | } | ||
/* | void | ||
* Handle an escape sequence: determine its length and pass it to the | term_fontrepl(struct termp *p, enum termfont f) | ||
* escape-symbol look table. Note that we assume mdoc(3) has validated | |||
* the escape sequence (we assert upon badly-formed escape sequences). | |||
*/ | |||
static void | |||
do_escaped(struct termp *p, const char **word) | |||
{ | { | ||
int j, type, sv; | |||
const char *wp; | |||
wp = *word; | p->fontl = p->fontq[p->fonti]; | ||
type = 1; | p->fontq[p->fonti] = f; | ||
} | |||
if (0 == *(++wp)) { | |||
*word = wp; | |||
return; | |||
} | |||
if ('(' == *wp) { | void | ||
wp++; | term_fontpush(struct termp *p, enum termfont f) | ||
if (0 == *wp || 0 == *(wp + 1)) { | { | ||
*word = 0 == *wp ? wp : wp + 1; | |||
return; | |||
} | |||
do_special(p, wp, 2); | assert(p->fonti + 1 < 10); | ||
*word = ++wp; | p->fontl = p->fontq[p->fonti]; | ||
return; | p->fontq[++p->fonti] = f; | ||
} | |||
} else if ('*' == *wp) { | |||
if (0 == *(++wp)) { | |||
*word = wp; | |||
return; | |||
} | |||
switch (*wp) { | const void * | ||
case ('('): | term_fontq(struct termp *p) | ||
wp++; | { | ||
if (0 == *wp || 0 == *(wp + 1)) { | |||
*word = 0 == *wp ? wp : wp + 1; | |||
return; | |||
} | |||
do_reserved(p, wp, 2); | return(&p->fontq[p->fonti]); | ||
*word = ++wp; | } | ||
return; | |||
case ('['): | |||
type = 0; | |||
break; | |||
default: | |||
do_reserved(p, wp, 1); | |||
*word = wp; | |||
return; | |||
} | |||
} else if ('f' == *wp) { | |||
if (0 == *(++wp)) { | |||
*word = wp; | |||
return; | |||
} | |||
switch (*wp) { | |||
case ('3'): | |||
/* FALLTHROUGH */ | |||
case ('B'): | |||
p->metamask = p->metafont; | |||
p->metafont |= METAF_BOLD; | |||
break; | |||
case ('2'): | |||
/* FALLTHROUGH */ | |||
case ('I'): | |||
p->metamask = p->metafont; | |||
p->metafont |= METAF_UNDER; | |||
break; | |||
case ('P'): | |||
sv = p->metamask; | |||
p->metamask = p->metafont; | |||
p->metafont = sv; | |||
break; | |||
case ('1'): | |||
/* FALLTHROUGH */ | |||
case ('R'): | |||
p->metamask = p->metafont; | |||
p->metafont &= ~METAF_UNDER; | |||
p->metafont &= ~METAF_BOLD; | |||
break; | |||
default: | |||
break; | |||
} | |||
*word = wp; | enum termfont | ||
return; | term_fonttop(struct termp *p) | ||
{ | |||
} else if ('[' != *wp) { | return(p->fontq[p->fonti]); | ||
do_special(p, wp, 1); | } | ||
*word = wp; | |||
return; | |||
} | |||
wp++; | |||
for (j = 0; *wp && ']' != *wp; wp++, j++) | |||
/* Loop... */ ; | |||
if (0 == *wp) { | void | ||
*word = wp; | term_fontpopq(struct termp *p, const void *key) | ||
return; | { | ||
} | |||
if (type) | while (p->fonti >= 0 && key != &p->fontq[p->fonti]) | ||
do_special(p, wp - j, (size_t)j); | p->fonti--; | ||
else | assert(p->fonti >= 0); | ||
do_reserved(p, wp - j, (size_t)j); | |||
*word = wp; | |||
} | } | ||
void | |||
term_fontpop(struct termp *p) | |||
{ | |||
assert(p->fonti); | |||
p->fonti--; | |||
} | |||
/* | /* | ||
* Handle pwords, partial words, which may be either a single word or a | * Handle pwords, partial words, which may be either a single word or a | ||
* phrase that cannot be broken down (such as a literal string). This | * phrase that cannot be broken down (such as a literal string). This | ||
|
|
||
void | void | ||
term_word(struct termp *p, const char *word) | term_word(struct termp *p, const char *word) | ||
{ | { | ||
const char *sv; | const char *sv, *seq; | ||
int sz; | |||
size_t ssz; | |||
enum roffdeco deco; | |||
sv = word; | sv = word; | ||
if (word[0] && 0 == word[1]) | if (word[0] && '\0' == word[1]) | ||
switch (word[0]) { | switch (word[0]) { | ||
case('.'): | case('.'): | ||
/* FALLTHROUGH */ | /* FALLTHROUGH */ | ||
|
|
||
case(')'): | case(')'): | ||
/* FALLTHROUGH */ | /* FALLTHROUGH */ | ||
case(']'): | case(']'): | ||
/* FALLTHROUGH */ | |||
case('}'): | |||
if ( ! (TERMP_IGNDELIM & p->flags)) | if ( ! (TERMP_IGNDELIM & p->flags)) | ||
p->flags |= TERMP_NOSPACE; | p->flags |= TERMP_NOSPACE; | ||
break; | break; | ||
|
|
||
break; | break; | ||
} | } | ||
if ( ! (TERMP_NOSPACE & p->flags)) | if ( ! (TERMP_NOSPACE & p->flags)) { | ||
buffer(p, ' '); | if ( ! (TERMP_KEEP & p->flags)) { | ||
if (TERMP_PREKEEP & p->flags) | |||
p->flags |= TERMP_KEEP; | |||
bufferc(p, ' '); | |||
if (TERMP_SENTENCE & p->flags) | |||
bufferc(p, ' '); | |||
} else | |||
bufferc(p, ASCII_NBRSP); | |||
} | |||
if ( ! (p->flags & TERMP_NONOSPACE)) | if ( ! (p->flags & TERMP_NONOSPACE)) | ||
p->flags &= ~TERMP_NOSPACE; | p->flags &= ~TERMP_NOSPACE; | ||
else | |||
p->flags |= TERMP_NOSPACE; | |||
for ( ; *word; word++) | p->flags &= ~TERMP_SENTENCE; | ||
while (*word) { | |||
if ((ssz = strcspn(word, "\\")) > 0) | |||
encode(p, word, ssz); | |||
word += ssz; | |||
if ('\\' != *word) | if ('\\' != *word) | ||
encode(p, *word); | continue; | ||
else | |||
do_escaped(p, &word); | |||
if (sv[0] && 0 == sv[1]) | seq = ++word; | ||
sz = a2roffdeco(&deco, &seq, &ssz); | |||
switch (deco) { | |||
case (DECO_RESERVED): | |||
res(p, seq, ssz); | |||
break; | |||
case (DECO_SPECIAL): | |||
/* FALLTHROUGH */ | |||
case (DECO_SSPECIAL): | |||
spec(p, deco, seq, ssz); | |||
break; | |||
case (DECO_BOLD): | |||
term_fontrepl(p, TERMFONT_BOLD); | |||
break; | |||
case (DECO_ITALIC): | |||
term_fontrepl(p, TERMFONT_UNDER); | |||
break; | |||
case (DECO_ROMAN): | |||
term_fontrepl(p, TERMFONT_NONE); | |||
break; | |||
case (DECO_PREVIOUS): | |||
term_fontlast(p); | |||
break; | |||
default: | |||
break; | |||
} | |||
word += sz; | |||
if (DECO_NOSPACE == deco && '\0' == *word) | |||
p->flags |= TERMP_NOSPACE; | |||
} | |||
/* | |||
* Note that we don't process the pipe: the parser sees it as | |||
* punctuation, but we don't in terms of typography. | |||
*/ | |||
if (sv[0] && '\0' == sv[1]) | |||
switch (sv[0]) { | switch (sv[0]) { | ||
case('('): | case('('): | ||
/* FALLTHROUGH */ | /* FALLTHROUGH */ | ||
case('['): | case('['): | ||
/* FALLTHROUGH */ | |||
case('{'): | |||
p->flags |= TERMP_NOSPACE; | p->flags |= TERMP_NOSPACE; | ||
break; | break; | ||
default: | default: | ||
|
|
||
} | } | ||
/* | |||
* Insert a single character into the line-buffer. If the buffer's | |||
* space is exceeded, then allocate more space by doubling the buffer | |||
* size. | |||
*/ | |||
static void | static void | ||
buffer(struct termp *p, char c) | adjbuf(struct termp *p, size_t sz) | ||
{ | { | ||
size_t s; | |||
if (p->col + 1 >= p->maxcols) { | if (0 == p->maxcols) | ||
if (0 == p->maxcols) | p->maxcols = 1024; | ||
p->maxcols = 256; | while (sz >= p->maxcols) | ||
s = p->maxcols * 2; | p->maxcols <<= 2; | ||
p->buf = realloc(p->buf, s); | |||
if (NULL == p->buf) { | p->buf = realloc(p->buf, p->maxcols); | ||
perror(NULL); | if (NULL == p->buf) { | ||
exit(EXIT_FAILURE); | perror(NULL); | ||
} | exit(MANDOCLEVEL_SYSERR); | ||
p->maxcols = s; | |||
} | } | ||
p->buf[(int)(p->col)++] = c; | |||
} | } | ||
static void | static void | ||
encode(struct termp *p, char c) | bufferc(struct termp *p, char c) | ||
{ | { | ||
if (isgraph((u_char)c)) { | if (p->col + 1 >= p->maxcols) | ||
if (p->under || METAF_UNDER & p->metafont) { | adjbuf(p, p->col + 1); | ||
buffer(p, '_'); | |||
buffer(p, 8); | p->buf[(int)p->col++] = c; | ||
} | |||
static void | |||
encode(struct termp *p, const char *word, size_t sz) | |||
{ | |||
enum termfont f; | |||
int i; | |||
/* | |||
* Encode and buffer a string of characters. If the current | |||
* font mode is unset, buffer directly, else encode then buffer | |||
* character by character. | |||
*/ | |||
if (TERMFONT_NONE == (f = term_fonttop(p))) { | |||
if (p->col + sz >= p->maxcols) | |||
adjbuf(p, p->col + sz); | |||
memcpy(&p->buf[(int)p->col], word, sz); | |||
p->col += sz; | |||
return; | |||
} | |||
/* Pre-buffer, assuming worst-case. */ | |||
if (p->col + 1 + (sz * 3) >= p->maxcols) | |||
adjbuf(p, p->col + 1 + (sz * 3)); | |||
for (i = 0; i < (int)sz; i++) { | |||
if ( ! isgraph((u_char)word[i])) { | |||
p->buf[(int)p->col++] = word[i]; | |||
continue; | |||
} | } | ||
if (p->bold || METAF_BOLD & p->metafont) { | |||
buffer(p, c); | if (TERMFONT_UNDER == f) | ||
buffer(p, 8); | p->buf[(int)p->col++] = '_'; | ||
} | else | ||
p->buf[(int)p->col++] = word[i]; | |||
p->buf[(int)p->col++] = 8; | |||
p->buf[(int)p->col++] = word[i]; | |||
} | } | ||
buffer(p, c); | |||
} | } | ||
size_t | size_t | ||
term_vspan(const struct roffsu *su) | term_len(const struct termp *p, size_t sz) | ||
{ | { | ||
return((*p->width)(p, ' ') * sz); | |||
} | |||
size_t | |||
term_strlen(const struct termp *p, const char *cp) | |||
{ | |||
size_t sz; | |||
for (sz = 0; *cp; cp++) | |||
sz += (*p->width)(p, *cp); | |||
return(sz); | |||
} | |||
/* ARGSUSED */ | |||
size_t | |||
term_vspan(const struct termp *p, const struct roffsu *su) | |||
{ | |||
double r; | double r; | ||
switch (su->unit) { | switch (su->unit) { | ||
|
|
||
size_t | size_t | ||
term_hspan(const struct roffsu *su) | term_hspan(const struct termp *p, const struct roffsu *su) | ||
{ | { | ||
double r; | double v; | ||
/* XXX: CM, IN, and PT are approximations. */ | v = ((*p->hspan)(p, su)); | ||
if (v < 0.0) | |||
switch (su->unit) { | v = 0.0; | ||
case (SCALE_CM): | return((size_t) /* LINTED */ | ||
r = 4 * su->scale; | v); | ||
break; | |||
case (SCALE_IN): | |||
/* XXX: this is an approximation. */ | |||
r = 10 * su->scale; | |||
break; | |||
case (SCALE_PC): | |||
r = (10 * su->scale) / 6; | |||
break; | |||
case (SCALE_PT): | |||
r = (10 * su->scale) / 72; | |||
break; | |||
case (SCALE_MM): | |||
r = su->scale / 1000; /* FIXME: double-check. */ | |||
break; | |||
case (SCALE_VS): | |||
r = su->scale * 2 - 1; /* FIXME: double-check. */ | |||
break; | |||
default: | |||
r = su->scale; | |||
break; | |||
} | |||
if (r < 0.0) | |||
r = 0.0; | |||
return((size_t)/* LINTED */ | |||
r); | |||
} | } | ||