scummvm/graphics/fonts/bdf.cpp
2014-02-18 02:39:34 +01:00

626 lines
16 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "graphics/fonts/bdf.h"
#include "common/file.h"
#include "common/endian.h"
#include "common/textconsole.h"
#include "graphics/surface.h"
namespace Graphics {
BdfFont::BdfFont(const BdfFontData &data, DisposeAfterUse::Flag dispose)
: _data(data), _dispose(dispose) {
}
BdfFont::~BdfFont() {
if (_dispose == DisposeAfterUse::YES) {
for (int i = 0; i < _data.numCharacters; ++i)
delete[] _data.bitmaps[i];
delete[] _data.bitmaps;
delete[] _data.advances;
delete[] _data.boxes;
}
}
int BdfFont::getFontHeight() const {
return _data.height;
}
int BdfFont::getMaxCharWidth() const {
return _data.maxAdvance;
}
int BdfFont::getCharWidth(uint32 chr) const {
// In case all font have the same advance value, we use the maximum.
if (!_data.advances)
return _data.maxAdvance;
const int ch = mapToIndex(chr);
// In case no mapping exists, we use the maximum advance.
if (ch < 0)
return _data.maxAdvance;
else
return _data.advances[ch];
}
template<typename PixelType>
void drawCharIntern(byte *ptr, uint pitch, const byte *src, int h, int width, int minX, int maxX, const PixelType color) {
byte data = 0;
while (h--) {
PixelType *dst = (PixelType *)ptr;
for (int x = 0; x < width; ++x) {
if (!(x % 8))
data = *src++;
if (x >= minX && x <= maxX && (data & 0x80))
dst[x] = color;
data <<= 1;
}
ptr += pitch;
}
}
int BdfFont::mapToIndex(uint32 ch) const {
// Check whether the character is included
if (_data.firstCharacter <= (int)ch && (int)ch <= _data.firstCharacter + _data.numCharacters) {
if (_data.bitmaps[ch - _data.firstCharacter])
return ch - _data.firstCharacter;
}
return _data.defaultCharacter - _data.firstCharacter;
}
void BdfFont::drawChar(Surface *dst, uint32 chr, const int tx, const int ty, const uint32 color) const {
assert(dst != 0);
// TODO: Where is the relation between the max advance being smaller or
// equal to 50 and the decision of the theme designer?
// asserting _data.maxAdvance <= 50: let the theme designer decide what looks best
assert(_data.maxAdvance <= 50);
assert(dst->format.bytesPerPixel == 1 || dst->format.bytesPerPixel == 2 || dst->format.bytesPerPixel == 4);
const int idx = mapToIndex(chr);
if (idx < 0)
return;
int width, height, xOffset, yOffset;
// Get the bounding box of the character
if (!_data.boxes) {
width = _data.defaultBox.width;
height = _data.defaultBox.height;
xOffset = _data.defaultBox.xOffset;
yOffset = _data.defaultBox.yOffset;
} else {
width = _data.boxes[idx].width;
height = _data.boxes[idx].height;
xOffset = _data.boxes[idx].xOffset;
yOffset = _data.boxes[idx].yOffset;
}
int y = ty + _data.ascent - yOffset - height;
int x = tx + xOffset;
const byte *src = _data.bitmaps[idx];
const int bytesPerRow = (width + 7) / 8;
const int originalWidth = width;
// Make sure we do not draw outside the surface
if (y < 0) {
src -= y * bytesPerRow;
height += y;
y = 0;
}
if (y + height > dst->h)
height = dst->h - y;
if (height <= 0)
return;
int xStart = 0;
if (x < 0) {
xStart = -x;
width += x;
x = 0;
}
if (x + width > dst->w)
width = dst->w - x;
if (width <= 0)
return;
const int xEnd = xStart + width - 1;
byte *ptr = (byte *)dst->getBasePtr(x, y);
if (dst->format.bytesPerPixel == 1)
drawCharIntern<byte>(ptr, dst->pitch, src, height, originalWidth, xStart, xEnd, color);
else if (dst->format.bytesPerPixel == 2)
drawCharIntern<uint16>(ptr, dst->pitch, src, height, originalWidth, xStart, xEnd, color);
else if (dst->format.bytesPerPixel == 4)
drawCharIntern<uint32>(ptr, dst->pitch, src, height, originalWidth, xStart, xEnd, color);
}
namespace {
inline byte hexToInt(char c) {
if (c >= '0' && c <= '9')
return c - '0';
else if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
else if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
else
return 0;
}
byte *loadCharacter(Common::SeekableReadStream &stream, int &encoding, int &advance, BdfBoundingBox &box) {
Common::String line;
byte *bitmap = 0;
while (true) {
line = stream.readLine();
if (stream.err() || stream.eos()) {
warning("BdfFont::loadCharacter: Premature end of file");
delete[] bitmap;
return 0;
}
if (line.hasPrefix("ENCODING ")) {
if (sscanf(line.c_str(), "ENCODING %d", &encoding) != 1) {
warning("BdfFont::loadCharacter: Invalid ENCODING");
delete[] bitmap;
return 0;
}
} else if (line.hasPrefix("DWIDTH ")) {
int yAdvance;
if (sscanf(line.c_str(), "DWIDTH %d %d", &advance, &yAdvance) != 2) {
warning("BdfFont::loadCharacter: Invalid DWIDTH");
delete[] bitmap;
return 0;
}
if (yAdvance != 0) {
warning("BdfFont::loadCharacter: Character %d has an y advance of %d", encoding, yAdvance);
delete[] bitmap;
return 0;
}
if (advance < 0) {
warning("BdfFont::loadCharacter: Character %d has an x advance of %d", encoding, advance);
delete[] bitmap;
return 0;
}
} else if (line.hasPrefix("BBX ")) {
int width, height, xOffset, yOffset;
if (sscanf(line.c_str(), "BBX %d %d %d %d",
&width, &height, &xOffset, &yOffset) != 4) {
warning("BdfFont::loadCharacter: Invalid BBX");
delete[] bitmap;
return 0;
}
box.width = width;
box.height = height;
box.xOffset = xOffset;
box.yOffset = yOffset;
} else if (line == "BITMAP") {
const uint bytesPerRow = (box.width + 7) / 8;
byte *dst = bitmap = new byte[box.height * bytesPerRow];
for (int y = 0; y < box.height; ++y) {
line = stream.readLine();
if (stream.err() || stream.eos()) {
warning("BdfFont::loadCharacter: Premature end of file");
delete[] bitmap;
return 0;
}
if (line.size() != 2 * bytesPerRow) {
warning("BdfFont::loadCharacter: Pixel line has wrong size");
delete[] bitmap;
return 0;
}
for (uint x = 0; x < bytesPerRow; ++x) {
char nibble1 = line[x * 2 + 0];
char nibble2 = line[x * 2 + 1];
*dst++ = (hexToInt(nibble1) << 4) | hexToInt(nibble2);
}
}
} else if (line == "ENDCHAR") {
return bitmap;
}
}
delete[] bitmap;
return 0;
}
void freeBitmaps(byte **bitmaps, int size) {
for (int i = 0; i < size; ++i)
delete[] bitmaps[i];
}
} // End of anonymous namespace
BdfFont *BdfFont::loadFont(Common::SeekableReadStream &stream) {
BdfFontData font;
memset(&font, 0, sizeof(font));
font.ascent = -1;
font.defaultCharacter = -1;
// We only load the first 256 characters
font.numCharacters = 256;
byte **bitmaps = new byte *[font.numCharacters];
memset(bitmaps, 0, sizeof(byte *) * font.numCharacters);
byte *advances = new byte[font.numCharacters];
BdfBoundingBox *boxes = new BdfBoundingBox[font.numCharacters];
int descent = -1;
Common::String line;
while (true) {
line = stream.readLine();
if (stream.err() || stream.eos()) {
warning("BdfFont::loadFont: Premature end of file");
freeBitmaps(bitmaps, font.numCharacters);
delete[] bitmaps;
delete[] advances;
delete[] boxes;
return 0;
}
// Only parse and handle declarations we actually need
if (line.hasPrefix("FONTBOUNDINGBOX ")) {
int width, height, xOffset, yOffset;
if (sscanf(line.c_str(), "FONTBOUNDINGBOX %d %d %d %d",
&width, &height, &xOffset, &yOffset) != 4) {
warning("BdfFont::loadFont: Invalid FONTBOUNDINGBOX");
freeBitmaps(bitmaps, font.numCharacters);
delete[] bitmaps;
delete[] advances;
delete[] boxes;
return 0;
}
font.defaultBox.width = width;
font.defaultBox.height = height;
font.defaultBox.xOffset = xOffset;
font.defaultBox.yOffset = yOffset;
} else if (line.hasPrefix("FONT_ASCENT ")) {
if (sscanf(line.c_str(), "FONT_ASCENT %d", &font.ascent) != 1) {
warning("BdfFont::loadFont: Invalid FONT_ASCENT");
freeBitmaps(bitmaps, font.numCharacters);
delete[] bitmaps;
delete[] advances;
delete[] boxes;
return 0;
}
} else if (line.hasPrefix("FONT_DESCENT ")) {
if (sscanf(line.c_str(), "FONT_DESCENT %d", &descent) != 1) {
warning("BdfFont::loadFont: Invalid FONT_DESCENT");
freeBitmaps(bitmaps, font.numCharacters);
delete[] bitmaps;
delete[] advances;
delete[] boxes;
return 0;
}
} else if (line.hasPrefix("DEFAULT_CHAR ")) {
if (sscanf(line.c_str(), "DEFAULT_CHAR %d", &font.defaultCharacter) != 1) {
warning("BdfFont::loadFont: Invalid DEFAULT_CHAR");
freeBitmaps(bitmaps, font.numCharacters);
delete[] bitmaps;
delete[] advances;
delete[] boxes;
return 0;
}
} else if (line.hasPrefix("STARTCHAR ")) {
BdfBoundingBox box = font.defaultBox;
int encoding = -1;
int advance = -1;
byte *bitmap = loadCharacter(stream, encoding, advance, box);
// Ignore all characters above 255.
if (encoding < -1 || encoding >= font.numCharacters) {
delete[] bitmap;
encoding = -1;
}
// Calculate the max advance
if (encoding != -1 && advance > font.maxAdvance)
font.maxAdvance = advance;
if (!bitmap && encoding != -1) {
warning("BdfFont::loadFont: Character %d invalid", encoding);
freeBitmaps(bitmaps, font.numCharacters);
delete[] bitmaps;
delete[] advances;
delete[] boxes;
return 0;
}
if (encoding != -1) {
bitmaps[encoding] = bitmap;
advances[encoding] = advance;
boxes[encoding] = box;
}
} else if (line == "ENDFONT") {
break;
}
}
if (font.ascent < 0 || descent < 0) {
warning("BdfFont::loadFont: Invalid ascent or descent");
freeBitmaps(bitmaps, font.numCharacters);
delete[] bitmaps;
delete[] advances;
delete[] boxes;
return 0;
}
font.height = font.ascent + descent;
font.bitmaps = bitmaps;
font.advances = advances;
font.boxes = boxes;
int firstCharacter = font.numCharacters;
int lastCharacter = -1;
bool hasFixedBBox = true;
bool hasFixedAdvance = true;
for (int i = 0; i < font.numCharacters; ++i) {
if (!font.bitmaps[i])
continue;
if (i < firstCharacter)
firstCharacter = i;
if (i > lastCharacter)
lastCharacter = i;
if (font.advances[i] != font.maxAdvance)
hasFixedAdvance = false;
const BdfBoundingBox &bbox = font.boxes[i];
if (bbox.width != font.defaultBox.width
|| bbox.height != font.defaultBox.height
|| bbox.xOffset != font.defaultBox.xOffset
|| bbox.yOffset != font.defaultBox.yOffset)
hasFixedBBox = false;
}
if (lastCharacter == -1) {
warning("BdfFont::loadFont: No glyphs found");
delete[] font.bitmaps;
delete[] font.advances;
delete[] font.boxes;
return 0;
}
// Free the advance table, in case all glyphs use the same advance
if (hasFixedAdvance) {
delete[] font.advances;
font.advances = 0;
}
// Free the box table, in case all glyphs use the same box
if (hasFixedBBox) {
delete[] font.boxes;
font.boxes = 0;
}
// Adapt for the fact that we never use encoding 0.
if (font.defaultCharacter < firstCharacter
|| font.defaultCharacter > lastCharacter)
font.defaultCharacter = -1;
font.firstCharacter = firstCharacter;
const int charsAvailable = lastCharacter - firstCharacter + 1;
// Try to compact the tables
if (charsAvailable < font.numCharacters) {
byte **newBitmaps = new byte *[charsAvailable];
boxes = 0;
advances = 0;
if (!hasFixedBBox)
boxes = new BdfBoundingBox[charsAvailable];
if (!hasFixedAdvance)
advances = new byte[charsAvailable];
for (int i = 0; i < charsAvailable; ++i) {
const int encoding = i + firstCharacter;
if (font.bitmaps[encoding]) {
newBitmaps[i] = bitmaps[encoding];
if (!hasFixedBBox)
boxes[i] = font.boxes[encoding];
if (!hasFixedAdvance)
advances[i] = font.advances[encoding];
} else {
newBitmaps[i] = 0;
}
}
delete[] font.bitmaps;
font.bitmaps = newBitmaps;
delete[] font.advances;
font.advances = advances;
delete[] font.boxes;
font.boxes = boxes;
font.numCharacters = charsAvailable;
}
return new BdfFont(font, DisposeAfterUse::YES);
}
#define BDF_FONTCACHE_TAG MKTAG('S', 'V', 'F', 'C')
#define BDF_FONTCACHE_VERSION 1
bool BdfFont::cacheFontData(const BdfFont &font, const Common::String &filename) {
Common::DumpFile cacheFile;
if (!cacheFile.open(filename)) {
warning("BdfFont::cacheFontData: Couldn't open file '%s' for writing", filename.c_str());
return false;
}
const BdfFontData &data = font._data;
cacheFile.writeUint32BE(BDF_FONTCACHE_TAG);
cacheFile.writeUint32BE(BDF_FONTCACHE_VERSION);
cacheFile.writeUint16BE(data.maxAdvance);
cacheFile.writeByte(data.height);
cacheFile.writeByte(data.defaultBox.width);
cacheFile.writeByte(data.defaultBox.height);
cacheFile.writeSByte(data.defaultBox.xOffset);
cacheFile.writeSByte(data.defaultBox.yOffset);
cacheFile.writeByte(data.ascent);
cacheFile.writeUint16BE(data.firstCharacter);
cacheFile.writeSint16BE(data.defaultCharacter);
cacheFile.writeUint16BE(data.numCharacters);
for (int i = 0; i < data.numCharacters; ++i) {
const BdfBoundingBox &box = data.boxes ? data.boxes[i] : data.defaultBox;
if (data.bitmaps[i]) {
const int bytes = ((box.width + 7) / 8) * box.height;
cacheFile.writeUint32BE(bytes);
cacheFile.write(data.bitmaps[i], bytes);
} else {
cacheFile.writeUint32BE(0);
}
}
if (data.advances) {
cacheFile.writeByte(0xFF);
cacheFile.write(data.advances, data.numCharacters);
} else {
cacheFile.writeByte(0x00);
}
if (data.boxes) {
cacheFile.writeByte(0xFF);
for (int i = 0; i < data.numCharacters; ++i) {
const BdfBoundingBox &box = data.boxes[i];
cacheFile.writeByte(box.width);
cacheFile.writeByte(box.height);
cacheFile.writeSByte(box.xOffset);
cacheFile.writeSByte(box.yOffset);
}
} else {
cacheFile.writeByte(0x00);
}
return !cacheFile.err();
}
BdfFont *BdfFont::loadFromCache(Common::SeekableReadStream &stream) {
const uint32 magic = stream.readUint32BE();
if (magic != BDF_FONTCACHE_TAG)
return 0;
const uint32 version = stream.readUint32BE();
if (version != BDF_FONTCACHE_VERSION)
return 0;
BdfFontData data;
data.maxAdvance = stream.readUint16BE();
data.height = stream.readByte();
data.defaultBox.width = stream.readByte();
data.defaultBox.height = stream.readByte();
data.defaultBox.xOffset = stream.readSByte();
data.defaultBox.yOffset = stream.readSByte();
data.ascent = stream.readByte();
data.firstCharacter = stream.readUint16BE();
data.defaultCharacter = stream.readSint16BE();
data.numCharacters = stream.readUint16BE();
if (stream.err() || stream.eos())
return 0;
byte **bitmaps = new byte *[data.numCharacters];
byte *advances = 0;
BdfBoundingBox *boxes = 0;
for (int i = 0; i < data.numCharacters; ++i) {
uint32 size = stream.readUint32BE();
if (stream.err() || stream.eos()) {
for (int j = 0; j < i; ++j)
delete[] bitmaps[i];
delete[] bitmaps;
return 0;
}
if (size) {
bitmaps[i] = new byte[size];
stream.read(bitmaps[i], size);
} else {
bitmaps[i] = 0;
}
}
if (stream.readByte() == 0xFF) {
advances = new byte[data.numCharacters];
stream.read(advances, data.numCharacters);
}
if (stream.readByte() == 0xFF) {
boxes = new BdfBoundingBox[data.numCharacters];
for (int i = 0; i < data.numCharacters; ++i) {
boxes[i].width = stream.readByte();
boxes[i].height = stream.readByte();
boxes[i].xOffset = stream.readSByte();
boxes[i].yOffset = stream.readSByte();
}
}
if (stream.eos() || stream.err()) {
for (int i = 0; i < data.numCharacters; ++i)
delete[] bitmaps[i];
delete[] bitmaps;
delete[] advances;
delete[] boxes;
return 0;
}
data.bitmaps = bitmaps;
data.advances = advances;
data.boxes = boxes;
return new BdfFont(data, DisposeAfterUse::YES);
}
} // End of namespace Graphics