ugfx/tools/mcufontencoder/src/freetype_import.cc

178 lines
5.5 KiB
C++

#include "freetype_import.hh"
#include "importtools.hh"
#include <map>
#include <string>
#include <stdexcept>
#include <iostream>
#include "ccfixes.hh"
#include <ft2build.h>
#include FT_FREETYPE_H
#undef __FTERRORS_H__
#define FT_ERRORDEF( e, v, s ) std::make_pair( e, s ),
#define FT_ERROR_START_LIST static const std::map<FT_Error, std::string> ft_errors {
#define FT_ERROR_END_LIST };
#include FT_ERRORS_H
namespace mcufont {
static void checkFT(FT_Error error)
{
if (error != 0)
{
if (ft_errors.count(error))
throw std::runtime_error("libfreetype error " +
std::to_string(error) + ": " + ft_errors.at(error));
else
throw std::runtime_error("unknown libfreetype error " +
std::to_string(error));
}
}
// Automatically allocated & freed wrapper for FT_Library
class _FT_Library
{
public:
_FT_Library() { checkFT(FT_Init_FreeType(&m_lib)); }
~_FT_Library() { checkFT(FT_Done_FreeType(m_lib)); }
operator FT_Library() { return m_lib; }
private:
FT_Library m_lib;
};
// Automatically allocated & freed wrapper for FT_Face
class _FT_Face
{
public:
_FT_Face(FT_Library lib, const std::vector<char> &data)
{
checkFT(FT_New_Memory_Face(lib, (const unsigned char *)&data[0],
data.size(), 0, &m_face));
}
~_FT_Face() { checkFT(FT_Done_Face(m_face)); }
operator FT_Face() { return m_face; }
FT_Face operator->() { return m_face; }
private:
FT_Face m_face;
};
// Read all the data from a file into a memory buffer.
static void readfile(std::istream &file, std::vector<char> &data)
{
while (file.good())
{
const size_t blocksize = 4096;
size_t oldsize = data.size();
data.resize(oldsize + blocksize);
file.read(&data[oldsize], blocksize);
data.resize(oldsize + file.gcount());
}
}
std::unique_ptr<DataFile> LoadFreetype(std::istream &file, int size, bool bw)
{
std::vector<char> data;
readfile(file, data);
_FT_Library lib;
_FT_Face face(lib, data);
checkFT(FT_Set_Pixel_Sizes(face, size, size));
DataFile::fontinfo_t fontinfo = {};
std::vector<DataFile::glyphentry_t> glyphtable;
std::vector<DataFile::dictentry_t> dictionary;
// Convert size to pixels and round to nearest.
int u_per_em = face->units_per_EM;
auto topx = [size, u_per_em](int s) { return (s * size + u_per_em / 2) / u_per_em; };
fontinfo.name = std::string(face->family_name) + " " +
std::string(face->style_name) + " " +
std::to_string(size);
// Reserve 4 pixels on each side for antialiasing + hinting.
// They will be cropped off later.
fontinfo.max_width = topx(face->bbox.xMax - face->bbox.xMin) + 8;
fontinfo.max_height = topx(face->bbox.yMax - face->bbox.yMin) + 8;
fontinfo.baseline_x = topx(-face->bbox.xMin) + 4;
fontinfo.baseline_y = topx(face->bbox.yMax) + 4;
fontinfo.line_height = topx(face->height);
FT_Int32 loadmode = FT_LOAD_TARGET_NORMAL | FT_LOAD_RENDER;
if (bw)
loadmode = FT_LOAD_TARGET_MONO | FT_LOAD_MONOCHROME | FT_LOAD_RENDER;
FT_ULong charcode;
FT_UInt gindex;
charcode = FT_Get_First_Char(face, &gindex);
while (gindex)
{
try
{
checkFT(FT_Load_Glyph(face, gindex, loadmode));
}
catch (std::runtime_error &e)
{
std::cerr << "Skipping glyph " << gindex << ": " << e.what() << std::endl;
charcode = FT_Get_Next_Char(face, charcode, &gindex);
}
DataFile::glyphentry_t glyph;
glyph.width = (face->glyph->advance.x + 32) / 64;
glyph.chars.push_back(charcode);
glyph.data.resize(fontinfo.max_width * fontinfo.max_height);
int w = face->glyph->bitmap.width;
int dw = fontinfo.max_width;
int dx = fontinfo.baseline_x + face->glyph->bitmap_left;
int dy = fontinfo.baseline_y - face->glyph->bitmap_top;
/* Some combining diacritics seem to exceed the bounding box.
* We don't support them all that well anyway, so just move
* them inside the box in order not to crash.. */
if (dy < 0)
dy = 0;
if (dy + face->glyph->bitmap.rows > fontinfo.max_height)
dy = fontinfo.max_height - face->glyph->bitmap.rows;
size_t s = face->glyph->bitmap.pitch;
for (int y = 0; y < face->glyph->bitmap.rows; y++)
{
for (int x = 0; x < face->glyph->bitmap.width; x++)
{
size_t index = (y + dy) * dw + x + dx;
if (face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO)
{
uint8_t byte = face->glyph->bitmap.buffer[s * y + x / 8];
byte <<= x % 8;
glyph.data.at(index) = (byte & 0x80) ? 15 : 0;
}
else
{
glyph.data.at(index) =
(face->glyph->bitmap.buffer[w * y + x] + 8) / 17;
}
}
}
glyphtable.push_back(glyph);
charcode = FT_Get_Next_Char(face, charcode, &gindex);
}
eliminate_duplicates(glyphtable);
crop_glyphs(glyphtable, fontinfo);
detect_flags(glyphtable, fontinfo);
std::unique_ptr<DataFile> result(new DataFile(
dictionary, glyphtable, fontinfo));
return result;
}
}