From 2d86f6da9c0ee2ffa80a2b570c148ea337b09cec Mon Sep 17 00:00:00 2001 From: Bastien Bouclet Date: Sun, 22 Nov 2015 14:18:14 +0100 Subject: GRAPHICS: Introduce a size mode for TrueType fonts Allows to match Windows font size selection by converting font heights to point sizes using the TrueType tables. --- graphics/fonts/ttf.cpp | 144 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 139 insertions(+), 5 deletions(-) (limited to 'graphics/fonts/ttf.cpp') diff --git a/graphics/fonts/ttf.cpp b/graphics/fonts/ttf.cpp index dc7335f1c2..98420f6dfb 100644 --- a/graphics/fonts/ttf.cpp +++ b/graphics/fonts/ttf.cpp @@ -33,11 +33,15 @@ #include "common/singleton.h" #include "common/stream.h" +#include "common/memstream.h" #include "common/hashmap.h" +#include "common/ptr.h" #include #include FT_FREETYPE_H #include FT_GLYPH_H +#include FT_TRUETYPE_TABLES_H +#include FT_TRUETYPE_TAGS_H namespace Graphics { @@ -47,6 +51,10 @@ 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 { @@ -101,7 +109,7 @@ public: TTFFont(); virtual ~TTFFont(); - bool load(Common::SeekableReadStream &stream, int size, uint dpi, TTFRenderMode renderMode, const uint32 *mapping); + bool load(Common::SeekableReadStream &stream, int size, TTFSizeMode sizeMode, uint dpi, TTFRenderMode renderMode, const uint32 *mapping); virtual int getFontHeight() const; @@ -137,6 +145,12 @@ private: 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; @@ -162,7 +176,7 @@ TTFFont::~TTFFont() { } } -bool TTFFont::load(Common::SeekableReadStream &stream, int size, uint dpi, TTFRenderMode renderMode, const uint32 *mapping) { +bool TTFFont::load(Common::SeekableReadStream &stream, int size, TTFSizeMode sizeMode, uint dpi, TTFRenderMode renderMode, const uint32 *mapping) { if (!g_ttf.isInitialized()) return false; @@ -200,7 +214,7 @@ bool TTFFont::load(Common::SeekableReadStream &stream, int size, uint dpi, TTFRe // Check whether we have kerning support _hasKerning = (FT_HAS_KERNING(_face) != 0); - if (FT_Set_Char_Size(_face, 0, size * 64, dpi, dpi)) { + if (FT_Set_Char_Size(_face, 0, computePointSize(size, sizeMode) * 64, dpi, dpi)) { delete[] _ttfFile; _ttfFile = 0; @@ -262,6 +276,126 @@ bool TTFFont::load(Common::SeekableReadStream &stream, int size, uint dpi, TTFRe return _initialized; } +int TTFFont::computePointSize(int size, TTFSizeMode sizeMode) const { + int ptSize; + 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; + } + + 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 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; } @@ -521,10 +655,10 @@ void TTFFont::assureCached(uint32 chr) const { } } -Font *loadTTFFont(Common::SeekableReadStream &stream, int size, uint dpi, TTFRenderMode renderMode, const uint32 *mapping) { +Font *loadTTFFont(Common::SeekableReadStream &stream, int size, TTFSizeMode sizeMode, uint dpi, TTFRenderMode renderMode, const uint32 *mapping) { TTFFont *font = new TTFFont(); - if (!font->load(stream, size, dpi, renderMode, mapping)) { + if (!font->load(stream, size, sizeMode, dpi, renderMode, mapping)) { delete font; return 0; } -- cgit v1.2.3