mirror of
https://github.com/scummvm/scummvm.git
synced 2026-06-20 05:45:29 +00:00
Different platforms have different levels of support of encodings and often have slight variations. We already have tables for most encoding with only CJK missing. Full transcoding inclusion allows us to get reliable encoding results independently of platform. The biggest con is the need for external tables encoding.dat. It removes a duplicate table for korean in graphics/korfont.cpp
1032 lines
26 KiB
C++
1032 lines
26 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.
|
|
*
|
|
*/
|
|
|
|
// Since FreeType2 includes files, which contain forbidden symbols, we need to
|
|
// allow all symbols here.
|
|
#define FORBIDDEN_SYMBOL_ALLOW_ALL
|
|
|
|
#include "common/scummsys.h"
|
|
#ifdef USE_FREETYPE2
|
|
|
|
#include "graphics/fonts/ttf.h"
|
|
#include "graphics/font.h"
|
|
#include "graphics/surface.h"
|
|
|
|
#include "common/ustr.h"
|
|
#include "common/file.h"
|
|
#include "common/config-manager.h"
|
|
#include "common/singleton.h"
|
|
#include "common/stream.h"
|
|
#include "common/memstream.h"
|
|
#include "common/hashmap.h"
|
|
#include "common/ptr.h"
|
|
#include "common/unzip.h"
|
|
|
|
#include <ft2build.h>
|
|
#include FT_FREETYPE_H
|
|
#include FT_BITMAP_H
|
|
#include FT_GLYPH_H
|
|
#include FT_SFNT_NAMES_H
|
|
#include FT_TRUETYPE_IDS_H
|
|
#include FT_TRUETYPE_TABLES_H
|
|
#include FT_TRUETYPE_TAGS_H
|
|
|
|
#if (FREETYPE_MAJOR > 2 || \
|
|
(FREETYPE_MAJOR == 2 && (FREETYPE_MINOR > 3 || \
|
|
(FREETYPE_MINOR == 3 && FREETYPE_PATCH >= 8))))
|
|
// FT2.3.8+, nothing to do, FT_GlyphSlot_Own_Bitmap is in FT_BITMAP_H
|
|
#define FAKE_BOLD 2
|
|
#elif (FREETYPE_MAJOR > 2 || \
|
|
(FREETYPE_MAJOR == 2 && (FREETYPE_MINOR > 2 || \
|
|
(FREETYPE_MINOR == 2 && FREETYPE_PATCH >= 0))))
|
|
// FT2.2.0+ have FT_GlyphSlot_Own_Bitmap in FT_SYNTHESIS_H
|
|
#include FT_SYNTHESIS_H
|
|
#define FAKE_BOLD 2
|
|
#elif (FREETYPE_MAJOR > 2 || \
|
|
(FREETYPE_MAJOR == 2 && (FREETYPE_MINOR > 1 || \
|
|
(FREETYPE_MINOR == 1 && FREETYPE_PATCH >= 10))))
|
|
// FT2.1.10+ don't have FT_GlyphSlot_Own_Bitmap but they have FT_Bitmap_Embolden, do workaround
|
|
#define FAKE_BOLD 1
|
|
#else
|
|
// Older versions don't have FT_Bitmap_Embolden
|
|
#define FAKE_BOLD 0
|
|
#endif
|
|
|
|
#if FREETYPE_MAJOR > 2 || ( FREETYPE_MAJOR == 2 && FREETYPE_MINOR >= 9)
|
|
#include FT_TRUETYPE_DRIVER_H
|
|
#endif
|
|
|
|
namespace Graphics {
|
|
|
|
namespace {
|
|
|
|
inline int ftCeil26_6(FT_Pos x) {
|
|
return (x + 63) / 64;
|
|
}
|
|
|
|
inline int divRoundToNearest(int dividend, int divisor) {
|
|
return (dividend + (divisor / 2)) / divisor;
|
|
}
|
|
|
|
} // End of anonymous namespace
|
|
|
|
class TTFLibrary : public Common::Singleton<TTFLibrary> {
|
|
public:
|
|
TTFLibrary();
|
|
~TTFLibrary();
|
|
|
|
/**
|
|
* Check whether FreeType2 is initialized properly.
|
|
*/
|
|
bool isInitialized() const { return _initialized; }
|
|
|
|
bool loadFont(const uint8 *file, const int32 face_index, const uint32 size, FT_Face &face);
|
|
void closeFont(FT_Face &face);
|
|
private:
|
|
FT_Library _library;
|
|
bool _initialized;
|
|
};
|
|
|
|
void shutdownTTF() {
|
|
TTFLibrary::destroy();
|
|
}
|
|
|
|
#define g_ttf ::Graphics::TTFLibrary::instance()
|
|
|
|
TTFLibrary::TTFLibrary() : _library(), _initialized(false) {
|
|
if (!FT_Init_FreeType(&_library))
|
|
_initialized = true;
|
|
}
|
|
|
|
TTFLibrary::~TTFLibrary() {
|
|
if (_initialized) {
|
|
FT_Done_FreeType(_library);
|
|
_initialized = false;
|
|
}
|
|
}
|
|
|
|
bool TTFLibrary::loadFont(const uint8 *file, const int32 face_index, const uint32 size, FT_Face &face) {
|
|
assert(_initialized);
|
|
|
|
return (FT_New_Memory_Face(_library, file, size, face_index, &face) == 0);
|
|
}
|
|
|
|
void TTFLibrary::closeFont(FT_Face &face) {
|
|
assert(_initialized);
|
|
|
|
FT_Done_Face(face);
|
|
}
|
|
|
|
class TTFFont : public Font {
|
|
public:
|
|
TTFFont();
|
|
virtual ~TTFFont();
|
|
|
|
bool load(Common::SeekableReadStream &stream, int size, TTFSizeMode sizeMode,
|
|
uint dpi, TTFRenderMode renderMode, const uint32 *mapping, bool stemDarkening);
|
|
bool load(uint8 *ttfFile, uint32 sizeFile, int32 faceIndex, bool fakeBold, bool fakeItalic,
|
|
int size, TTFSizeMode sizeMode, uint dpi, TTFRenderMode renderMode, const uint32 *mapping, bool stemDarkening);
|
|
|
|
virtual int getFontHeight() const;
|
|
|
|
virtual int getMaxCharWidth() const;
|
|
|
|
virtual int getCharWidth(uint32 chr) const;
|
|
|
|
virtual int getKerningOffset(uint32 left, uint32 right) const;
|
|
|
|
virtual Common::Rect getBoundingBox(uint32 chr) const;
|
|
|
|
virtual void drawChar(Surface *dst, uint32 chr, int x, int y, uint32 color) const;
|
|
private:
|
|
bool _initialized;
|
|
FT_Face _face;
|
|
|
|
uint8 *_ttfFile;
|
|
uint32 _size;
|
|
|
|
int _width, _height;
|
|
int _ascent, _descent;
|
|
|
|
struct Glyph {
|
|
Surface image;
|
|
int xOffset, yOffset;
|
|
int advance;
|
|
FT_UInt slot;
|
|
};
|
|
|
|
bool cacheGlyph(Glyph &glyph, uint32 chr) const;
|
|
typedef Common::HashMap<uint32, Glyph> GlyphCache;
|
|
mutable GlyphCache _glyphs;
|
|
bool _allowLateCaching;
|
|
void assureCached(uint32 chr) const;
|
|
|
|
Common::SeekableReadStream *readTTFTable(FT_ULong tag) const;
|
|
|
|
int computePointSize(int size, TTFSizeMode sizeMode) const;
|
|
int readPointSizeFromVDMXTable(int height) const;
|
|
int computePointSizeFromHeaders(int height) const;
|
|
|
|
FT_Int32 _loadFlags;
|
|
FT_Render_Mode _renderMode;
|
|
bool _hasKerning;
|
|
|
|
bool _fakeBold;
|
|
bool _fakeItalic;
|
|
};
|
|
|
|
TTFFont::TTFFont()
|
|
: _initialized(false), _face(), _ttfFile(0), _size(0), _width(0), _height(0), _ascent(0),
|
|
_descent(0), _glyphs(), _loadFlags(FT_LOAD_TARGET_NORMAL), _renderMode(FT_RENDER_MODE_NORMAL),
|
|
_hasKerning(false), _allowLateCaching(false), _fakeBold(false), _fakeItalic(false) {
|
|
}
|
|
|
|
TTFFont::~TTFFont() {
|
|
if (_initialized) {
|
|
g_ttf.closeFont(_face);
|
|
|
|
delete[] _ttfFile;
|
|
_ttfFile = 0;
|
|
|
|
for (GlyphCache::iterator i = _glyphs.begin(), end = _glyphs.end(); i != end; ++i)
|
|
i->_value.image.free();
|
|
|
|
_initialized = false;
|
|
}
|
|
}
|
|
|
|
bool TTFFont::load(Common::SeekableReadStream &stream, int size, TTFSizeMode sizeMode,
|
|
uint dpi, TTFRenderMode renderMode, const uint32 *mapping, bool stemDarkening) {
|
|
if (!g_ttf.isInitialized())
|
|
return false;
|
|
|
|
uint32 sizeFile = stream.size();
|
|
if (!sizeFile)
|
|
return false;
|
|
|
|
uint8 *ttfFile = new uint8[sizeFile];
|
|
assert(ttfFile);
|
|
|
|
if (stream.read(ttfFile, sizeFile) != sizeFile) {
|
|
delete[] ttfFile;
|
|
return false;
|
|
}
|
|
|
|
if (!load(ttfFile, sizeFile, 0, false, false, size, sizeMode, dpi, renderMode, mapping, stemDarkening)) {
|
|
delete[] ttfFile;
|
|
return false;
|
|
}
|
|
|
|
// Don't delete ttfFile as it's now owned by the class
|
|
return true;
|
|
}
|
|
|
|
bool TTFFont::load(uint8 *ttfFile, uint32 sizeFile, int32 faceIndex, bool bold, bool italic,
|
|
int size, TTFSizeMode sizeMode, uint dpi, TTFRenderMode renderMode, const uint32 *mapping, bool stemDarkening) {
|
|
_initialized = false;
|
|
|
|
if (!g_ttf.isInitialized())
|
|
return false;
|
|
|
|
_size = sizeFile;
|
|
if (!_size)
|
|
return false;
|
|
|
|
_ttfFile = ttfFile;
|
|
assert(_ttfFile);
|
|
|
|
if (!g_ttf.loadFont(_ttfFile, faceIndex, _size, _face)) {
|
|
// Don't delete ttfFile as we return fail
|
|
_ttfFile = 0;
|
|
|
|
return false;
|
|
}
|
|
|
|
// We only support scalable fonts.
|
|
if (!FT_IS_SCALABLE(_face)) {
|
|
g_ttf.closeFont(_face);
|
|
|
|
// Don't delete ttfFile as we return fail
|
|
_ttfFile = 0;
|
|
|
|
return false;
|
|
}
|
|
if (stemDarkening) {
|
|
#if FREETYPE_MAJOR > 2 || ( FREETYPE_MAJOR == 2 && FREETYPE_MINOR >= 9)
|
|
FT_Parameter param;
|
|
param.tag = FT_PARAM_TAG_STEM_DARKENING;
|
|
param.data = &stemDarkening;
|
|
FT_Face_Properties(_face, 1, ¶m);
|
|
#else
|
|
warning("Stem darkening is not available with this version of FreeType");
|
|
#endif
|
|
}
|
|
|
|
// Check whether we have kerning support
|
|
_hasKerning = (FT_HAS_KERNING(_face) != 0);
|
|
|
|
if (FT_Set_Char_Size(_face, 0, computePointSize(size, sizeMode) * 64, dpi, dpi)) {
|
|
g_ttf.closeFont(_face);
|
|
|
|
// Don't delete ttfFile as we return fail
|
|
_ttfFile = 0;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool fontBold = ((_face->style_flags & FT_STYLE_FLAG_BOLD) != 0);
|
|
_fakeBold = bold && !fontBold;
|
|
bool fontItalic = ((_face->style_flags & FT_STYLE_FLAG_ITALIC) != 0);
|
|
_fakeItalic = italic && !fontItalic;
|
|
|
|
switch (renderMode) {
|
|
case kTTFRenderModeNormal:
|
|
_loadFlags = FT_LOAD_TARGET_NORMAL;
|
|
_renderMode = FT_RENDER_MODE_NORMAL;
|
|
break;
|
|
|
|
case kTTFRenderModeLight:
|
|
_loadFlags = FT_LOAD_TARGET_LIGHT;
|
|
_renderMode = FT_RENDER_MODE_LIGHT;
|
|
break;
|
|
|
|
case kTTFRenderModeMonochrome:
|
|
_loadFlags = FT_LOAD_TARGET_MONO;
|
|
_renderMode = FT_RENDER_MODE_MONO;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
FT_Fixed yScale = _face->size->metrics.y_scale;
|
|
_ascent = ftCeil26_6(FT_MulFix(_face->ascender, yScale));
|
|
_descent = ftCeil26_6(FT_MulFix(_face->descender, yScale));
|
|
|
|
_width = ftCeil26_6(FT_MulFix(_face->max_advance_width, _face->size->metrics.x_scale));
|
|
_height = _ascent - _descent + 1;
|
|
|
|
#if FAKE_BOLD > 0
|
|
// Width isn't modified when we can't fake bold
|
|
if (_fakeBold) {
|
|
// Embolden by 1 pixel width
|
|
_width += 1;
|
|
}
|
|
#endif
|
|
|
|
// Apply a matrix transform for all loaded glyphs
|
|
if (_fakeItalic) {
|
|
// This matrix is taken from Wine source code
|
|
// 16.16 fixed-point
|
|
FT_Matrix slantMat;
|
|
slantMat.xx = (1 << 16); // 1.
|
|
slantMat.xy = (1 << 16) >> 2; // .25
|
|
slantMat.yx = 0; // 0.
|
|
slantMat.yy = (1 << 16); // 1.
|
|
FT_Set_Transform(_face, &slantMat, nullptr);
|
|
|
|
// Don't try to load bitmap version of font as we have to transform the strokes
|
|
_loadFlags |= FT_LOAD_NO_BITMAP;
|
|
}
|
|
|
|
if (!mapping) {
|
|
// Allow loading of all unicode characters.
|
|
_allowLateCaching = true;
|
|
|
|
// Load all ISO-8859-1 characters.
|
|
for (uint i = 0; i < 256; ++i) {
|
|
if (!cacheGlyph(_glyphs[i], i)) {
|
|
_glyphs.erase(i);
|
|
}
|
|
}
|
|
} else {
|
|
// We have a fixed map of characters do not load more later.
|
|
_allowLateCaching = false;
|
|
|
|
for (uint i = 0; i < 256; ++i) {
|
|
const uint32 unicode = mapping[i] & 0x7FFFFFFF;
|
|
const bool isRequired = (mapping[i] & 0x80000000) != 0;
|
|
// Check whether loading an important glyph fails and error out if
|
|
// that is the case.
|
|
if (!cacheGlyph(_glyphs[i], unicode)) {
|
|
_glyphs.erase(i);
|
|
if (isRequired) {
|
|
g_ttf.closeFont(_face);
|
|
|
|
// Don't delete ttfFile as we return fail
|
|
_ttfFile = 0;
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_glyphs.size() == 0) {
|
|
g_ttf.closeFont(_face);
|
|
|
|
// Don't delete ttfFile as we return fail
|
|
_ttfFile = 0;
|
|
|
|
return false;
|
|
} else {
|
|
_initialized = true;
|
|
// At this point we get ownership of _ttfFile
|
|
return true;
|
|
}
|
|
}
|
|
|
|
int TTFFont::computePointSize(int size, TTFSizeMode sizeMode) const {
|
|
int ptSize = 0;
|
|
switch (sizeMode) {
|
|
case kTTFSizeModeCell: {
|
|
ptSize = readPointSizeFromVDMXTable(size);
|
|
|
|
if (ptSize == 0) {
|
|
ptSize = computePointSizeFromHeaders(size);
|
|
}
|
|
|
|
if (ptSize == 0) {
|
|
warning("Unable to compute point size for font '%s'", _face->family_name);
|
|
ptSize = 1;
|
|
}
|
|
break;
|
|
}
|
|
case kTTFSizeModeCharacter:
|
|
ptSize = size;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ptSize;
|
|
}
|
|
|
|
Common::SeekableReadStream *TTFFont::readTTFTable(FT_ULong tag) const {
|
|
// Find the required buffer size by calling the load function with nullptr
|
|
FT_ULong size = 0;
|
|
FT_Error err = FT_Load_Sfnt_Table(_face, tag, 0, nullptr, &size);
|
|
if (err) {
|
|
return nullptr;
|
|
}
|
|
|
|
byte *buf = (byte *)malloc(size);
|
|
if (!buf) {
|
|
return nullptr;
|
|
}
|
|
|
|
err = FT_Load_Sfnt_Table(_face, tag, 0, buf, &size);
|
|
if (err) {
|
|
free(buf);
|
|
return nullptr;
|
|
}
|
|
|
|
return new Common::MemoryReadStream(buf, size, DisposeAfterUse::YES);
|
|
}
|
|
|
|
int TTFFont::readPointSizeFromVDMXTable(int height) const {
|
|
// The Vertical Device Metrics table matches font heights with point sizes.
|
|
// FreeType does not expose it, we have to parse it ourselves.
|
|
// See https://www.microsoft.com/typography/otspec/vdmx.htm
|
|
|
|
Common::ScopedPtr<Common::SeekableReadStream> vdmxBuf(readTTFTable(TTAG_VDMX));
|
|
if (!vdmxBuf) {
|
|
return 0;
|
|
}
|
|
|
|
// Read the main header
|
|
vdmxBuf->skip(4); // Skip the version
|
|
uint16 numRatios = vdmxBuf->readUint16BE();
|
|
|
|
// Compute the starting position for the group table positions table
|
|
int32 offsetTableStart = vdmxBuf->pos() + 4 * numRatios;
|
|
|
|
// Search the ratio table for the 1:1 ratio, or the default record (0, 0, 0)
|
|
int32 selectedRatio = -1;
|
|
for (uint16 i = 0; i < numRatios; i++) {
|
|
vdmxBuf->skip(1); // Skip the charset subset
|
|
uint8 xRatio = vdmxBuf->readByte();
|
|
uint8 yRatio1 = vdmxBuf->readByte();
|
|
uint8 yRatio2 = vdmxBuf->readByte();
|
|
|
|
if ((xRatio == 1 && yRatio1 <= 1 && yRatio2 >= 1)
|
|
|| (xRatio == 0 && yRatio1 == 0 && yRatio2 == 0)) {
|
|
selectedRatio = i;
|
|
break;
|
|
}
|
|
}
|
|
if (selectedRatio < 0) {
|
|
return 0;
|
|
}
|
|
|
|
// Read from group table positions table to get the group table offset
|
|
vdmxBuf->seek(offsetTableStart + sizeof(uint16) * selectedRatio);
|
|
uint16 groupOffset = vdmxBuf->readUint16BE();
|
|
|
|
// Read the group table header
|
|
vdmxBuf->seek(groupOffset);
|
|
uint16 numRecords = vdmxBuf->readUint16BE();
|
|
vdmxBuf->skip(2); // Skip the table bounds
|
|
|
|
// Search a record matching the required height
|
|
for (uint16 i = 0; i < numRecords; i++) {
|
|
uint16 pointSize = vdmxBuf->readUint16BE();
|
|
int16 yMax = vdmxBuf->readSint16BE();
|
|
int16 yMin = vdmxBuf->readSint16BE();
|
|
|
|
if (yMax + -yMin > height) {
|
|
return 0;
|
|
}
|
|
if (yMax + -yMin == height) {
|
|
return pointSize;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int TTFFont::computePointSizeFromHeaders(int height) const {
|
|
TT_OS2 *os2Header = (TT_OS2 *)FT_Get_Sfnt_Table(_face, ft_sfnt_os2);
|
|
TT_HoriHeader *horiHeader = (TT_HoriHeader *)FT_Get_Sfnt_Table(_face, ft_sfnt_hhea);
|
|
|
|
if (os2Header && (os2Header->usWinAscent + os2Header->usWinDescent != 0)) {
|
|
return divRoundToNearest(_face->units_per_EM * height, os2Header->usWinAscent + os2Header->usWinDescent);
|
|
} else if (horiHeader && (horiHeader->Ascender + horiHeader->Descender != 0)) {
|
|
return divRoundToNearest(_face->units_per_EM * height, horiHeader->Ascender + horiHeader->Descender);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int TTFFont::getFontHeight() const {
|
|
return _height;
|
|
}
|
|
|
|
int TTFFont::getMaxCharWidth() const {
|
|
return _width;
|
|
}
|
|
|
|
int TTFFont::getCharWidth(uint32 chr) const {
|
|
assureCached(chr);
|
|
GlyphCache::const_iterator glyphEntry = _glyphs.find(chr);
|
|
if (glyphEntry == _glyphs.end())
|
|
return 0;
|
|
else
|
|
return glyphEntry->_value.advance;
|
|
}
|
|
|
|
int TTFFont::getKerningOffset(uint32 left, uint32 right) const {
|
|
if (!_hasKerning)
|
|
return 0;
|
|
|
|
assureCached(left);
|
|
assureCached(right);
|
|
|
|
FT_UInt leftGlyph, rightGlyph;
|
|
GlyphCache::const_iterator glyphEntry;
|
|
|
|
glyphEntry = _glyphs.find(left);
|
|
if (glyphEntry != _glyphs.end()) {
|
|
leftGlyph = glyphEntry->_value.slot;
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
glyphEntry = _glyphs.find(right);
|
|
if (glyphEntry != _glyphs.end()) {
|
|
rightGlyph = glyphEntry->_value.slot;
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
if (!leftGlyph || !rightGlyph)
|
|
return 0;
|
|
|
|
FT_Vector kerningVector;
|
|
FT_Get_Kerning(_face, leftGlyph, rightGlyph, FT_KERNING_DEFAULT, &kerningVector);
|
|
return (kerningVector.x / 64);
|
|
}
|
|
|
|
Common::Rect TTFFont::getBoundingBox(uint32 chr) const {
|
|
assureCached(chr);
|
|
GlyphCache::const_iterator glyphEntry = _glyphs.find(chr);
|
|
if (glyphEntry == _glyphs.end()) {
|
|
return Common::Rect();
|
|
} else {
|
|
const int xOffset = glyphEntry->_value.xOffset;
|
|
const int yOffset = glyphEntry->_value.yOffset;
|
|
const Graphics::Surface &image = glyphEntry->_value.image;
|
|
return Common::Rect(xOffset, yOffset, xOffset + image.w, yOffset + image.h);
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
template<typename ColorType>
|
|
static void renderGlyph(uint8 *dstPos, const int dstPitch, const uint8 *srcPos, const int srcPitch, const int w, const int h, ColorType color, const PixelFormat &dstFormat) {
|
|
uint8 sR, sG, sB;
|
|
dstFormat.colorToRGB(color, sR, sG, sB);
|
|
|
|
for (int y = 0; y < h; ++y) {
|
|
ColorType *rDst = (ColorType *)dstPos;
|
|
const uint8 *src = srcPos;
|
|
|
|
for (int x = 0; x < w; ++x) {
|
|
if (*src == 255) {
|
|
*rDst = color;
|
|
} else if (*src) {
|
|
const uint8 a = *src;
|
|
|
|
uint8 dR, dG, dB;
|
|
dstFormat.colorToRGB(*rDst, dR, dG, dB);
|
|
|
|
dR = ((255 - a) * dR + a * sR) / 255;
|
|
dG = ((255 - a) * dG + a * sG) / 255;
|
|
dB = ((255 - a) * dB + a * sB) / 255;
|
|
|
|
*rDst = dstFormat.RGBToColor(dR, dG, dB);
|
|
}
|
|
|
|
++rDst;
|
|
++src;
|
|
}
|
|
|
|
dstPos += dstPitch;
|
|
srcPos += srcPitch;
|
|
}
|
|
}
|
|
|
|
} // End of anonymous namespace
|
|
|
|
void TTFFont::drawChar(Surface *dst, uint32 chr, int x, int y, uint32 color) const {
|
|
assureCached(chr);
|
|
GlyphCache::const_iterator glyphEntry = _glyphs.find(chr);
|
|
if (glyphEntry == _glyphs.end())
|
|
return;
|
|
|
|
const Glyph &glyph = glyphEntry->_value;
|
|
|
|
x += glyph.xOffset;
|
|
y += glyph.yOffset;
|
|
|
|
if (x > dst->w)
|
|
return;
|
|
if (y > dst->h)
|
|
return;
|
|
|
|
int w = glyph.image.w;
|
|
int h = glyph.image.h;
|
|
|
|
const uint8 *srcPos = (const uint8 *)glyph.image.getPixels();
|
|
|
|
// Make sure we are not drawing outside the screen bounds
|
|
if (x < 0) {
|
|
srcPos -= x;
|
|
w += x;
|
|
x = 0;
|
|
}
|
|
|
|
if (x + w > dst->w)
|
|
w = dst->w - x;
|
|
|
|
if (w <= 0)
|
|
return;
|
|
|
|
if (y < 0) {
|
|
srcPos -= y * glyph.image.pitch;
|
|
h += y;
|
|
y = 0;
|
|
}
|
|
|
|
if (y + h > dst->h)
|
|
h = dst->h - y;
|
|
|
|
if (h <= 0)
|
|
return;
|
|
|
|
uint8 *dstPos = (uint8 *)dst->getBasePtr(x, y);
|
|
|
|
if (dst->format.bytesPerPixel == 1) {
|
|
for (int cy = 0; cy < h; ++cy) {
|
|
uint8 *rDst = dstPos;
|
|
const uint8 *src = srcPos;
|
|
|
|
for (int cx = 0; cx < w; ++cx) {
|
|
// We assume a 1Bpp mode is a color indexed mode, thus we can
|
|
// not take advantage of anti-aliasing here.
|
|
if (*src >= 0x80)
|
|
*rDst = color;
|
|
|
|
++rDst;
|
|
++src;
|
|
}
|
|
|
|
dstPos += dst->pitch;
|
|
srcPos += glyph.image.pitch;
|
|
}
|
|
} else if (dst->format.bytesPerPixel == 2) {
|
|
renderGlyph<uint16>(dstPos, dst->pitch, srcPos, glyph.image.pitch, w, h, color, dst->format);
|
|
} else if (dst->format.bytesPerPixel == 4) {
|
|
renderGlyph<uint32>(dstPos, dst->pitch, srcPos, glyph.image.pitch, w, h, color, dst->format);
|
|
}
|
|
}
|
|
|
|
bool TTFFont::cacheGlyph(Glyph &glyph, uint32 chr) const {
|
|
FT_UInt slot = FT_Get_Char_Index(_face, chr);
|
|
if (!slot)
|
|
return false;
|
|
|
|
glyph.slot = slot;
|
|
|
|
// We use the light target and render mode to improve the looks of the
|
|
// glyphs. It is most noticable in FreeSansBold.ttf, where otherwise the
|
|
// 't' glyph looks like it is cut off on the right side.
|
|
if (FT_Load_Glyph(_face, slot, _loadFlags))
|
|
return false;
|
|
|
|
if (FT_Render_Glyph(_face->glyph, _renderMode))
|
|
return false;
|
|
|
|
if (_face->glyph->format != FT_GLYPH_FORMAT_BITMAP)
|
|
return false;
|
|
|
|
glyph.xOffset = _face->glyph->bitmap_left;
|
|
glyph.yOffset = _ascent - _face->glyph->bitmap_top;
|
|
|
|
glyph.advance = ftCeil26_6(_face->glyph->advance.x);
|
|
|
|
const FT_Bitmap *bitmap;
|
|
#if FAKE_BOLD == 1
|
|
FT_Bitmap ownBitmap;
|
|
#endif
|
|
|
|
if (_fakeBold) {
|
|
#if FAKE_BOLD >= 2
|
|
// Embolden by 1 pixel in x and 0 in y
|
|
glyph.advance += 1;
|
|
|
|
if (FT_GlyphSlot_Own_Bitmap(_face->glyph))
|
|
return false;
|
|
|
|
// That's 26.6 fixed-point units
|
|
if (FT_Bitmap_Embolden(_face->glyph->library, &_face->glyph->bitmap, 1 << 6, 0))
|
|
return false;
|
|
|
|
bitmap = &_face->glyph->bitmap;
|
|
#elif FAKE_BOLD >= 1
|
|
FT_Bitmap_New(&ownBitmap);
|
|
|
|
if (FT_Bitmap_Copy(_face->glyph->library, &_face->glyph->bitmap, &ownBitmap))
|
|
return false;
|
|
|
|
// Embolden by 1 pixel in x and 0 in y
|
|
glyph.advance += 1;
|
|
|
|
// That's 26.6 fixed-point units
|
|
if (FT_Bitmap_Embolden(_face->glyph->library, &ownBitmap, 1 << 6, 0))
|
|
return false;
|
|
|
|
bitmap = &ownBitmap;
|
|
#else
|
|
// Can't do anything, just don't fake bold
|
|
bitmap = &_face->glyph->bitmap;
|
|
#endif
|
|
} else {
|
|
bitmap = &_face->glyph->bitmap;
|
|
}
|
|
|
|
|
|
glyph.image.create(bitmap->width, bitmap->rows, PixelFormat::createFormatCLUT8());
|
|
|
|
const uint8 *src = bitmap->buffer;
|
|
int srcPitch = bitmap->pitch;
|
|
if (srcPitch < 0) {
|
|
src += (bitmap->rows - 1) * srcPitch;
|
|
srcPitch = -srcPitch;
|
|
}
|
|
|
|
uint8 *dst = (uint8 *)glyph.image.getPixels();
|
|
memset(dst, 0, glyph.image.h * glyph.image.pitch);
|
|
|
|
switch (bitmap->pixel_mode) {
|
|
case FT_PIXEL_MODE_MONO:
|
|
for (int y = 0; y < (int)bitmap->rows; ++y) {
|
|
const uint8 *curSrc = src;
|
|
uint8 mask = 0;
|
|
|
|
for (int x = 0; x < (int)bitmap->width; ++x) {
|
|
if ((x % 8) == 0)
|
|
mask = *curSrc++;
|
|
|
|
if (mask & 0x80)
|
|
*dst = 255;
|
|
|
|
mask <<= 1;
|
|
++dst;
|
|
}
|
|
|
|
src += srcPitch;
|
|
}
|
|
break;
|
|
|
|
case FT_PIXEL_MODE_GRAY:
|
|
for (int y = 0; y < (int)bitmap->rows; ++y) {
|
|
memcpy(dst, src, bitmap->width);
|
|
dst += glyph.image.pitch;
|
|
src += srcPitch;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
warning("TTFFont::cacheGlyph: Unsupported pixel mode %d", bitmap->pixel_mode);
|
|
glyph.image.free();
|
|
return false;
|
|
}
|
|
|
|
#if FAKE_BOLD == 1
|
|
if (_fakeBold) {
|
|
FT_Bitmap_Done(_face->glyph->library, &ownBitmap);
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
void TTFFont::assureCached(uint32 chr) const {
|
|
if (!chr || !_allowLateCaching || _glyphs.contains(chr)) {
|
|
return;
|
|
}
|
|
|
|
Glyph newGlyph;
|
|
if (cacheGlyph(newGlyph, chr)) {
|
|
_glyphs[chr] = newGlyph;
|
|
}
|
|
}
|
|
|
|
Font *loadTTFFont(Common::SeekableReadStream &stream, int size, TTFSizeMode sizeMode, uint dpi, TTFRenderMode renderMode, const uint32 *mapping, bool stemDarkening) {
|
|
TTFFont *font = new TTFFont();
|
|
|
|
if (!font->load(stream, size, sizeMode, dpi, renderMode, mapping, stemDarkening)) {
|
|
delete font;
|
|
return 0;
|
|
}
|
|
|
|
return font;
|
|
}
|
|
|
|
Font *loadTTFFontFromArchive(const Common::String &filename, int size, TTFSizeMode sizeMode, uint dpi, TTFRenderMode renderMode, const uint32 *mapping) {
|
|
Common::SeekableReadStream *archiveStream = nullptr;
|
|
if (ConfMan.hasKey("extrapath")) {
|
|
Common::FSDirectory extrapath(ConfMan.get("extrapath"));
|
|
archiveStream = extrapath.createReadStreamForMember("fonts.dat");
|
|
}
|
|
|
|
if (!archiveStream) {
|
|
archiveStream = SearchMan.createReadStreamForMember("fonts.dat");
|
|
}
|
|
|
|
Common::Archive *archive = Common::makeZipArchive(archiveStream);
|
|
if (!archive) {
|
|
return nullptr;
|
|
}
|
|
|
|
Common::File f;
|
|
if (!f.open(filename, *archive)) {
|
|
return nullptr;
|
|
}
|
|
|
|
Font *font = loadTTFFont(f, size, sizeMode, dpi, renderMode, mapping);
|
|
|
|
delete archive;
|
|
return font;
|
|
}
|
|
|
|
static bool matchFaceName(const Common::U32String &faceName, const FT_Face &face) {
|
|
if (faceName == Common::U32String(face->family_name)) {
|
|
// International name in ASCII match
|
|
return true;
|
|
}
|
|
|
|
// Try to match with localized name
|
|
// Loosely copied from freetype2-demos
|
|
FT_SfntName aname;
|
|
FT_UInt num_strings = FT_Get_Sfnt_Name_Count(face);
|
|
for (FT_UInt j = 0; j < num_strings; j++) {
|
|
if (FT_Get_Sfnt_Name(face, j, &aname)) {
|
|
continue;
|
|
}
|
|
if (aname.name_id != TT_NAME_ID_FONT_FAMILY) {
|
|
continue;
|
|
}
|
|
|
|
if (aname.platform_id == TT_PLATFORM_MICROSOFT &&
|
|
aname.language_id != TT_MS_LANGID_ENGLISH_UNITED_STATES) {
|
|
if (aname.encoding_id == TT_MS_ID_SYMBOL_CS ||
|
|
aname.encoding_id == TT_MS_ID_UNICODE_CS) {
|
|
// MS local name in UTF-16
|
|
Common::U32String localName = Common::U32String::decodeUTF16BE((uint16 *) aname.string, aname.string_len);
|
|
|
|
if (faceName == localName) {
|
|
return true;
|
|
}
|
|
} else {
|
|
// No conversion
|
|
if (faceName == Common::U32String((char *)aname.string, aname.string_len)) {
|
|
return true;
|
|
}
|
|
}
|
|
} else if (aname.platform_id == TT_PLATFORM_MACINTOSH &&
|
|
aname.language_id != TT_MAC_LANGID_ENGLISH) {
|
|
// No conversion
|
|
if (faceName == Common::U32String((char *)aname.string, aname.string_len)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Font *findTTFace(const Common::Array<Common::String> &files, const Common::U32String &faceName,
|
|
bool bold, bool italic, int size, uint dpi, TTFRenderMode renderMode, const uint32 *mapping) {
|
|
if (!g_ttf.isInitialized())
|
|
return nullptr;
|
|
|
|
uint8 *bestTTFFile = nullptr;
|
|
uint32 bestSize = 0;
|
|
uint32 bestFaceId = (uint32) -1;
|
|
uint32 bestPenalty = (uint32) -1;
|
|
|
|
for (Common::Array<Common::String>::const_iterator it = files.begin(); it != files.end(); it++) {
|
|
Common::File ttf;
|
|
if (!ttf.open(*it)) {
|
|
continue;
|
|
}
|
|
uint32 sizeFile = ttf.size();
|
|
if (!sizeFile) {
|
|
continue;
|
|
}
|
|
uint8 *ttfFile = new uint8[sizeFile];
|
|
assert(ttfFile);
|
|
|
|
if (ttf.read(ttfFile, sizeFile) != sizeFile) {
|
|
delete[] ttfFile;
|
|
ttfFile = 0;
|
|
|
|
continue;
|
|
}
|
|
|
|
ttf.close();
|
|
|
|
FT_Face face;
|
|
|
|
// Load face index -1 to get the count
|
|
if (!g_ttf.loadFont(ttfFile, -1, sizeFile, face)) {
|
|
delete[] ttfFile;
|
|
ttfFile = 0;
|
|
|
|
continue;
|
|
}
|
|
|
|
FT_Long num_faces = face->num_faces;
|
|
|
|
g_ttf.closeFont(face);
|
|
|
|
for (FT_Long i = 0; i < num_faces; i++) {
|
|
if (!g_ttf.loadFont(ttfFile, i, sizeFile, face)) {
|
|
continue;
|
|
}
|
|
|
|
if (!matchFaceName(faceName, face)) {
|
|
// No match on names: we don't do like Windows, we don't take a random font
|
|
g_ttf.closeFont(face);
|
|
continue;
|
|
}
|
|
|
|
bool fontBold = ((face->style_flags & FT_STYLE_FLAG_BOLD) != 0);
|
|
bool fontItalic = ((face->style_flags & FT_STYLE_FLAG_ITALIC) != 0);
|
|
|
|
g_ttf.closeFont(face);
|
|
|
|
// These scores are taken from Microsoft docs (table 1):
|
|
// https://docs.microsoft.com/en-us/previous-versions/ms969909(v=msdn.10)
|
|
uint32 penalty = 0;
|
|
if (italic != fontItalic) {
|
|
penalty += 4;
|
|
}
|
|
if (bold != fontBold) {
|
|
penalty += 120;
|
|
}
|
|
if (penalty < bestPenalty) {
|
|
// Better font
|
|
// Cleanup old best font if it's not the same file as the current one
|
|
if (bestTTFFile != ttfFile) {
|
|
delete [] bestTTFFile;
|
|
}
|
|
|
|
bestPenalty = penalty;
|
|
bestTTFFile = ttfFile;
|
|
bestFaceId = i;
|
|
bestSize = sizeFile;
|
|
}
|
|
}
|
|
|
|
// Don't free the file if it has been elected the best
|
|
if (bestTTFFile != ttfFile) {
|
|
delete [] ttfFile;
|
|
}
|
|
ttfFile = nullptr;
|
|
}
|
|
|
|
if (!bestTTFFile) {
|
|
return nullptr;
|
|
}
|
|
|
|
TTFFont *font = new TTFFont();
|
|
|
|
TTFSizeMode sizeMode = kTTFSizeModeCell;
|
|
if (size < 0) {
|
|
size = -size;
|
|
sizeMode = kTTFSizeModeCharacter;
|
|
}
|
|
if (dpi == 0) {
|
|
dpi = 96;
|
|
}
|
|
|
|
if (!font->load(bestTTFFile, bestSize, bestFaceId, bold, italic, size, sizeMode,
|
|
dpi, renderMode, mapping, false)) {
|
|
delete font;
|
|
delete [] bestTTFFile;
|
|
return nullptr;
|
|
}
|
|
|
|
return font;
|
|
}
|
|
|
|
} // End of namespace Graphics
|
|
|
|
namespace Common {
|
|
DECLARE_SINGLETON(Graphics::TTFLibrary);
|
|
} // End of namespace Common
|
|
|
|
#endif
|
|
|