TTFRenderer::TTFRenderer(Common::SeekableReadStream &ttfFile, int height) : _library(0), _face(0), _fileBuffer(0), _width(0), _height(0), _ascent(0), _descent(0) { // Initialize a library object of FreeType2 if (FT_Init_FreeType(&_library)) throw Common::Exception("TTFRenderer: Could not init freetype2"); const uint32 size = ttfFile.size(); _fileBuffer = new uint8[size]; if (!_fileBuffer) { FT_Done_FreeType(_library); throw Common::Exception("TTFRenderer: Out of memory"); } if (ttfFile.read(_fileBuffer, size) != size) { FT_Done_FreeType(_library); delete[] _fileBuffer; throw Common::kReadError; } if (FT_New_Memory_Face(_library, _fileBuffer, size, 0, &_face)) { FT_Done_FreeType(_library); delete[] _fileBuffer; throw Common::Exception("TTFRenderer: Could not load font file"); } // We only support scalable fonts. if (!FT_IS_SCALABLE(_face)) { FT_Done_Face(_face); FT_Done_FreeType(_library); delete[] _fileBuffer; throw Common::Exception("TTFRenderer: Font is not scalable"); } // Set the font height if (FT_Set_Char_Size(_face, 0, height * 64, 0, 0)) { FT_Done_Face(_face); FT_Done_FreeType(_library); delete[] _fileBuffer; throw Common::Exception("TTFRenderer: Setting height to %d failed", height); } 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; }
bool TrueTypeFont::setSize(int height) { if (FT_Set_Char_Size(_sjisFont, 0, height * 64, 0, 0)) { warning("Could not initialize font for height %d", height); return false; } FT_Fixed yScale = _sjisFont->size->metrics.y_scale; _ascent = ftCeil26_6(FT_MulFix(_sjisFont->ascender, yScale)); _descent = ftCeil26_6(FT_MulFix(_sjisFont->descender, yScale)); _width = ftCeil26_6(FT_MulFix(_sjisFont->max_advance_width, _sjisFont->size->metrics.x_scale)); _height = _ascent - _descent + 1; return true; }
void TTFRenderer::getFaceMetrics(int &advance, int &yOffset, int &xMin) const { FT_Glyph_Metrics &metrics = _face->glyph->metrics; xMin = ftFloor26_6(metrics.horiBearingX); yOffset = _ascent - ftFloor26_6(metrics.horiBearingY); advance = ftCeil26_6(metrics.horiAdvance); }
void TTFRenderer::getFaceMetrics(int &advance, int &yOffset, int &xMin) const { FT_Glyph_Metrics &metrics = _face->glyph->metrics; xMin = ftFloor26_6(metrics.horiBearingX); int xMax = xMin + ftCeil26_6(metrics.width); yOffset = _ascent - ftFloor26_6(metrics.horiBearingY); advance = ftCeil26_6(metrics.horiAdvance); // In case we got a negative xMin we adjust that, this might make some // characters look a bit odd, but it's the only way we can ensure no // invalid memory gets written to with the current font API if (xMin < 0) { xMax -= xMin; xMin = 0; if (xMax > advance) advance = xMax; } }
bool TTFFont::cacheGlyph(Glyph &glyph, FT_UInt &slot, uint chr) { slot = FT_Get_Char_Index(_face, chr); if (!slot) return false; // 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, (_monochrome ? FT_LOAD_TARGET_MONO : FT_LOAD_TARGET_LIGHT))) return false; if (FT_Render_Glyph(_face->glyph, (_monochrome ? FT_RENDER_MODE_MONO : FT_RENDER_MODE_LIGHT))) return false; if (_face->glyph->format != FT_GLYPH_FORMAT_BITMAP) return false; FT_Glyph_Metrics &metrics = _face->glyph->metrics; glyph.xOffset = _face->glyph->bitmap_left; int xMax = glyph.xOffset + ftCeil26_6(metrics.width); glyph.yOffset = _ascent - _face->glyph->bitmap_top; glyph.advance = ftCeil26_6(_face->glyph->advance.x); // In case we got a negative xMin we adjust that, this might make some // characters make a bit odd, but it's the only way we can assure no // invalid memory writes with the current font API if (glyph.xOffset < 0) { xMax -= glyph.xOffset; glyph.xOffset = 0; if (xMax > glyph.advance) glyph.advance = xMax; } const FT_Bitmap &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.getBasePtr(0, 0); memset(dst, 0, glyph.image.h * glyph.image.pitch); switch (bitmap.pixel_mode) { case FT_PIXEL_MODE_MONO: for (int y = 0; y < bitmap.rows; ++y) { const uint8 *curSrc = src; uint8 mask = 0; for (int x = 0; x < 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 < 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); return false; } return true; }
bool TTFFont::load(Common::SeekableReadStream &stream, int size, bool monochrome, const uint32 *mapping) { if (!g_ttf.isInitialized()) return false; _size = stream.size(); if (!_size) return false; _ttfFile = new uint8[_size]; assert(_ttfFile); if (stream.read(_ttfFile, _size) != _size) { delete[] _ttfFile; _ttfFile = 0; return false; } if (!g_ttf.loadFont(_ttfFile, _size, _face)) { delete[] _ttfFile; _ttfFile = 0; return false; } // We only support scalable fonts. if (!FT_IS_SCALABLE(_face)) { delete[] _ttfFile; _ttfFile = 0; g_ttf.closeFont(_face); return false; } // Check whether we have kerning support _hasKerning = (FT_HAS_KERNING(_face) != 0); if (FT_Set_Char_Size(_face, 0, size * 64, 0, 0)) { delete[] _ttfFile; _ttfFile = 0; return false; } _monochrome = monochrome; 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 (!mapping) { // Load all ISO-8859-1 characters. for (uint i = 0; i < 256; ++i) { if (!cacheGlyph(_glyphs[i], _glyphSlots[i], i)) _glyphSlots[i] = 0; } } else { 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], _glyphSlots[i], unicode)) { _glyphSlots[i] = 0; if (isRequired) return false; } } } _initialized = (_glyphs.size() != 0); return _initialized; }
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 = _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 (uint y = 0; y < bitmap.rows; ++y) { const uint8 *curSrc = src; uint8 mask = 0; for (uint x = 0; x < 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 (uint y = 0; y < 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; } return true; }
bool TTFFont::load(Common::SeekableReadStream &stream, int size, uint dpi, TTFRenderMode renderMode, const uint32 *mapping) { if (!g_ttf.isInitialized()) return false; _size = stream.size(); if (!_size) return false; _ttfFile = new uint8[_size]; assert(_ttfFile); if (stream.read(_ttfFile, _size) != _size) { delete[] _ttfFile; _ttfFile = 0; return false; } if (!g_ttf.loadFont(_ttfFile, _size, _face)) { delete[] _ttfFile; _ttfFile = 0; return false; } // We only support scalable fonts. if (!FT_IS_SCALABLE(_face)) { delete[] _ttfFile; _ttfFile = 0; g_ttf.closeFont(_face); return false; } // Check whether we have kerning support _hasKerning = (FT_HAS_KERNING(_face) != 0); if (FT_Set_Char_Size(_face, 0, size * 64, dpi, dpi)) { delete[] _ttfFile; _ttfFile = 0; return false; } 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; } 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 (!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) return false; } } } _initialized = (_glyphs.size() != 0); return _initialized; }