[Scummvm-git-logs] scummvm master -> ee1aa8106314371594a8ea4ea014f1e136c8d299

lephilousophe lephilousophe at users.noreply.github.com
Sat Mar 14 11:19:25 UTC 2020


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
ee1aa81063 GRAPHICS: Mimic CreateFont Windows API


Commit: ee1aa8106314371594a8ea4ea014f1e136c8d299
    https://github.com/scummvm/scummvm/commit/ee1aa8106314371594a8ea4ea014f1e136c8d299
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2020-03-14T12:19:21+01:00

Commit Message:
GRAPHICS: Mimic CreateFont Windows API

Implement a kind of font mapper which takes a list of font files, font
face name, size, slant and weight and finds the best matching face.
Fakes italic and bold if there it isn't available.
This commit adds support for TTC files which contains multiple faces
(used by CJK Windows fonts).

Changed paths:
    graphics/fonts/ttf.cpp
    graphics/fonts/ttf.h


diff --git a/graphics/fonts/ttf.cpp b/graphics/fonts/ttf.cpp
index 8c75aaa831..8c8b0d5ff9 100644
--- a/graphics/fonts/ttf.cpp
+++ b/graphics/fonts/ttf.cpp
@@ -31,6 +31,7 @@
 #include "graphics/font.h"
 #include "graphics/surface.h"
 
+#include "common/encoding.h"
 #include "common/file.h"
 #include "common/config-manager.h"
 #include "common/singleton.h"
@@ -42,7 +43,10 @@
 
 #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
 
@@ -70,7 +74,7 @@ public:
 	 */
 	bool isInitialized() const { return _initialized; }
 
-	bool loadFont(const uint8 *file, const uint32 size, FT_Face &face);
+	bool loadFont(const uint8 *file, const int32 face_index, const uint32 size, FT_Face &face);
 	void closeFont(FT_Face &face);
 private:
 	FT_Library _library;
@@ -95,10 +99,10 @@ TTFLibrary::~TTFLibrary() {
 	}
 }
 
