scummvm/graphics/fonts/winfont.cpp
2025-03-02 00:25:33 +01:00

438 lines
13 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/file.h"
#include "common/str.h"
#include "common/stream.h"
#include "common/textconsole.h"
#include "common/formats/winexe_ne.h"
#include "common/formats/winexe_pe.h"
#include "graphics/surface.h"
#include "graphics/fonts/winfont.h"
namespace Graphics {
WinFont::WinFont() {
_glyphs = nullptr;
close();
}
WinFont::~WinFont() {
close();
}
void WinFont::close() {
_pixHeight = 0;
_sizeInPoints = 0;
_dpi = 0;
_maxWidth = 0;
_firstChar = 0;
_lastChar = 0;
_defaultChar = 0;
_glyphCount = 0;
delete[] _glyphs;
_glyphs = nullptr;
}
// Reads a null-terminated string
static Common::String readString(Common::SeekableReadStream &stream) {
Common::String string;
char c = stream.readByte();
while (c && stream.pos() < stream.size()) {
string += c;
c = stream.readByte();
}
return string;
}
static WinFontDirEntry readDirEntry(Common::SeekableReadStream &stream) {
WinFontDirEntry entry;
stream.skip(68); // Useless
entry.points = stream.readUint16LE();
stream.skip(43); // Useless (for now, maybe not in the future)
readString(stream); // Skip Device Name
entry.faceName = readString(stream);
return entry;
}
bool WinFont::loadFromFON(const Common::Path &fileName, const WinFontDirEntry &dirEntry) {
Common::WinResources *exe = Common::WinResources::createFromEXE(fileName);
if (!exe)
return false;
bool ok = loadFromEXE(exe, fileName, dirEntry);
delete exe;
return ok;
}
bool WinFont::loadFromFON(Common::SeekableReadStream &stream, const WinFontDirEntry &dirEntry) {
Common::WinResources *exe = Common::WinResources::createFromEXE(&stream);
if (!exe)
return false;
bool ok = loadFromEXE(exe, Common::Path("stream"), dirEntry);
delete exe;
return ok;
}
bool WinFont::loadFromEXE(Common::WinResources *exe, const Common::Path &fileName, const WinFontDirEntry &dirEntry) {
// Let's pull out the font directory
Common::SeekableReadStream *fontDirectory = exe->getResource(Common::kWinFontDir, Common::String("FONTDIR"));
if (!fontDirectory) {
warning("No font directory in '%s'", fileName.toString(Common::Path::kNativeSeparator).c_str());
return false;
}
uint32 fontId = getFontIndex(*fontDirectory, dirEntry);
delete fontDirectory;
// Couldn't match the face name
if (fontId == 0xffffffff) {
warning("Could not find face '%s' in '%s'", dirEntry.faceName.c_str(),
fileName.toString(Common::Path::kNativeSeparator).c_str());
return false;
}
// Actually go get our font now...
Common::SeekableReadStream *fontStream = exe->getResource(Common::kWinFont, fontId);
if (!fontStream) {
warning("Could not find font %d in %s", fontId,
fileName.toString(Common::Path::kNativeSeparator).c_str());
return false;
}
bool ok = loadFromFNT(*fontStream);
delete fontStream;
return ok;
}
/**
* Size in typographic "points"
*
* While early Macintosh mapped "points" and "pixels" very closely,
* that was not the case on Windows.
*
* Windows used 96 dpi for font rendering so a 10 point font would
*
* Macintosh used 72 dpi for fonts while Windows used 96 dpi
*/
int WinFont::getFontSizeInPointsAtDPI(const int dpi) const {
return _sizeInPoints * _dpi / dpi;
}
uint32 WinFont::getFontIndex(Common::SeekableReadStream &stream, const WinFontDirEntry &dirEntry) {
uint16 numFonts = stream.readUint16LE();
// Probably not possible, so this is really a sanity check
if (numFonts == 0) {
warning("No fonts in exe");
return 0xffffffff;
}
// Scour the directory for our matching name
for (uint16 i = 0; i < numFonts; i++) {
uint16 id = stream.readUint16LE();
// Use the first name when empty
if (dirEntry.faceName.empty()) {
_name = getFONFontName(stream);
return id;
}
WinFontDirEntry entry = readDirEntry(stream);
if (dirEntry.faceName.equalsIgnoreCase(entry.faceName) && dirEntry.points == entry.points) // Match!
return id;
}
return 0xffffffff;
}
Common::String WinFont::getFONFontName(Common::SeekableReadStream& stream) {
// Currently only works when dirEntry.faceName in getFontIndex is empty
// But this can be used for each FONTDIR entry
stream.seek(117);
/* Device Name = */ stream.readString();
Common::String fontName = stream.readString();
return fontName;
}
bool WinFont::loadFromFNT(const Common::Path &fileName) {
Common::File file;
return file.open(fileName) && loadFromFNT(file);
}
char WinFont::indexToCharacter(uint16 index) const {
// Use a space for the sentinel value
if (index == _glyphCount - 1)
return ' ';
return index + _firstChar;
}
uint16 WinFont::characterToIndex(uint32 character) const {
// Go to the default character if we didn't find a mapping
if (character < _firstChar || character > _lastChar)
character = _defaultChar;
return character - _firstChar;
}
int WinFont::getCharWidth(uint32 chr) const {
return _glyphs[characterToIndex(chr)].charWidth;
}
bool WinFont::loadFromFNT(Common::SeekableReadStream &stream) {
uint16 version = stream.readUint16LE();
// We'll accept Win1, Win2, and Win3 fonts
if (version != 0x100 && version != 0x200 && version != 0x300) {
warning("Bad FNT version %04x", version);
return false;
}
/* uint32 sizeOfGlyphTableInBytes = */ stream.readUint32LE();
stream.skip(60); // Copyright info
uint16 fontType = stream.readUint16LE();
_sizeInPoints = stream.readUint16LE();
uint16 vertRes = stream.readUint16LE(); // usually 96 as in 96dpi
uint16 horizRes = stream.readUint16LE(); // usually 96 as in 96dpi
if (vertRes != horizRes)
warning("WinFont::loadFromFNT(): FNT horizontal resolution and vertical resolution differ (%d vs %d)", horizRes, vertRes);
_dpi = vertRes;
_ascent = stream.readUint16LE();
/* uint16 internalLeading = */ stream.readUint16LE();
/* uint16 externalLeading = */ stream.readUint16LE();
_italic = stream.readByte();
_underline = stream.readByte();
_strikethrough = stream.readByte();
_weight = stream.readUint16LE();
/* byte charSet = */ stream.readByte();
uint16 pixWidth = stream.readUint16LE();
_pixHeight = stream.readUint16LE();
/* byte pitchAndFamily = */ stream.readByte();
/* uint16 avgWidth = */ stream.readUint16LE();
_maxWidth = stream.readUint16LE();
_firstChar = stream.readByte();
_lastChar = stream.readByte();
_defaultChar = stream.readByte();
/* byte breakChar = */ stream.readByte();
/* uint16 widthBytes = */ stream.readUint16LE();
/* uint32 device = */ stream.readUint32LE();
/* uint32 face = */ stream.readUint32LE();
/* uint32 bitsPointer = */ stream.readUint32LE();
uint32 bitsOffset = stream.readUint32LE();
/* byte reserved = */ stream.readByte();
if (version == 0x100) {
// Seems Win1 has an extra byte?
stream.readByte();
} else if (version == 0x300) {
// For Windows 3.0, Microsoft added 6 new fields. All of which are
// guaranteed to be 0. Which leads to the question: Why add these at all?
/* uint32 flags = */ stream.readUint32LE();
/* uint16 aSpace = */ stream.readUint16LE();
/* uint16 bSpace = */ stream.readUint16LE();
/* uint16 cSpace = */ stream.readUint16LE();
/* uint32 colorPointer = */ stream.readUint32LE();
stream.skip(16); // Reserved
}
// Begin loading in the glyphs
_glyphCount = (_lastChar - _firstChar) + 2;
delete[] _glyphs;
_glyphs = new GlyphEntry[_glyphCount];
for (uint16 i = 0; i < _glyphCount; i++) {
_glyphs[i].charWidth = stream.readUint16LE();
// Use the default if present
if (pixWidth)
_glyphs[i].charWidth = pixWidth;
_glyphs[i].offset = (version == 0x300) ? stream.readUint32LE() : stream.readUint16LE();
// Seems the offsets in the Win1 font format are based on bitsOffset
if (version == 0x100)
_glyphs[i].offset += bitsOffset;
}
// TODO: Currently only raster fonts are supported!
if (fontType & 1) {
warning("Vector FNT files not supported yet");
return false;
}
// Read in the bitmaps for the raster images
for (uint16 i = 0; i < _glyphCount - 1; i++) {
stream.seek(_glyphs[i].offset);
_glyphs[i].bitmap = new byte[_pixHeight * _glyphs[i].charWidth];
// Calculate the amount of columns
byte colCount = (_glyphs[i].charWidth + 7) / 8;
for (uint16 j = 0; j < colCount; j++) {
for (uint16 k = 0; k < _pixHeight; k++) {
byte x = stream.readByte();
uint offset = j * 8 + k * _glyphs[i].charWidth;
for (byte l = 0; l < 8 && j * 8 + l < _glyphs[i].charWidth; l++)
_glyphs[i].bitmap[offset + l] = (x & (1 << (7 - l))) ? 1 : 0;
}
}
#if 0
// Debug print
debug("Character %02x '%c' at %08x", indexToCharacter(i), indexToCharacter(i), _glyphs[i].offset);
for (uint16 j = 0; j < _pixHeight; j++) {
for (uint16 k = 0; k < _glyphs[i].charWidth; k++)
debugN("%c", _glyphs[i].bitmap[k + j * _glyphs[i].charWidth] ? 'X' : ' ');
debugN("\n");
}
#endif
}
return true;
}
void WinFont::drawChar(Surface *dst, uint32 chr, int x, int y, uint32 color) const {
assert(dst);
assert(dst->format.bytesPerPixel == 1 || dst->format.bytesPerPixel == 2 || dst->format.bytesPerPixel == 4);
assert(_glyphs);
GlyphEntry &glyph = _glyphs[characterToIndex(chr)];
for (uint16 i = 0; i < _pixHeight; i++) {
for (uint16 j = 0; j < glyph.charWidth; j++) {
if (glyph.bitmap[j + i * glyph.charWidth]) {
if (dst->format.bytesPerPixel == 1)
*((byte *)dst->getBasePtr(x + j, y + i)) = color;
else if (dst->format.bytesPerPixel == 2)
*((uint16 *)dst->getBasePtr(x + j, y + i)) = color;
else if (dst->format.bytesPerPixel == 4)
*((uint32 *)dst->getBasePtr(x + j, y + i)) = color;
}
}
}
}
int WinFont::getStyle() const {
int style = kFontStyleRegular;
// This has been taken from Wine Source
// https://github.com/wine-mirror/wine/blob/b9a61cde89e5dc6264b4c152f4dc24ecf064f8f6/include/wingdi.h#L728
if (_weight >= 700)
style |= kFontStyleBold;
if (_italic)
style |= kFontStyleItalic;
if (_underline)
style |= kFontStyleUnderline;
return style;
}
WinFont *WinFont::scaleFont(const WinFont *src, int newSize) {
if (!src) {
warning("WinFont::scaleFont(): Empty font reference in scale font");
return nullptr;
}
if (src->getFontHeight() == 0) {
warning("WinFont::scaleFont(): Requested to scale 0 size font");
return nullptr;
}
WinFont *scaledFont = new WinFont();
Graphics::Surface srcSurf;
srcSurf.create(MAX(src->getFontHeight() * 2, newSize * 2), MAX(src->getFontHeight() * 2, newSize * 2), PixelFormat::createFormatCLUT8());
int dstGraySize = newSize * 20 * newSize;
int *dstGray = (int *)malloc(dstGraySize * sizeof(int));
float scale = (float)newSize / (float)src->getFontHeight();
scaledFont->_pixHeight = (int)(roundf((float)src->_pixHeight * scale));
scaledFont->_maxWidth = (int)(roundf((float)src->_maxWidth * scale));
scaledFont->_ascent = src->_ascent;
scaledFont->_firstChar = src->_firstChar;
scaledFont->_lastChar = src->_lastChar;
scaledFont->_defaultChar = src->_defaultChar;
scaledFont->_italic = src->_italic;
scaledFont->_strikethrough = src->_strikethrough;
scaledFont->_underline = src->_underline;
scaledFont->_weight = src->_weight;
scaledFont->_name = Common::String(src->_name);
scaledFont->_glyphCount = src->_glyphCount;
GlyphEntry *glyphs = new GlyphEntry[src->_glyphCount];
for (int i = 0; i < src->_glyphCount; i++) {
glyphs[i].charWidth = (int)(roundf((float)src->_glyphs[i].charWidth * scale));
glyphs[i].offset = src->_glyphs[i].offset;
int boxWidth = glyphs[i].charWidth;
int boxHeight = scaledFont->_pixHeight;
int grayLevel = (boxWidth * boxHeight) / 3;
byte *bitmap = new byte[boxWidth * boxHeight];
memset(bitmap, 0, boxWidth * boxHeight);
// Scale single character
src->scaleSingleGlyph(&srcSurf, dstGray, dstGraySize, boxWidth, boxHeight, 0, 0, grayLevel, i + src->_firstChar,
src->_pixHeight, src->_glyphs[i].charWidth, scale);
// Convert back to bytes representation
byte *ptr = bitmap;
for (int y = 0; y < boxHeight; y++) {
byte *srcd = (byte *)srcSurf.getBasePtr(0, y);
byte *dst = ptr;
for (int x = 0; x < boxWidth; x++, srcd++) {
*dst++ = *srcd;
}
ptr += boxWidth;
}
glyphs[i].bitmap = bitmap;
}
scaledFont->_glyphs = glyphs;
free(dstGray);
srcSurf.free();
return (WinFont *)scaledFont;
}
} // End of namespace Graphics