aboutsummaryrefslogtreecommitdiffstats
path: root/tools/mcufontencoder/src/freetype_import.cc
blob: 27a0734d7d50e5e195aad5f2b09fbad9f7f050f4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#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;
}

}