forked from KolibriOS/kolibrios
3cf7852e03
git-svn-id: svn://kolibrios.org@5131 a494cfbc-eb01-0410-851d-a64ba20cac60
589 lines
17 KiB
C
589 lines
17 KiB
C
/*
|
|
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);
|
|
}
|