0,0 → 1,588 |
/* |
SDL_bdf - renders BDF fonts |
Copyright (C) 2002-2003 Andre de Leiradella |
|
This library is free software; you can redistribute it and/or |
modify it under the terms of the GNU Lesser General Public |
License as published by the Free Software Foundation; either |
version 2.1 of the License, or (at your option) any later version. |
|
This library is distributed in the hope that it will be useful, |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
Lesser General Public License for more details. |
|
You should have received a copy of the GNU Lesser General Public |
License along with this library; if not, write to the Free Software |
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|
For information about SDL_bdf contact leiradella@bigfoot.com |
|
Version 1.0: first public release. |
Version 1.1: removed SDL dependecies, now SDL_bdf can be used with any graphics |
library. |
Version 1.2: fixed BDF_SizeH and BDF_SizeEntitiesH to return the correct sizes. |
*/ |
#include <stdio.h> |
#include <string.h> |
#include <ctype.h> |
#include <stdarg.h> |
#include <stdlib.h> |
#include <limits.h> |
#include <SDL_bdf.h> |
|
/* |
BDF fonts are encoded in ascii. Each line of the file begins with a keyword, |
has zero or more arguments which can be integers, numbers (floats), strings and |
quoted string, and ends with an eol. Some keywords are optional or |
user-defined, unquoted strings can begin with any character which means that it |
can start with a minus sign making it hard to distinguish them from negative |
integers or numbers (and it do happen in some places). Adobe's spec isn't clear |
sometimes so we have to be cautious (eg it doesn't say how eols are encoded so |
we have to accept MSDOS (cr lf), Unix (lf) and Mac (cr) styles. I implemented a |
*very* relaxed parser which won't stop on some errors. The parser reads the |
font line by line and for each one verifies which is the keyword (using a hash |
table generated by gperf) and takes the appropriate action through a switch |
statement. |
*/ |
|
/* BDF keywords. */ |
#define BBX 1 |
#define BITMAP 2 |
#define CHARS 3 |
#define COMMENT 4 |
#define CONTENTVERSION 5 |
#define DWIDTH 6 |
#define DWIDTH1 7 |
#define ENCODING 8 |
#define ENDCHAR 9 |
#define ENDFONT 10 |
#define ENDPROPERTIES 11 |
#define FONT 12 |
#define FONTBOUNDINGBOX 13 |
#define METRICSSET 14 |
#define SIZE 15 |
#define STARTCHAR 16 |
#define STARTFONT 17 |
#define STARTPROPERTIES 18 |
#define SWIDTH 19 |
#define SWIDTH1 20 |
#define VVECTOR 21 |
|
/* Include GPERF hash function generated from bdf.in. */ |
#include "bdf.gperf" |
|
/* Reads a line from rwops up to 65536 characters. */ |
static int readline(BDF_ReadByte getbyte, void *info, char *data) { |
int k, i = 65536; |
|
for (;;) { |
k = getbyte(info); |
if (k == -1) |
return 0; |
switch (k) { |
default: |
*data++=k; |
if (--i==0) { |
case '\n': |
*data='\0'; |
return 1; |
} |
case '\r': |
; |
} |
} |
} |
|
/* Parse an integer updating the pointer. */ |
static int readinteger(char **data, int *integer) { |
char *aux; |
int i, signal; |
|
aux = *data; |
signal = 1; |
/* Adobe's spec doesn't say numbers can be preceeded by '+' but we never know. */ |
switch (*aux) { |
case '-': |
signal = -1; |
case '+': |
aux++; |
break; |
} |
/* Skip spaces between signal and first digit. */ |
while (isspace(*aux)) aux++; |
if (!isdigit(*aux)) return 0; |
/* Now we start reading digits. */ |
i = 0; |
while (isdigit(*aux)) i = i * 10 + *aux++ - '0'; |
/* We're done, update pointer and return value. */ |
*data = aux; |
if (integer != NULL) *integer = signal * i; |
return 1; |
} |
|
/* Parse a double updating the pointer. */ |
static int readnumber(char **data, double *number) { |
register char *aux; |
double n, d; |
int signal; |
|
aux = *data; |
signal = 1; |
/* Adobe's spec doesn't say numbers can be preceeded by '+' but we never know. */ |
switch (*aux) { |
case '-': |
signal = -1; |
case '+': |
aux++; |
break; |
} |
/* Skip spaces between signal and first digit. */ |
while (isspace(*aux)) aux++; |
if (!isdigit(*aux)) return 0; |
/* Now we start reading digits */ |
n = 0; |
while (isdigit(*aux)) n = n * 10 + *aux++ - '0'; |
d = 10; |
/* If next character is a dot then we have a decimal part. */ |
if (*aux == '.') { |
aux++; |
/* Decimal point must be succeeded by one or more digits. */ |
if (!isdigit(*aux)) return 0; |
while (isdigit(*aux)) n += (*aux++ - '0') / d, d /= 10; |
} |
/* We're done, update pointer and return value. */ |
*data = aux; |
if (number != NULL) *number = signal*n; |
return 1; |
} |
|
/* Parse a string updating the pointer. */ |
static int readstring(char **data, char **string) { |
int len; |
|
len = strlen(*data); |
if (string != NULL) { |
/* Malloc the required space. */ |
*string=(char *)malloc(len + 1); |
if (*string == NULL) |
return 0; |
/* Copy everything. */ |
strcpy(*string, *data); |
} |
/* We're done, update pointer. */ |
*data += len; |
return 1; |
} |
|
/* Scan the line (just after the keyword) for elements listed in format. */ |
static int scan(char *data, char *format, ...) { |
va_list args; |
int *i; |
double *n; |
char **s; |
|
/* Keyword already skipped, skip spaces. */ |
while (*data != '\0' && isspace(*data)) data++; |
/* Scan the data for the pattern in format. */ |
va_start(args, format); |
while (*format != '\0') { |
switch (*format++) { |
case 'i': /* integer. */ |
i = va_arg(args, int *); |
if (!readinteger(&data, i)) { |
va_end(args); |
return 0; |
} |
break; |
case 'n': /* number. */ |
n = va_arg(args, double *); |
if (!readnumber(&data, n)) { |
va_end(args); |
return 0; |
} |
break; |
case 's': /* string. */ |
s = va_arg(args, char **); |
if (!readstring(&data, s)) { |
va_end(args); |
return 0; |
} |
break; |
} |
/* Skip spaces between elements. */ |
while (*data != '\0' && isspace(*data)) data++; |
} |
va_end(args); |
return *data == '\0'; |
} |
|
/* Compare function to sort characters by their names. */ |
static int compare(const void *c1, const void *c2) { |
return strcmp(((BDF_Char *)c1)->name, ((BDF_Char *)c2)->name); |
} |
|
#define XVAL(x) (isdigit((x)) ? (x) - '0' : toupper((x)) - 'A' + 10) |
|
BDF_Font *BDF_OpenFont(BDF_ReadByte getbyte, void *info, int *error) { |
BDF_Font *font; |
BDF_Char *chr; |
char *data; |
register char *aux; |
struct bdfword *word; |
unsigned char *bits; |
double n; |
int numChars; |
int dwx0, dwy0; |
int dwx1, dwy1; |
int bbw, bbh, bbxoff0x, bbyoff0y, i; |
|
/* Malloc the font. */ |
font = (BDF_Font *)malloc(sizeof(BDF_Font)); |
if (font == NULL) { |
if (error != NULL) *error = BDF_MEMORYERROR; |
goto error; |
} |
/* Null array of characters. */ |
font->chars = NULL; |
/* Looks ugly but I'm lazy... */ |
data = (char *)malloc(65537 * sizeof(char)); |
if (data == NULL) { |
if (error != NULL) *error = BDF_MEMORYERROR; |
goto error; |
} |
/* Zero the structure. */ |
font->metricsSet = font->numChars = 0; |
dwx0 = dwy0 = 0; |
dwx1 = dwy1 = 0; |
bbw = bbh = 0; |
bbxoff0x = bbyoff0y = 0; |
/* chr holds the current character or NULL if we're not inside a character definition. */ |
chr = NULL; |
for (;;) { |
/* Read one line at a time. */ |
if (!readline(getbyte, info, data)) { |
if (*error != NULL) *error = BDF_READERROR; |
goto error; |
} |
/* Find end of keyword. */ |
aux = data; |
while (*aux != '\0' && !isspace(*aux)) aux++; |
/* Find which keyword it is using gperf's hash function. */ |
word = (struct bdfword *)in_word_set(data, aux - data); |
switch (word == NULL ? 0 : word->code) { |
case STARTFONT: |
/* Issue an error on versions higher than 2.2. */ |
if (!scan(aux, "n", &n)) { |
if (error != NULL) *error = BDF_PARSEERROR; |
goto error; |
} |
if (n > 2.2) { |
if (error != NULL) *error = BDF_WRONGVERSION; |
goto error; |
} |
break; |
case FONTBOUNDINGBOX: |
/* The FONTBOUNDINGBOX values seems to be defaults for BBX values. */ |
if (!scan(aux, "iiii", &bbw, &bbh, &bbxoff0x, &bbyoff0y)) { |
if (error != NULL) *error = BDF_PARSEERROR; |
goto error; |
} |
break; |
case METRICSSET: |
/* We only handle horizontal writing by now. */ |
if (!scan(aux, "i", &font->metricsSet)) { |
if (error != NULL) *error = BDF_PARSEERROR; |
goto error; |
} |
if (font->metricsSet != 0) { |
if (error != NULL) *error = BDF_CANNOTHANDLEVERTICAL; |
goto error; |
} |
break; |
case DWIDTH: |
/* This is the character's width in pixels. */ |
if (chr != NULL) |
if (!scan(aux, "ii", &chr->dwx0, &chr->dwy0)) { |
if (error != NULL) *error = BDF_PARSEERROR; |
goto error; |
} |
else |
if (!scan(aux, "ii", &dwx0, &dwy0)) { |
if (error != NULL) *error = BDF_PARSEERROR; |
goto error; |
} |
break; |
case DWIDTH1: |
/* This is the character's width in pixels for vertical writing. */ |
if (chr != NULL) |
if (!scan(aux, "ii", &chr->dwx1, &chr->dwy1)) { |
if (error != NULL) *error = BDF_PARSEERROR; |
goto error; |
} |
else |
if (!scan(aux, "ii", &dwx1, &dwy1)) { |
if (error != NULL) *error = BDF_PARSEERROR; |
goto error; |
} |
break; |
case CHARS: |
/* We read the number of chars in this font and malloc the required memory. */ |
if (!scan(aux, "i", &font->numChars)) { |
if (error != NULL) *error = BDF_PARSEERROR; |
goto error; |
} |
font->chars = (BDF_Char *)malloc(font->numChars * sizeof(BDF_Char)); |
if (font->chars == NULL) { |
if (error != NULL) *error = BDF_MEMORYERROR; |
goto error; |
} |
/* Zero all characters' info. */ |
for (i = font->numChars, chr = font->chars; i != 0; i--, chr++) { |
chr->name = NULL; |
chr->code = -1; |
chr->dwx0 = chr->dwy0 = 0; |
chr->dwx1 = chr->dwy1 = 0; |
chr->bbw = chr->bbh = 0; |
chr->bbxoff0x = chr->bbyoff0y = 0; |
chr->bits = NULL; |
} |
/* chr points to the current character. */ |
chr = font->chars; |
break; |
case STARTCHAR: |
/* If chr is NULL there are more characters in the font then expected. */ |
if (chr == NULL) { |
if (error != NULL) *error = BDF_TOOMANYCHARACTERS; |
goto error; |
} |
if (!scan(aux, "s", &chr->name)) { |
if (error != NULL) *error = BDF_PARSEERROR; |
goto error; |
} |
/* Copy default values. */ |
chr->code = -1; |
chr->dwx0 = dwx0; |
chr->dwy0 = dwy0; |
chr->dwx1 = dwx1; |
chr->dwy1 = dwy1; |
chr->bbw = bbw; |
chr->bbh = bbh; |
chr->bbxoff0x = bbxoff0x; |
chr->bbyoff0y = bbyoff0y; |
break; |
case ENCODING: |
/* Read character's code, it can be -1. */ |
if (chr != NULL) |
if (!scan(aux, "i", &chr->code)) { |
if (error != NULL) *error = BDF_PARSEERROR; |
goto error; |
} |
break; |
case BBX: |
/* The bounding box around the character's black pixels. */ |
if (chr != NULL) |
if (!scan(aux, "iiii", &chr->bbw, &chr->bbh, &chr->bbxoff0x, &chr->bbyoff0y)) { |
if (error != NULL) *error = BDF_PARSEERROR; |
goto error; |
} |
break; |
case BITMAP: |
/* BITMAP signals the start of the hex data. */ |
if (chr != NULL) { |
/* wbytes is the width of the char in bytes. */ |
chr->wbytes = (chr->bbw + 7) / 8; |
/* Malloc the memory for the pixels. */ |
chr->bits = (unsigned char *)malloc(chr->wbytes * chr->bbh); |
if (chr->bits == NULL) { |
if (error != NULL) *error = BDF_MEMORYERROR; |
goto error; |
} |
/* Read all pixels from file. */ |
for (i = chr->bbh, bits = chr->bits; i != 0; i--) { |
if (!readline(getbyte, info, data)) { |
if (error != NULL) *error = BDF_READERROR; |
goto error; |
} |
aux = data; |
while (aux[0] != '\0' && aux[1] != '\0') { |
*bits++ = XVAL(aux[0]) * 16 + XVAL(aux[1]); |
aux += 2; |
} |
} |
} |
break; |
case ENDCHAR: |
/* Skip to the next character, makes a bound check. */ |
chr++; |
if ((chr-font->chars) >= font->numChars) |
chr = NULL; |
break; |
case ENDFONT: |
/* Ends the font, if chr is not NULL then we are short on characters. */ |
if (chr != NULL) { |
if (error != NULL) *error = BDF_TOOFEWCHARACTERS; |
goto error; |
} |
/* Sort font by character names, should be an hash table. */ |
qsort(font->chars, font->numChars, sizeof(BDF_Char), compare); |
/* Fast pointers to characters encoded between [0..255]. */ |
for (i = 0; i < 256; i++) |
font->code[i] = NULL; |
for (i = font->numChars, chr = font->chars; i != 0; i--, chr++) |
if (chr->code >= 0 && chr->code <= 255) |
font->code[chr->code] = chr; |
if (error != NULL) *error = BDF_OK; |
free(data); |
return font; |
} |
} |
error: |
/* Free everything. */ |
free(data); |
BDF_CloseFont(font); |
return NULL; |
} |
|
void BDF_CloseFont(BDF_Font *font) { |
int i; |
BDF_Char *chr; |
|
/* Free everything. */ |
if (font != NULL) { |
if (font->chars != NULL) { |
for (i = font->numChars, chr = font->chars; i != 0; i--, chr++) { |
free(chr->name); |
free(chr->bits); |
} |
free(font->chars); |
} |
free(font); |
} |
} |
|
/* Finds a char in the font, if entities is not zero then handle entities. */ |
static BDF_Char *findchar(BDF_Font *font, char **text, int entities) { |
char *aux; |
BDF_Char key, *chr; |
|
/* Handle entities. */ |
if (entities != 0 && **text == '&') { |
if ((*text)[1] != '&') { |
key.name = *text + 1; |
aux = strchr(*text, ';'); |
if (aux == NULL) { |
*text = *text + strlen(*text); |
return NULL; |
} |
*aux = '\0'; |
*text = aux + 1; |
chr = (BDF_Char *)bsearch(&key, font->chars, font->numChars, sizeof(BDF_Char), compare); |
*aux = ';'; |
return chr; |
} else |
(*text)++; |
} |
/* Return the character in the range [0..255]. */ |
return font->code[*(unsigned char *)(*text)++]; |
} |
|
/* Determines the size of the horizontal text. */ |
static void sizeh(BDF_Font *font, char *text, int entities, int *x0, int *y0, int *width, int *height) { |
BDF_Char *chr; |
int first, y, h, minh, maxh; |
|
first = 1; |
minh = *y0 = INT_MAX; |
maxh = INT_MIN; |
y = 0; |
while (*text != '\0') { |
chr = findchar(font, &text, entities); |
if (first != 0) { |
first = 0; |
*x0 = *width = -chr->bbxoff0x; |
} |
if (chr != NULL) { |
h = y - (chr->bbyoff0y + chr->bbh); |
if (h < minh) |
minh = h; |
h += chr->bbh - 1; |
if (h > maxh) |
maxh = h; |
*width += chr->dwx0; |
if (chr->bbyoff0y < *y0) |
*y0 = chr->bbyoff0y; |
y += chr->dwy0; |
} |
} |
*height = maxh - minh + 1; |
*y0 += *height; |
} |
|
void BDF_SizeH(BDF_Font *font, char *text, int *x0, int *y0, int *width, int *height) { |
int _x0, _y0, _width, _height; |
|
sizeh(font, text, 0, &_x0, &_y0, &_width, &_height); |
if (x0 != NULL) *x0 = _x0; |
if (y0 != NULL) *y0 = _y0; |
if (width != NULL) *width = _width; |
if (height != NULL) *height = _height; |
} |
|
void BDF_SizeEntitiesH(BDF_Font *font, char *text, int *x0, int *y0, int *width, int *height) { |
int _x0, _y0, _width, _height; |
|
sizeh(font, text, 1, &_x0, &_y0, &_width, &_height); |
if (x0 != NULL) *x0 = _x0; |
if (y0 != NULL) *y0 = _y0; |
if (width != NULL) *width = _width; |
if (height != NULL) *height = _height; |
} |
|
/* Draws a char on the surface. */ |
static void drawchar(void *surface, BDF_PutPixel putpixel, BDF_Char *chr, int x, int y, unsigned int color) { |
int xx; |
unsigned char *bits, *endfont, *endline; |
|
/* Calculate the position of the first pixel. */ |
x += chr->bbxoff0x; |
y -= (chr->bbyoff0y + chr->bbh); |
bits = chr->bits; |
/* Put them! */ |
for (endfont = bits + chr->wbytes * chr->bbh; bits < endfont; y++) |
for (endline = bits + chr->wbytes, xx = x; bits < endline; xx += 8, bits++) { |
if ((*bits) & 0x80) putpixel(surface, xx, y, color); |
if ((*bits) & 0x40) putpixel(surface, xx + 1, y, color); |
if ((*bits) & 0x20) putpixel(surface, xx + 2, y, color); |
if ((*bits) & 0x10) putpixel(surface, xx + 3, y, color); |
if ((*bits) & 0x08) putpixel(surface, xx + 4, y, color); |
if ((*bits) & 0x04) putpixel(surface, xx + 5, y, color); |
if ((*bits) & 0x02) putpixel(surface, xx + 6, y, color); |
if ((*bits) & 0x01) putpixel(surface, xx + 7, y, color); |
} |
} |
|
/* Draws an entire line of text. */ |
static int drawh(void *surface, BDF_PutPixel putpixel, BDF_Font *font, char *text, int entities, int x, int y, unsigned int color) { |
BDF_Char *chr; |
|
/* For each character... */ |
while (*text != '\0') { |
chr = findchar(font, &text, entities); |
if (chr != NULL) { |
/* ... draw it. */ |
drawchar(surface, putpixel, chr, x, y, color); |
x += chr->dwx0; |
y += chr->dwy0; |
} |
} |
return x; |
} |
|
int BDF_DrawH(void *surface, BDF_PutPixel putpixel, BDF_Font *font, char *text, int x, int y, unsigned int color) { |
return drawh(surface, putpixel, font, text, 0, x, y, color); |
} |
|
int BDF_DrawEntitiesH(void *surface, BDF_PutPixel putpixel, BDF_Font *font, char *text, int x, int y,unsigned int color) { |
return drawh(surface, putpixel, font, text, 1, x, y, color); |
} |