Return to term.c CVS log | Up to [cvsweb.bsd.lv] / mandoc |
version 1.138, 2010/05/24 21:34:16 | version 1.203, 2012/05/31 22:29:13 | ||
---|---|---|---|
|
|
||
/* $Id$ */ | /* $Id$ */ | ||
/* | /* | ||
* Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se> | * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> | ||
* Copyright (c) 2010, 2011, 2012 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 | ||
|
|
||
#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 "mandoc.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" | ||
static struct termp *term_alloc(enum termenc, size_t); | static size_t cond_width(const struct termp *, int, int *); | ||
static void term_free(struct termp *); | static void adjbuf(struct termp *p, int); | ||
static void spec(struct termp *, const char *, size_t); | static void bufferc(struct termp *, char); | ||
static void res(struct termp *, const char *, size_t); | static void encode(struct termp *, const char *, size_t); | ||
static void buffera(struct termp *, const char *, size_t); | static void encode1(struct termp *, int); | ||
static void bufferc(struct termp *, char); | |||
static void adjbuf(struct termp *p, size_t); | |||
static void encode(struct termp *, const char *, size_t); | |||
void * | |||
ascii_alloc(size_t width) | |||
{ | |||
return(term_alloc(TERMENC_ASCII, width)); | |||
} | |||
void | void | ||
terminal_free(void *arg) | |||
{ | |||
term_free((struct termp *)arg); | |||
} | |||
static void | |||
term_free(struct termp *p) | term_free(struct termp *p) | ||
{ | { | ||
if (p->buf) | if (p->buf) | ||
free(p->buf); | free(p->buf); | ||
if (p->symtab) | if (p->symtab) | ||
chars_free(p->symtab); | mchars_free(p->symtab); | ||
free(p); | free(p); | ||
} | } | ||
static struct termp * | void | ||
term_alloc(enum termenc enc, size_t width) | term_begin(struct termp *p, term_margin head, | ||
term_margin foot, const void *arg) | |||
{ | { | ||
struct termp *p; | |||
p = calloc(1, sizeof(struct termp)); | p->headf = head; | ||
if (NULL == p) { | p->footf = foot; | ||
perror(NULL); | p->argf = arg; | ||
exit(EXIT_FAILURE); | (*p->begin)(p); | ||
} | |||
p->tabwidth = 5; | |||
p->enc = enc; | |||
/* Enforce some lower boundary. */ | |||
if (width < 60) | |||
width = 60; | |||
p->defrmargin = width - 2; | |||
return(p); | |||
} | } | ||
void | |||
term_end(struct termp *p) | |||
{ | |||
(*p->end)(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 | ||
|
|
||
* | * | ||
* The following flags may be specified: | * The following flags may be specified: | ||
* | * | ||
* - TERMP_NOLPAD: when beginning to write the line, don't left-pad the | |||
* offset value. This is useful when doing columnar lists where the | |||
* prior column has right-padded. | |||
* | |||
* - TERMP_NOBREAK: this is the most important and is used when making | * - TERMP_NOBREAK: this is the most important and is used when making | ||
* columns. In short: don't print a newline and instead pad to the | * columns. In short: don't print a newline and instead expect the | ||
* right margin. Used in conjunction with TERMP_NOLPAD. | * next call to do the padding up to the start of the next column. | ||
* | * | ||
* - TERMP_TWOSPACE: when padding, make sure there are at least two | * - TERMP_TWOSPACE: make sure there is room for at least two space | ||
* space characters of padding. Otherwise, rather break the line. | * characters of padding. Otherwise, rather break the line. | ||
* | * | ||
* - TERMP_DANGLE: don't newline when TERMP_NOBREAK is specified and | * - TERMP_DANGLE: don't newline when TERMP_NOBREAK is specified and | ||
* the line is overrun, and don't pad-right if it's underrun. | * the line is overrun, and don't pad-right if it's underrun. | ||
* | * | ||
* - TERMP_HANG: like TERMP_DANGLE, but doesn't newline when | * - TERMP_HANG: like TERMP_DANGLE, but doesn't newline when | ||
* overruning, instead save the position and continue at that point | * overrunning, instead save the position and continue at that point | ||
* when the next invocation. | * when the next invocation. | ||
* | * | ||
* In-line line breaking: | * In-line line breaking: | ||
|
|
||
size_t vbl; /* number of blanks to prepend to output */ | size_t vbl; /* number of blanks to prepend to output */ | ||
size_t vend; /* end of word visual position on 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 */ | size_t dv; /* temporary for visual pos calculations */ | ||
size_t maxvis, mmax; | int j; /* temporary loop index for p->buf */ | ||
int jhy; /* last hyph before overflow w/r/t j */ | |||
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. | ||
|
|
||
* an indentation, but can be, for tagged lists or columns, a | * an indentation, but can be, for tagged lists or columns, a | ||
* small set of values. | * small set of values. | ||
*/ | */ | ||
assert (p->rmargin >= p->offset); | |||
dv = p->rmargin - p->offset; | |||
maxvis = (int)dv > p->overstep ? dv - (size_t)p->overstep : 0; | |||
dv = p->maxrmargin - p->offset; | |||
mmax = (int)dv > p->overstep ? dv - (size_t)p->overstep : 0; | |||
assert(p->offset < p->rmargin); | |||
maxvis = (int)(p->rmargin - p->offset) - p->overstep < 0 ? | |||
/* LINTED */ | |||
0 : p->rmargin - p->offset - p->overstep; | |||
mmax = (int)(p->maxrmargin - p->offset) - p->overstep < 0 ? | |||
/* LINTED */ | |||
0 : p->maxrmargin - p->offset - p->overstep; | |||
bp = TERMP_NOBREAK & p->flags ? mmax : maxvis; | bp = TERMP_NOBREAK & p->flags ? mmax : maxvis; | ||
/* | /* | ||
* Indent the first line of a paragraph. | * Calculate the required amount of padding. | ||
*/ | */ | ||
vbl = p->flags & TERMP_NOLPAD ? 0 : p->offset; | vbl = p->offset + p->overstep > p->viscol ? | ||
p->offset + p->overstep - p->viscol : 0; | |||
/* | vis = vend = 0; | ||
* FIXME: if bp is zero, we still output the first word before | i = 0; | ||
* breaking the line. | |||
*/ | |||
vis = vend = i = 0; | while (i < p->col) { | ||
while (i < (int)p->col) { | |||
/* | /* | ||
* Handle literal tab characters. | * Handle literal tab characters: collapse all | ||
* subsequent tabs into a single huge set of spaces. | |||
*/ | */ | ||
for (j = i; j < (int)p->col; j++) { | while (i < p->col && '\t' == p->buf[i]) { | ||
if ('\t' != p->buf[j]) | vend = (vis / p->tabwidth + 1) * p->tabwidth; | ||
break; | |||
vend = (vis/p->tabwidth+1)*p->tabwidth; | |||
vbl += vend - vis; | vbl += vend - vis; | ||
vis = vend; | vis = vend; | ||
i++; | |||
} | } | ||
/* | /* | ||
|
|
||
* space is printed according to regular spacing rules). | * space is printed according to regular spacing rules). | ||
*/ | */ | ||
/* LINTED */ | for (j = i, jhy = 0; j < p->col; j++) { | ||
for ( ; j < (int)p->col; j++) { | |||
if ((j && ' ' == p->buf[j]) || '\t' == p->buf[j]) | if ((j && ' ' == p->buf[j]) || '\t' == p->buf[j]) | ||
break; | break; | ||
if (8 == p->buf[j]) | |||
vend--; | /* Back over the the last printed character. */ | ||
else | if (8 == p->buf[j]) { | ||
vend++; | 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]); | |||
} | } | ||
/* | /* | ||
* 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. | * If so, break to the next line. | ||
*/ | */ | ||
if (vend > bp && vis > 0) { | if (vend > bp && 0 == jhy && vis > 0) { | ||
vend -= vis; | vend -= vis; | ||
putchar('\n'); | (*p->endline)(p); | ||
p->viscol = 0; | |||
if (TERMP_NOBREAK & p->flags) { | if (TERMP_NOBREAK & p->flags) { | ||
for (j = 0; j < (int)p->rmargin; j++) | vbl = p->rmargin; | ||
putchar(' '); | |||
vend += p->rmargin - p->offset; | vend += p->rmargin - p->offset; | ||
} else { | } else | ||
vbl = p->offset; | vbl = p->offset; | ||
} | |||
/* Remove the p->overstep width. */ | /* Remove the p->overstep width. */ | ||
bp += (int)/* LINTED */ | bp += (size_t)p->overstep; | ||
p->overstep; | |||
p->overstep = 0; | p->overstep = 0; | ||
} | } | ||
/* | |||
* Skip leading tabs, they were handled above. | |||
*/ | |||
while (i < (int)p->col && '\t' == p->buf[i]) | |||
i++; | |||
/* Write out the [remaining] word. */ | /* Write out the [remaining] word. */ | ||
for ( ; i < (int)p->col; i++) { | for ( ; i < p->col; i++) { | ||
if (vend > bp && jhy > 0 && i > jhy) | |||
break; | |||
if ('\t' == p->buf[i]) | if ('\t' == p->buf[i]) | ||
break; | break; | ||
if (' ' == p->buf[i]) { | if (' ' == p->buf[i]) { | ||
while (' ' == p->buf[i]) { | j = i; | ||
vbl++; | while (' ' == p->buf[i]) | ||
i++; | i++; | ||
} | dv = (size_t)(i - j) * (*p->width)(p, ' '); | ||
vbl += dv; | |||
vend += dv; | |||
break; | break; | ||
} | } | ||
if (ASCII_NBRSP == p->buf[i]) { | if (ASCII_NBRSP == p->buf[i]) { | ||
vbl++; | vbl += (*p->width)(p, ' '); | ||
continue; | continue; | ||
} | } | ||
|
|
||
* so write preceding white space now. | * so write preceding white space now. | ||
*/ | */ | ||
if (vbl) { | if (vbl) { | ||
for (j = 0; j < (int)vbl; j++) | (*p->advance)(p, vbl); | ||
putchar(' '); | p->viscol += vbl; | ||
vbl = 0; | vbl = 0; | ||
} | } | ||
putchar(p->buf[i]); | |||
if (ASCII_HYPH == p->buf[i]) { | |||
(*p->letter)(p, '-'); | |||
p->viscol += (*p->width)(p, '-'); | |||
continue; | |||
} | |||
(*p->letter)(p, p->buf[i]); | |||
if (8 == p->buf[i]) | |||
p->viscol -= (*p->width)(p, p->buf[i-1]); | |||
else | |||
p->viscol += (*p->width)(p, p->buf[i]); | |||
} | } | ||
vend += vbl; | |||
vis = vend; | vis = vend; | ||
} | } | ||
/* | |||
* If there was trailing white space, it was not printed; | |||
* so reset the cursor position accordingly. | |||
*/ | |||
if (vis) | |||
vis -= vbl; | |||
p->col = 0; | p->col = 0; | ||
p->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. */ | ||
p->overstep = /* LINTED */ | p->overstep = (int)(vis - maxvis + (*p->width)(p, ' ')); | ||
vis - maxvis + 1; | |||
/* | /* | ||
* 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 (p->overstep >= -1) { | if (p->overstep < -1) | ||
assert((int)maxvis + p->overstep >= 0); | |||
/* LINTED */ | |||
maxvis += p->overstep; | |||
} else | |||
p->overstep = 0; | p->overstep = 0; | ||
return; | |||
} else if (TERMP_DANGLE & p->flags) | } else if (TERMP_DANGLE & p->flags) | ||
return; | return; | ||
/* Right-pad. */ | /* If the column was overrun, break the line. */ | ||
if (maxvis > vis + /* LINTED */ | if (maxvis <= vis + | ||
((TERMP_TWOSPACE & p->flags) ? 1 : 0)) | ((TERMP_TWOSPACE & p->flags) ? (*p->width)(p, ' ') : 0)) { | ||
for ( ; vis < maxvis; vis++) | (*p->endline)(p); | ||
putchar(' '); | p->viscol = 0; | ||
else { /* ...or newline break. */ | |||
putchar('\n'); | |||
for (i = 0; i < (int)p->rmargin; i++) | |||
putchar(' '); | |||
} | } | ||
} | } | ||
|
|
||
{ | { | ||
p->flags |= TERMP_NOSPACE; | p->flags |= TERMP_NOSPACE; | ||
if (0 == p->col) { | if (p->col || p->viscol) | ||
p->flags &= ~TERMP_NOLPAD; | term_flushln(p); | ||
return; | |||
} | |||
term_flushln(p); | |||
p->flags &= ~TERMP_NOLPAD; | |||
} | } | ||
|
|
||
{ | { | ||
term_newln(p); | term_newln(p); | ||
putchar('\n'); | p->viscol = 0; | ||
if (0 < p->skipvsp) | |||
p->skipvsp--; | |||
else | |||
(*p->endline)(p); | |||
} | } | ||
static void | |||
spec(struct termp *p, const char *word, size_t len) | |||
{ | |||
const char *rhs; | |||
size_t sz; | |||
rhs = chars_a2ascii(p->symtab, word, len, &sz); | |||
if (rhs) | |||
encode(p, rhs, sz); | |||
} | |||
static void | |||
res(struct termp *p, const char *word, size_t len) | |||
{ | |||
const char *rhs; | |||
size_t sz; | |||
rhs = chars_a2res(p->symtab, word, len, &sz); | |||
if (rhs) | |||
encode(p, rhs, sz); | |||
} | |||
void | void | ||
term_fontlast(struct termp *p) | term_fontlast(struct termp *p) | ||
{ | { | ||
|
|
||
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, *seq; | const char *seq, *cp; | ||
int sz; | char c; | ||
int sz, uc; | |||
size_t ssz; | size_t ssz; | ||
enum roffdeco deco; | enum mandoc_esc esc; | ||
sv = word; | |||
if (word[0] && '\0' == word[1]) | |||
switch (word[0]) { | |||
case('.'): | |||
/* FALLTHROUGH */ | |||
case(','): | |||
/* FALLTHROUGH */ | |||
case(';'): | |||
/* FALLTHROUGH */ | |||
case(':'): | |||
/* FALLTHROUGH */ | |||
case('?'): | |||
/* FALLTHROUGH */ | |||
case('!'): | |||
/* FALLTHROUGH */ | |||
case(')'): | |||
/* FALLTHROUGH */ | |||
case(']'): | |||
if ( ! (TERMP_IGNDELIM & p->flags)) | |||
p->flags |= TERMP_NOSPACE; | |||
break; | |||
default: | |||
break; | |||
} | |||
if ( ! (TERMP_NOSPACE & p->flags)) { | if ( ! (TERMP_NOSPACE & p->flags)) { | ||
bufferc(p, ' '); | if ( ! (TERMP_KEEP & p->flags)) { | ||
if (TERMP_SENTENCE & p->flags) | if (TERMP_PREKEEP & p->flags) | ||
p->flags |= TERMP_KEEP; | |||
bufferc(p, ' '); | 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; | |||
p->flags &= ~TERMP_SENTENCE; | p->flags &= ~(TERMP_SENTENCE | TERMP_IGNDELIM); | ||
/* FIXME: use strcspn. */ | while ('\0' != *word) { | ||
while (*word) { | |||
if ('\\' != *word) { | if ('\\' != *word) { | ||
encode(p, word, 1); | if (TERMP_SKIPCHAR & p->flags) { | ||
word++; | p->flags &= ~TERMP_SKIPCHAR; | ||
word++; | |||
continue; | |||
} | |||
ssz = strcspn(word, "\\"); | |||
encode(p, word, ssz); | |||
word += (int)ssz; | |||
continue; | continue; | ||
} | } | ||
seq = ++word; | word++; | ||
sz = a2roffdeco(&deco, &seq, &ssz); | esc = mandoc_escape(&word, &seq, &sz); | ||
if (ESCAPE_ERROR == esc) | |||
break; | |||
switch (deco) { | if (TERMENC_ASCII != p->enc) | ||
case (DECO_RESERVED): | switch (esc) { | ||
res(p, seq, ssz); | case (ESCAPE_UNICODE): | ||
uc = mchars_num2uc(seq + 1, sz - 1); | |||
if ('\0' == uc) | |||
break; | |||
encode1(p, uc); | |||
continue; | |||
case (ESCAPE_SPECIAL): | |||
uc = mchars_spec2cp(p->symtab, seq, sz); | |||
if (uc <= 0) | |||
break; | |||
encode1(p, uc); | |||
continue; | |||
default: | |||
break; | |||
} | |||
switch (esc) { | |||
case (ESCAPE_UNICODE): | |||
encode1(p, '?'); | |||
break; | break; | ||
case (DECO_SPECIAL): | case (ESCAPE_NUMBERED): | ||
spec(p, seq, ssz); | c = mchars_num2char(seq, sz); | ||
if ('\0' != c) | |||
encode(p, &c, 1); | |||
break; | break; | ||
case (DECO_BOLD): | case (ESCAPE_SPECIAL): | ||
cp = mchars_spec2str(p->symtab, seq, sz, &ssz); | |||
if (NULL != cp) | |||
encode(p, cp, ssz); | |||
else if (1 == ssz) | |||
encode(p, seq, sz); | |||
break; | |||
case (ESCAPE_FONTBOLD): | |||
term_fontrepl(p, TERMFONT_BOLD); | term_fontrepl(p, TERMFONT_BOLD); | ||
break; | break; | ||
case (DECO_ITALIC): | case (ESCAPE_FONTITALIC): | ||
term_fontrepl(p, TERMFONT_UNDER); | term_fontrepl(p, TERMFONT_UNDER); | ||
break; | break; | ||
case (DECO_ROMAN): | case (ESCAPE_FONT): | ||
/* FALLTHROUGH */ | |||
case (ESCAPE_FONTROMAN): | |||
term_fontrepl(p, TERMFONT_NONE); | term_fontrepl(p, TERMFONT_NONE); | ||
break; | break; | ||
case (DECO_PREVIOUS): | case (ESCAPE_FONTPREV): | ||
term_fontlast(p); | term_fontlast(p); | ||
break; | break; | ||
default: | case (ESCAPE_NOSPACE): | ||
if (TERMP_SKIPCHAR & p->flags) | |||
p->flags &= ~TERMP_SKIPCHAR; | |||
else if ('\0' == *word) | |||
p->flags |= TERMP_NOSPACE; | |||
break; | break; | ||
} | case (ESCAPE_SKIPCHAR): | ||
p->flags |= TERMP_SKIPCHAR; | |||
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]) { | |||
case('('): | |||
/* FALLTHROUGH */ | |||
case('['): | |||
p->flags |= TERMP_NOSPACE; | |||
break; | break; | ||
default: | default: | ||
break; | break; | ||
} | } | ||
} | |||
} | } | ||
static void | static void | ||
adjbuf(struct termp *p, size_t sz) | adjbuf(struct termp *p, int sz) | ||
{ | { | ||
if (0 == p->maxcols) | if (0 == p->maxcols) | ||
|
|
||
while (sz >= p->maxcols) | while (sz >= p->maxcols) | ||
p->maxcols <<= 2; | p->maxcols <<= 2; | ||
p->buf = realloc(p->buf, p->maxcols); | p->buf = mandoc_realloc | ||
if (NULL == p->buf) { | (p->buf, sizeof(int) * (size_t)p->maxcols); | ||
perror(NULL); | |||
exit(EXIT_FAILURE); | |||
} | |||
} | } | ||
static void | static void | ||
buffera(struct termp *p, const char *word, size_t sz) | bufferc(struct termp *p, char c) | ||
{ | { | ||
if (p->col + sz >= p->maxcols) | if (p->col + 1 >= p->maxcols) | ||
adjbuf(p, p->col + sz); | adjbuf(p, p->col + 1); | ||
memcpy(&p->buf[(int)p->col], word, sz); | p->buf[p->col++] = c; | ||
p->col += sz; | |||
} | } | ||
/* | |||
* See encode(). | |||
* Do this for a single (probably unicode) value. | |||
* Does not check for non-decorated glyphs. | |||
*/ | |||
static void | static void | ||
bufferc(struct termp *p, char c) | encode1(struct termp *p, int c) | ||
{ | { | ||
enum termfont f; | |||
if (p->col + 1 >= p->maxcols) | if (TERMP_SKIPCHAR & p->flags) { | ||
adjbuf(p, p->col + 1); | p->flags &= ~TERMP_SKIPCHAR; | ||
return; | |||
} | |||
p->buf[(int)p->col++] = c; | if (p->col + 4 >= p->maxcols) | ||
} | adjbuf(p, p->col + 4); | ||
f = term_fonttop(p); | |||
if (TERMFONT_NONE == f) { | |||
p->buf[p->col++] = c; | |||
return; | |||
} else if (TERMFONT_UNDER == f) { | |||
p->buf[p->col++] = '_'; | |||
} else | |||
p->buf[p->col++] = c; | |||
p->buf[p->col++] = 8; | |||
p->buf[p->col++] = c; | |||
} | |||
static void | static void | ||
encode(struct termp *p, const char *word, size_t sz) | encode(struct termp *p, const char *word, size_t sz) | ||
{ | { | ||
enum termfont f; | enum termfont f; | ||
int i; | int i, len; | ||
if (TERMP_SKIPCHAR & p->flags) { | |||
p->flags &= ~TERMP_SKIPCHAR; | |||
return; | |||
} | |||
/* LINTED */ | |||
len = sz; | |||
/* | /* | ||
* Encode and buffer a string of characters. If the current | * Encode and buffer a string of characters. If the current | ||
* font mode is unset, buffer directly, else encode then buffer | * font mode is unset, buffer directly, else encode then buffer | ||
|
|
||
*/ | */ | ||
if (TERMFONT_NONE == (f = term_fonttop(p))) { | if (TERMFONT_NONE == (f = term_fonttop(p))) { | ||
buffera(p, word, sz); | if (p->col + len >= p->maxcols) | ||
adjbuf(p, p->col + len); | |||
for (i = 0; i < len; i++) | |||
p->buf[p->col++] = word[i]; | |||
return; | return; | ||
} | } | ||
for (i = 0; i < (int)sz; i++) { | /* Pre-buffer, assuming worst-case. */ | ||
if ( ! isgraph((u_char)word[i])) { | |||
bufferc(p, word[i]); | if (p->col + 1 + (len * 3) >= p->maxcols) | ||
adjbuf(p, p->col + 1 + (len * 3)); | |||
for (i = 0; i < len; i++) { | |||
if (ASCII_HYPH != word[i] && | |||
! isgraph((unsigned char)word[i])) { | |||
p->buf[p->col++] = word[i]; | |||
continue; | continue; | ||
} | } | ||
if (TERMFONT_UNDER == f) | if (TERMFONT_UNDER == f) | ||
bufferc(p, '_'); | p->buf[p->col++] = '_'; | ||
else if (ASCII_HYPH == word[i]) | |||
p->buf[p->col++] = '-'; | |||
else | else | ||
bufferc(p, word[i]); | p->buf[p->col++] = word[i]; | ||
bufferc(p, 8); | p->buf[p->col++] = 8; | ||
bufferc(p, word[i]); | p->buf[p->col++] = word[i]; | ||
} | } | ||
} | } | ||
size_t | |||
term_len(const struct termp *p, size_t sz) | |||
{ | |||
return((*p->width)(p, ' ') * sz); | |||
} | |||
static size_t | |||
cond_width(const struct termp *p, int c, int *skip) | |||
{ | |||
if (*skip) { | |||
(*skip) = 0; | |||
return(0); | |||
} else | |||
return((*p->width)(p, c)); | |||
} | |||
size_t | size_t | ||
term_vspan(const struct roffsu *su) | term_strlen(const struct termp *p, const char *cp) | ||
{ | { | ||
size_t sz, rsz, i; | |||
int ssz, skip, c; | |||
const char *seq, *rhs; | |||
enum mandoc_esc esc; | |||
static const char rej[] = { '\\', ASCII_HYPH, ASCII_NBRSP, '\0' }; | |||
/* | |||
* Account for escaped sequences within string length | |||
* calculations. This follows the logic in term_word() as we | |||
* must calculate the width of produced strings. | |||
*/ | |||
sz = 0; | |||
skip = 0; | |||
while ('\0' != *cp) { | |||
rsz = strcspn(cp, rej); | |||
for (i = 0; i < rsz; i++) | |||
sz += cond_width(p, *cp++, &skip); | |||
c = 0; | |||
switch (*cp) { | |||
case ('\\'): | |||
cp++; | |||
esc = mandoc_escape(&cp, &seq, &ssz); | |||
if (ESCAPE_ERROR == esc) | |||
return(sz); | |||
if (TERMENC_ASCII != p->enc) | |||
switch (esc) { | |||
case (ESCAPE_UNICODE): | |||
c = mchars_num2uc | |||
(seq + 1, ssz - 1); | |||
if ('\0' == c) | |||
break; | |||
sz += cond_width(p, c, &skip); | |||
continue; | |||
case (ESCAPE_SPECIAL): | |||
c = mchars_spec2cp | |||
(p->symtab, seq, ssz); | |||
if (c <= 0) | |||
break; | |||
sz += cond_width(p, c, &skip); | |||
continue; | |||
default: | |||
break; | |||
} | |||
rhs = NULL; | |||
switch (esc) { | |||
case (ESCAPE_UNICODE): | |||
sz += cond_width(p, '?', &skip); | |||
break; | |||
case (ESCAPE_NUMBERED): | |||
c = mchars_num2char(seq, ssz); | |||
if ('\0' != c) | |||
sz += cond_width(p, c, &skip); | |||
break; | |||
case (ESCAPE_SPECIAL): | |||
rhs = mchars_spec2str | |||
(p->symtab, seq, ssz, &rsz); | |||
if (ssz != 1 || rhs) | |||
break; | |||
rhs = seq; | |||
rsz = ssz; | |||
break; | |||
case (ESCAPE_SKIPCHAR): | |||
skip = 1; | |||
break; | |||
default: | |||
break; | |||
} | |||
if (NULL == rhs) | |||
break; | |||
if (skip) { | |||
skip = 0; | |||
break; | |||
} | |||
for (i = 0; i < rsz; i++) | |||
sz += (*p->width)(p, *rhs++); | |||
break; | |||
case (ASCII_NBRSP): | |||
sz += cond_width(p, ' ', &skip); | |||
cp++; | |||
break; | |||
case (ASCII_HYPH): | |||
sz += cond_width(p, '-', &skip); | |||
cp++; | |||
break; | |||
default: | |||
break; | |||
} | |||
} | |||
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) { | ||
|
|
||
r); | r); | ||
} | } | ||
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); | |||
} | } | ||