Hello,
I'm integrating Freetype into my project, but so far I've never been able to render any font into a bitmap.
Freetype functions never returns an error, the bitmap I get is just empty, width and height are zero, whatever character I pass.
The fon't I'm using is a basic outline TTF.
In my project, I split the code in 2 files: FontLoader.cpp and Font.cpp.
FontLoader is a class that holds the FT_Library, and Font holds the FT_Face.
Here is the part of my code that uses Freetype (unrelated code eluded for clarity):
FontLoader.hpp
//...
#include <core/asset/AssetLoader.hpp>
#include <ft2build.h>
#include FT_FREETYPE_H
namespace freetype
{
class FontLoader : public sn::AssetLoader
{
public:
//...
bool load(std::ifstream & ifs, sn::Asset & asset) const override;
//...
private:
FT_Library m_library;
};
} // freetype
//...
FontLoader.cpp
//...
#include "Font.hpp"
#include "FontLoader.hpp"
using namespace sn;
namespace freetype
{
FontLoader::FontLoader():
m_library(nullptr)
{
// Initialize Freetype
if (FT_Init_FreeType(&m_library) != 0)
{
SN_ERROR("Failed to initialize FreeType library");
}
}
FontLoader::~FontLoader()
{
if (m_library != 0)
{
// Deinitialize Freetype
FT_Done_FreeType(m_library);
}
}
//...
bool FontLoader::load(std::ifstream & ifs, sn::Asset & asset) const
{
freetype::Font * font = sn::checked_cast<freetype::Font*>(&asset);
SN_ASSERT(font != nullptr, "Invalid asset type");
// Read the whole stream
ifs.seekg(0, ifs.end);
u32 len = ifs.tellg();
ifs.seekg(0, ifs.beg);
char * data = new char[len];
ifs.read(data, len);
// Load the face
FT_Face face;
if (FT_New_Memory_Face(m_library, reinterpret_cast<const FT_Byte*>(data), len, 0, &face) != 0)
{
SN_ERROR("Failed to create Freetype font face from memory");
delete[] data;
return false;
}
delete[] data;
// Select the unicode character map
if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) != 0)
{
SN_ERROR("Failed to select the Unicode character set (Freetype)");
return false;
}
// Store the loaded font
font->setFace(face);
return true;
}
} // namespace freetype
Font.hpp
//...
#include <ft2build.h>
#include FT_FREETYPE_H
namespace freetype
{
class Font : public sn::Font, public sn::NonCopyable
{
//...
private:
bool generateGlyph(sn::Glyph & out_glyph, sn::u32 unicode, sn::FontFormat format) const;
//...
bool setCurrentSize(sn::u32 characterSize) const;
private:
FT_Face m_face;
//...
};
} // namespace freetype
//...
Font.cpp
#include "Font.hpp"
#include FT_GLYPH_H
#include FT_OUTLINE_H
#include FT_BITMAP_H
//...
bool Font::generateGlyph(Glyph & out_glyph, sn::u32 unicode, sn::FontFormat format) const
{
Glyph glyph;
if (!setCurrentSize(format.size))
return false;
// Load the glyph corresponding the unicode
if (FT_Load_Char(m_face, unicode, FT_LOAD_TARGET_NORMAL) != 0)
return false;
// Retrieve the glyph
FT_Glyph glyphDesc;
if (FT_Get_Glyph(m_face->glyph, &glyphDesc) != 0)
return false;
// Apply bold
FT_Pos weight = 1 << 6;
bool outline = (glyphDesc->format == FT_GLYPH_FORMAT_OUTLINE);
if (format.isBold() && outline)
{
FT_OutlineGlyph outlineGlyph = (FT_OutlineGlyph)glyphDesc;
FT_Outline_Embolden(&outlineGlyph->outline, weight);
}
// Convert the glyph to a bitmap (i.e. rasterize it)
if (glyphDesc->format != FT_GLYPH_FORMAT_BITMAP)
{
if (FT_Glyph_To_Bitmap(&glyphDesc, FT_RENDER_MODE_NORMAL, 0, 1) != 0)
{
SN_ERROR("Failed to convert glyph to bitmap");
}
}
FT_BitmapGlyph bitmapGlyph = (FT_BitmapGlyph)glyphDesc;
FT_Bitmap& bitmap = bitmapGlyph->bitmap;
// Compute the glyph's advance offset
glyph.advance = glyphDesc->advance.x >> 16;
if (format.isBold())
glyph.advance += weight >> 6;
u32 width = bitmap.width;
u32 height = bitmap.rows;
if (width > 0 && height > 0)
{
// NEVER ENTERS HERE
// Funny conversion stuff
//...
}
else
{
SN_DLOG("Character " << unicode << " (ascii: " << (char)unicode << ") has an empty bitmap");
}
// Delete the FT glyph
FT_Done_Glyph(glyphDesc);
out_glyph = glyph;
return true;
}
//...
bool Font::setCurrentSize(sn::u32 characterSize) const
{
SN_ASSERT(m_face != nullptr, "Invalid state: Freetype font face is null");
FT_UShort currentSize = m_face->size->metrics.x_ppem;
if (currentSize != characterSize)
{
return FT_Set_Pixel_Sizes(m_face, 0, characterSize) == 0;
}
else
{
return true;
}
}
//...
EDIT: by stepping into FT_Glyph_To_Bitmap and further, I discovered this:
ftobjs.c
error = renderer->render( renderer, slot, render_mode, NULL );
if ( !error ||
FT_ERR_NEQ( error, Cannot_Render_Glyph ) ) // This line is reached
break;
ftrend1.c
renderer->render being a function pointer, I steeped in it too:
/* check rendering mode */
#ifndef FT_CONFIG_OPTION_PIC
if ( mode != FT_RENDER_MODE_MONO )
{
/* raster1 is only capable of producing monochrome bitmaps */
if ( render->clazz == &ft_raster1_renderer_class )
return FT_THROW( Cannot_Render_Glyph ); // THIS LINE IS REACHED
}
else
{
/* raster5 is only capable of producing 5-gray-levels bitmaps */
if ( render->clazz == &ft_raster5_renderer_class )
return FT_THROW( Cannot_Render_Glyph );
}
#else /* FT_CONFIG_OPTION_PIC */
I want to render with the FT_RENDER_NORMAL mode, but for some reason it seems Freetype can't