version 2.8
freetype_import.cc
1 #include "freetype_import.hh"
2 #include "importtools.hh"
3 #include <map>
4 #include <string>
5 #include <stdexcept>
6 #include <iostream>
7 #include "ccfixes.hh"
8 
9 #include <ft2build.h>
10 #include FT_FREETYPE_H
11 
12 #undef __FTERRORS_H__
13 #define FT_ERRORDEF( e, v, s ) std::make_pair( e, s ),
14 #define FT_ERROR_START_LIST static const std::map<FT_Error, std::string> ft_errors {
15 #define FT_ERROR_END_LIST };
16 #include FT_ERRORS_H
17 
18 namespace mcufont {
19 
20 static void checkFT(FT_Error error)
21 {
22  if (error != 0)
23  {
24  if (ft_errors.count(error))
25  throw std::runtime_error("libfreetype error " +
26  std::to_string(error) + ": " + ft_errors.at(error));
27  else
28  throw std::runtime_error("unknown libfreetype error " +
29  std::to_string(error));
30  }
31 }
32 
33 // Automatically allocated & freed wrapper for FT_Library
34 class _FT_Library
35 {
36 public:
37  _FT_Library() { checkFT(FT_Init_FreeType(&m_lib)); }
38  ~_FT_Library() { checkFT(FT_Done_FreeType(m_lib)); }
39  operator FT_Library() { return m_lib; }
40 
41 private:
42  FT_Library m_lib;
43 };
44 
45 // Automatically allocated & freed wrapper for FT_Face
46 class _FT_Face
47 {
48 public:
49  _FT_Face(FT_Library lib, const std::vector<char> &data)
50  {
51  checkFT(FT_New_Memory_Face(lib, (const unsigned char *)&data[0],
52  data.size(), 0, &m_face));
53  }
54  ~_FT_Face() { checkFT(FT_Done_Face(m_face)); }
55  operator FT_Face() { return m_face; }
56  FT_Face operator->() { return m_face; }
57 
58 private:
59  FT_Face m_face;
60 };
61 
62 // Read all the data from a file into a memory buffer.
63 static void readfile(std::istream &file, std::vector<char> &data)
64 {
65  while (file.good())
66  {
67  const size_t blocksize = 4096;
68  size_t oldsize = data.size();
69  data.resize(oldsize + blocksize);
70  file.read(&data[oldsize], blocksize);
71  data.resize(oldsize + file.gcount());
72  }
73 }
74 
75 std::unique_ptr<DataFile> LoadFreetype(std::istream &file, int size, bool bw)
76 {
77  std::vector<char> data;
78  readfile(file, data);
79 
80  _FT_Library lib;
81  _FT_Face face(lib, data);
82 
83  checkFT(FT_Set_Pixel_Sizes(face, size, size));
84 
85  DataFile::fontinfo_t fontinfo = {};
86  std::vector<DataFile::glyphentry_t> glyphtable;
87  std::vector<DataFile::dictentry_t> dictionary;
88 
89  // Convert size to pixels and round to nearest.
90  int u_per_em = face->units_per_EM;
91  auto topx = [size, u_per_em](int s) { return (s * size + u_per_em / 2) / u_per_em; };
92 
93  fontinfo.name = std::string(face->family_name) + " " +
94  std::string(face->style_name) + " " +
95  std::to_string(size);
96 
97  // Reserve 4 pixels on each side for antialiasing + hinting.
98  // They will be cropped off later.
99  fontinfo.max_width = topx(face->bbox.xMax - face->bbox.xMin) + 8;
100  fontinfo.max_height = topx(face->bbox.yMax - face->bbox.yMin) + 8;
101  fontinfo.baseline_x = topx(-face->bbox.xMin) + 4;
102  fontinfo.baseline_y = topx(face->bbox.yMax) + 4;
103  fontinfo.line_height = topx(face->height);
104 
105  FT_Int32 loadmode = FT_LOAD_TARGET_NORMAL | FT_LOAD_RENDER;
106 
107  if (bw)
108  loadmode = FT_LOAD_TARGET_MONO | FT_LOAD_MONOCHROME | FT_LOAD_RENDER;
109 
110  FT_ULong charcode;
111  FT_UInt gindex;
112  charcode = FT_Get_First_Char(face, &gindex);
113  while (gindex)
114  {
115  try
116  {
117  checkFT(FT_Load_Glyph(face, gindex, loadmode));
118  }
119  catch (std::runtime_error &e)
120  {
121  std::cerr << "Skipping glyph " << gindex << ": " << e.what() << std::endl;
122  charcode = FT_Get_Next_Char(face, charcode, &gindex);
123  }
124 
125  DataFile::glyphentry_t glyph;
126  glyph.width = (face->glyph->advance.x + 32) / 64;
127  glyph.chars.push_back(charcode);
128  glyph.data.resize(fontinfo.max_width * fontinfo.max_height);
129 
130  int w = face->glyph->bitmap.width;
131  int dw = fontinfo.max_width;
132  int dx = fontinfo.baseline_x + face->glyph->bitmap_left;
133  int dy = fontinfo.baseline_y - face->glyph->bitmap_top;
134 
135  /* Some combining diacritics seem to exceed the bounding box.
136  * We don't support them all that well anyway, so just move
137  * them inside the box in order not to crash.. */
138  if (dy < 0)
139  dy = 0;
140  if (dy + face->glyph->bitmap.rows > fontinfo.max_height)
141  dy = fontinfo.max_height - face->glyph->bitmap.rows;
142 
143  size_t s = face->glyph->bitmap.pitch;
144  for (int y = 0; y < face->glyph->bitmap.rows; y++)
145  {
146  for (int x = 0; x < face->glyph->bitmap.width; x++)
147  {
148  size_t index = (y + dy) * dw + x + dx;
149 
150  if (face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO)
151  {
152  uint8_t byte = face->glyph->bitmap.buffer[s * y + x / 8];
153  byte <<= x % 8;
154  glyph.data.at(index) = (byte & 0x80) ? 15 : 0;
155  }
156  else
157  {
158  glyph.data.at(index) =
159  (face->glyph->bitmap.buffer[w * y + x] + 8) / 17;
160  }
161  }
162  }
163  glyphtable.push_back(glyph);
164 
165  charcode = FT_Get_Next_Char(face, charcode, &gindex);
166  if (gindex % 1000 == 0)
167  std::cout << ".";
168  }
169 
170  std::cout << "\nEliminating duplicates (this may take a while)...";
171 
172  eliminate_duplicates(glyphtable);
173  std::cout << "\nCropping glyphs...";
174  crop_glyphs(glyphtable, fontinfo);
175  std::cout << "\nDetecting flags...";
176  detect_flags(glyphtable, fontinfo);
177 
178  std::cout << "\nGenerating datafile...";
179  std::unique_ptr<DataFile> result(new DataFile(
180  dictionary, glyphtable, fontinfo));
181  std::cout << "\n";
182  return result;
183 }
184 
185 }