-bool TTFLibrary::loadFont(const uint8 *file, const uint32 size, FT_Face &face) {
+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, 0, &face) == 0);
+	return (FT_New_Memory_Face(_library, file, size, face_index, &face) == 0);
 }
 
 void TTFLibrary::closeFont(FT_Face &face) {
@@ -112,7 +116,10 @@ public:
 	TTFFont();
 	virtual ~TTFFont();
 
-	bool load(Common::SeekableReadStream &stream, int size, TTFSizeMode sizeMode, uint dpi, TTFRenderMode renderMode, const uint32 *mapping);
+	bool load(Common::SeekableReadStream &stream, int size, TTFSizeMode sizeMode,
+	          uint dpi, TTFRenderMode renderMode, const uint32 *mapping);
+	bool load(uint8 *ttfFile, uint32 sizeFile, int faceIndex, bool fakeBold, bool fakeItalic,
+	          int size, TTFSizeMode sizeMode, uint dpi, TTFRenderMode renderMode, const uint32 *mapping);
 
 	virtual int getFontHeight() const;
 
@@ -157,12 +164,15 @@ private:
 	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) {
+      _hasKerning(false), _allowLateCaching(false), _fakeBold(false), _fakeItalic(false) {
 }
 
 TTFFont::~TTFFont() {
@@ -179,26 +189,48 @@ TTFFont::~TTFFont() {
 	}
 }
 
-bool TTFFont::load(Common::SeekableReadStream &stream, int size, TTFSizeMode sizeMode, 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;
 
-	_size = stream.size();
-	if (!_size)
+	uint32 sizeFile = stream.size();
+	if (!sizeFile)
 		return false;
 
-	_ttfFile = new uint8[_size];
-	assert(_ttfFile);
+	uint8 *ttfFile = new uint8[sizeFile];
+	assert(ttfFile);
 
-	if (stream.read(_ttfFile, _size) != _size) {
-		delete[] _ttfFile;
-		_ttfFile = 0;
+	if (stream.read(ttfFile, sizeFile) != sizeFile) {
+		delete[] ttfFile;
+		return false;
+	}
 
+	if (!load(ttfFile, sizeFile, 0, false, false, size, sizeMode, dpi, renderMode, mapping)) {
+		delete[] ttfFile;
 		return false;
 	}
 
-	if (!g_ttf.loadFont(_ttfFile, _size, _face)) {
-		delete[] _ttfFile;
+	// 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) {
+	_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;
@@ -206,11 +238,11 @@ bool TTFFont::load(Common::SeekableReadStream &stream, int size, TTFSizeMode siz
 
 	// We only support scalable fonts.
 	if (!FT_IS_SCALABLE(_face)) {
-		delete[] _ttfFile;
-		_ttfFile = 0;
-
 		g_ttf.closeFont(_face);
 
+		// Don't delete ttfFile as we return fail
+		_ttfFile = 0;
+
 		return false;
 	}
 
@@ -218,12 +250,19 @@ bool TTFFont::load(Common::SeekableReadStream &stream, int size, TTFSizeMode siz
 	_hasKerning = (FT_HAS_KERNING(_face) != 0);
 
 	if (FT_Set_Char_Size(_face, 0, computePointSize(size, sizeMode) * 64, dpi, dpi)) {
-		delete[] _ttfFile;
+		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;
@@ -251,6 +290,26 @@ bool TTFFont::load(Common::SeekableReadStream &stream, int size, TTFSizeMode siz
 	_width = ftCeil26_6(FT_MulFix(_face->max_advance_width, _face->size->metrics.x_scale));
 	_height = _ascent - _descent + 1;
 
+	if (_fakeBold) {
+		// Embolden by 1 pixel width
+		_width += 1;
+	}
+
+	// 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;
@@ -272,14 +331,30 @@ bool TTFFont::load(Common::SeekableReadStream &stream, int size, TTFSizeMode siz
 			// that is the case.
 			if (!cacheGlyph(_glyphs[i], unicode)) {
 				_glyphs.erase(i);
-				if (isRequired)
+				if (isRequired) {
+					g_ttf.closeFont(_face);
+
+					// Don't delete ttfFile as we return fail
+					_ttfFile = 0;
+
 					return false;
+				}
 			}
 		}
 	}
 
-	_initialized = (_glyphs.size() != 0);
-	return _initialized;
+	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 {
@@ -469,7 +544,7 @@ Common::Rect TTFFont::getBoundingBox(uint32 chr) const {
 namespace {
 
 template<typename ColorType>
-void renderGlyph(uint8 *dstPos, const int dstPitch, const uint8 *srcPos, const int srcPitch, const int w, const int h, ColorType color, const PixelFormat &dstFormat) {
+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);
 
@@ -601,7 +676,20 @@ bool TTFFont::cacheGlyph(Glyph &glyph, uint32 chr) const {
 
 	glyph.advance = ftCeil26_6(_face->glyph->advance.x);
 
+	if (_fakeBold) {
+		// 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;
+	}
+
 	const FT_Bitmap &bitmap = _face->glyph->bitmap;
+
 	glyph.image.create(bitmap.width, bitmap.rows, PixelFormat::createFormatCLUT8());
 
 	const uint8 *src = bitmap.buffer;
@@ -701,6 +789,169 @@ Font *loadTTFFontFromArchive(const Common::String &filename, int size, TTFSizeMo
 	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
+				char *u32 = Common::Encoding::convert("utf-32", "utf-16be", (char *)aname.string, aname.string_len);
+				Common::U32String localName((uint32 *)u32);
+				free(u32);
+
+				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)) {
+		delete font;
+		delete [] bestTTFFile;
+		return nullptr;
+	}
+
+	return font;
+}
+
 } // End of namespace Graphics
 
 namespace Common {
diff --git a/graphics/fonts/ttf.h b/graphics/fonts/ttf.h
index 26a93a163f..712ac18865 100644
--- a/graphics/fonts/ttf.h
+++ b/graphics/fonts/ttf.h
@@ -27,7 +27,9 @@
 
 #ifdef USE_FREETYPE2
 
+#include "common/array.h"
 #include "common/stream.h"
+#include "common/ustr.h"
 
 namespace Graphics {
 
@@ -114,6 +116,29 @@ Font *loadTTFFont(Common::SeekableReadStream &stream, int size, TTFSizeMode size
  */
 Font *loadTTFFontFromArchive(const Common::String &filename, int size, TTFSizeMode sizeMode = kTTFSizeModeCharacter, uint dpi = 0, TTFRenderMode renderMode = kTTFRenderModeLight, const uint32 *mapping = 0);
 
+/**
+ * Finds the specified face in a collection of TTF/TTC font files.
+ * This functions kinds of mimic CreateFont from Windows API.
+ *
+ * @param files      List of files where to find specified face.
+ * @param faceName   The face name to search for.
+ * @param bold       The weight of the desired face.
+ * @param italic     The slant of the desired face.
+ * @param size       The point size to load.
+ * @param sizeMode   The point size definition used for the size parameter.
+ * @param dpi        The dpi to use for size calculations, by default 96dpi
+ *                   are used. Like in Windows.
+ * @param renderMode FreeType2 mode used to render glyphs. @see TTFRenderMode
+ * @param mapping    A mapping from code points 0-255 into UTF-32 code points.
+ *                   This can be used to support various 8bit character sets.
+ *                   In case the msb of the UTF-32 code point is set the font
+ *                   loading fails in case no glyph for it is found. When this
+ *                   is non-null only characters given in the mapping are
+ *                   supported.
+ * @return 0 in case loading fails, otherwise a pointer to the Font object.
+ */
+Font *findTTFace(const Common::Array<Common::String> &files, const Common::U32String &faceName, bool bold, bool italic, int size, uint dpi = 0, TTFRenderMode renderMode = kTTFRenderModeLight, const uint32 *mapping = 0);
+
 void shutdownTTF();
 
 } // End of namespace Graphics




More information about the Scummvm-git-logs mailing list