Branch data Line data Source code
1 : : #ifdef RUN_TESTS
2 : : # include <gtest/gtest.h>
3 : : #endif
4 : : /** @file rendering/fonts/read_font.cc
5 : : * @brief Contains low-level functions for parsing font files. Used by ReadFontsList and FontPlan.
6 : : */
7 : :
8 : : #include <regex>
9 : : #include <fstream>
10 : : #include <unordered_map>
11 : : #include <mutex>
12 : : #include <set>
13 : :
14 : : #include "read_font.hh"
15 : : #include "gunzip.hh"
16 : : #include "endian.hh"
17 : : #include "share.hh"
18 : :
19 : : using namespace std::literals;
20 : :
21 : : static constexpr auto Ropt = std::regex_constants::ECMAScript | std::regex_constants::optimize;
22 : :
23 : : // Syntactic shorthand for creating regular expressions.
24 : 83 : static std::regex operator ""_r(const char* pattern, std::size_t length)
25 : : {
26 : 83 : return std::regex(pattern,length, Ropt);
27 : : }
28 : :
29 : : #pragma GCC push_options
30 : : #pragma GCC optimize ("Ofast")
31 : : template<unsigned l>
32 : 163797143 : static bool rmatch(std::string_view str, std::smatch& mat, const std::string& line)
33 : : {
34 [ + + + + : 163797143 : static std::regex r(std::string(str), Ropt);
+ - + - ]
35 : 163797145 : return std::regex_match(line, mat, r);
36 : : }
37 : : #pragma GCC pop_options
38 : :
39 : : /* Read a file in share/encodings/ that specifies character mapping for this encoding */
40 : 11 : static std::unordered_map<char32_t,char32_t> ReadEncoding(std::string_view filename)
41 : : {
42 [ + - ]: 11 : std::unordered_map<char32_t, char32_t> result;
43 [ + - + - : 44 : auto [fn, status] = FindShareFile(std::filesystem::path("encodings") / std::filesystem::path(filename));
+ - + - -
+ ]
44 [ - + ]: 12 : if(std::filesystem::exists(status))
45 : : {
46 [ + - ]: 10 : std::ifstream f(fn);
47 : 10 : auto delim = " \t"sv, hex = "0123456789ABCDEFabcdef"sv;
48 [ + - + + ]: 16119 : for(std::string line; std::getline(f,line), f; )
49 [ + - + - ]: 16109 : if(!line.empty() && line[0] != '#')
50 : : {
51 : 16109 : std::size_t space = line.find_first_of(delim);
52 [ - + ]: 16109 : if(space == line.npos) continue;
53 : 16109 : std::size_t notspace = line.find_first_not_of(delim, space);
54 [ - + ]: 16109 : if(notspace == line.npos) continue;
55 : 16109 : std::size_t notdigit = line.find_first_not_of(hex, notspace);
56 [ + + ]: 16109 : if(notdigit == line.npos) notdigit = line.size();
57 : :
58 [ + - ]: 16109 : result.emplace(std::stoi(std::string(line, 0, space), nullptr, 16),
59 [ + - + - ]: 48327 : std::stoi(std::string(line, notspace, notdigit-notspace), nullptr, 16));
60 : 0 : }
61 : : //std::sort(result.begin(), result.end());
62 : : //for(auto p: result)
63 : : // std::printf("Enc %s 0x%X = 0x%X\n", filename.data(), (int)p.first,(int)p.second);
64 : 10 : }
65 : : //else
66 : : //{
67 : : // std::printf("%s does not exist\n", filename.data());
68 : : //}
69 : 11 : return result;
70 : 11 : }
71 : :
72 : 795013 : std::vector<char32_t> BDFtranslateToUnicode(int index, std::string_view reg, std::string_view enc)
73 : : {
74 [ - + ]: 795013 : if(index == -1) return {65533}; // notdef in ibmfonts
75 : :
76 : 795013 : std::string regenc(reg);
77 [ + + + + : 795561 : if(!reg.empty() && !enc.empty()) regenc += '-';
+ - ]
78 [ + - ]: 795542 : regenc += enc;
79 [ + + + + ]: 8995328 : for(char& c: regenc) if(c >= 'A' && c <= 'Z') c += 32; // tolower
80 : :
81 : 796512 : char32_t result = index;
82 [ + + ]: 796512 : if(regenc == "iso10646-1"sv
83 [ + + + + ]: 796512 : || regenc == "iso-8859-1"sv)
84 : : {
85 : : result = index;
86 : : }
87 [ + + ]: 66406 : else if(regenc == "jisx0201.1976-0"sv)
88 : : {
89 [ + + ]: 636 : if(index == 0x5C) result = 0xA5;
90 [ + + ]: 632 : else if(index == 0x7E) result = 0x203E;
91 [ + + ]: 628 : else if(index >= 0xA1) result = index - 0xA1 + 0xFF61;
92 : : else result = index;
93 : : }
94 : : else
95 : : {
96 : 65770 : static std::mutex lock;
97 [ + + + - ]: 65770 : static std::unordered_map<std::string, std::unordered_map<char32_t,char32_t>> data;
98 : 65770 : auto i = data.find(regenc);
99 [ + + ]: 65777 : if(i == data.end())
100 : : {
101 [ + - ]: 19 : std::lock_guard lk(lock);
102 : 19 : i = data.find(regenc);
103 [ + + ]: 19 : if(i == data.end())
104 [ + - + - : 22 : i = data.emplace(regenc, ReadEncoding(regenc + ".txt")).first;
- - ]
105 : 19 : }
106 : 65777 : auto& translation = i->second;
107 : : /*
108 : : auto j = std::lower_bound(translation.begin(), translation.end(), index,
109 : : [](const auto& pair, char32_t value) { return pair.first < value; });
110 : : if(j != translation.end() && j->first == (char32_t)index)
111 : : {
112 : : result = j->second;
113 : : //std::printf("%s: 0x%X = 0x%X\n", regenc.c_str(), index, result);
114 : : }
115 : : */
116 : 65777 : auto j = translation.find(index);
117 [ + + ]: 65767 : if(j != translation.end())
118 : 63483 : result = j->second;
119 : : }
120 [ + - ]: 796509 : std::vector<char32_t> chars{ result };
121 : 796085 : static const unsigned short trans_00_to_1F[] =
122 : : {
123 : : 0x0000,0x263A,0x263B,0x2665,
124 : : 0x2666,0x2663,0x2660,0x2022,
125 : : 0x25D8,0x25CB,0x25D9,0x2642,
126 : : 0x2640,0x266A,0x266B,0x263C,
127 : : 0x25BA,0x25C4,0x2195,0x203C,
128 : : 0x00B6,0x00A7,0x25AC,0x21A8,
129 : : 0x2191,0x2193,0x2192,0x2190,
130 : : 0x221F,0x2194,0x25B2,0x25BC,
131 : : };
132 : 796085 : static const unsigned short trans_7F_to_9F[] =
133 : : {
134 : : 0x2302, // 7F house
135 : : // 81..8F follows CP/M plus character set (Amstrad CPC e.g.) but shifted 91..9F
136 : : // ⎺ ╵ ╶ └ ╷ │ ┌ ├ ╴ ┘ ─ ┴ ┐ ┤ ┬ ┼
137 : : 0x23BA,0x2575,0x2576,0x2514,0x2577,0x2502,0x250C,0x251C,
138 : : 0x2574,0x2518,0x2500,0x2534,0x2510,0x2524,0x252C,0x253C,
139 : : // 95..9F follows CP/M plus character set (Amstrad CPC e.g.) but shifted 85..8F
140 : : // ⎽ ╧ ╟ ╚ ╤ ║ ╔ ╠ ╢ ╝ ═ ╩ ╗ ╣ ╦ ╬
141 : : // Note that 91,92,94,98 are truncated *double* lines, which are not supported by unicode
142 : : 0x25BD,0x2567,0x255F,0x255A,0x2564,0x2551,0x2554,0x2560,
143 : : 0x2562,0x255D,0x2550,0x2569,0x2557,0x2563,0x2566,0x256C
144 : : };
145 [ + + + - ]: 796085 : if(/* result >= 0x00 &&*/ result <= 0x1F) chars.push_back(trans_00_to_1F[result]);
146 [ + + + - ]: 795047 : else if(result >= 0x7F && result <= 0x9F) chars.push_back(trans_7F_to_9F[result- 0x7F]);
147 : :
148 [ + + + - ]: 796087 : if(index < 32 && result >= 32) chars.push_back(index);
149 : :
150 : 796087 : return chars;
151 : 1592050 : }
152 : :
153 : :
154 : 158 : std::string ReadGZ(std::string_view filename)
155 : : {
156 [ + - ]: 158 : std::string result;
157 : 158 : char buf[8192];
158 : 158 : std::size_t pos=0, cap=0;
159 [ + - + - ]: 158 : std::ifstream f{ std::string(filename) };
160 [ + - ]: 158 : Deflate([&]() // in char
161 : : {
162 [ + + ]: 388857 : if(pos >= cap)
163 : : {
164 [ + - ]: 171 : if(!f) return -1;
165 : 171 : f.read(buf, sizeof(buf));
166 : 172 : pos = 0;
167 : 172 : cap = f.gcount();
168 : : }
169 : 388858 : return (int) (unsigned char) buf[pos++];
170 : : },
171 : 1036795 : [&](unsigned char c) // out char
172 : : {
173 : 1036795 : result += char(c);
174 : : }/*,
175 : : [&](std::size_t length, std::size_t offset) // window
176 : : {
177 : : result += std::string( result.end()-(offset+1), result.end()-(offset+1) + length );
178 : : }*/);
179 : 158 : return result;
180 : 158 : }
181 : :
182 : : /** Data that is read from the PSF file header. */
183 : : struct PSFheader
184 : : {
185 : : unsigned fontlen; ///< 256 or 512
186 : : bool hastable; ///< If the font contains encoding table
187 : : unsigned offset; ///< Starting offset of data
188 : : bool utf8; ///< If the codepoints are represented in UTF-8
189 : : unsigned charsize; ///< Size in bytes of each glyph
190 : : unsigned width; ///< Width
191 : : unsigned height; ///< Height
192 : : };
193 : 158 : PSFheader ReadPSFheader(const char* data)
194 : : {
195 [ + + ]: 158 : unsigned magic1 = R16r(&data[0]);
196 [ + + ]: 158 : unsigned magic2 = R32r(&data[0]);
197 [ + + ]: 158 : if(magic1 == 0x3604)
198 : : {
199 : 99 : unsigned char mode = data[2];
200 : 99 : unsigned char csize = data[3];
201 : : /* mode & 1: MODE512
202 : : * mode & 2: MODEHASTAB
203 : : * mode & 4: MODEHASSEQ
204 : : */
205 [ + + ]: 99 : return { (mode&1) ? 512u : 256u, // fontlen
206 : 99 : (mode&6) != 0, // hastable
207 : : 4, // offset
208 : : false, // utf8
209 : 99 : csize, // charsize
210 : : 8, // width
211 : 99 : csize }; // height
212 : : }
213 [ + - ]: 59 : else if(magic2 == 0x72B54A86)
214 : : {
215 : : //unsigned vers = R32(&data[4]);
216 : 59 : unsigned hdrsize = R32(&data[8]);
217 : 59 : unsigned flags = R32(&data[12]);
218 : 59 : unsigned length = R32(&data[16]);
219 : 59 : unsigned charsize = R32(&data[20]);
220 : 59 : unsigned height = R32(&data[24]);
221 : 59 : unsigned width = R32(&data[28]);
222 : 59 : return { length, // fontlen
223 : 59 : (flags&1) != 0, // hastable
224 : : hdrsize, // offset
225 : : true, // utf8
226 : : charsize, // charsize
227 : : width, // width
228 : 59 : height }; // height
229 : : }
230 : 0 : return {};
231 : : }
232 : :
233 : : std::multimap<std::size_t, char32_t>
234 : 158 : Read_PSFgzEncoding(std::string_view data, const PSFheader& h)
235 : : {
236 : 158 : static constexpr char32_t SS = 0xFFEFFAEE; // magic
237 : 158 : static constexpr char32_t TERM = 0xFFEFFAEF; // magic
238 : :
239 [ + + ]: 158 : std::multimap<std::size_t, char32_t> table;
240 [ + + ]: 158 : if(h.hastable)
241 : : {
242 : 140 : std::size_t pos = h.offset + h.fontlen * h.charsize;
243 : 324074 : auto ReadUC = [&h,&pos,&data]() -> char32_t
244 : : {
245 [ + + ]: 127652 : if(h.utf8)
246 : : {
247 [ + - ]: 62191 : char32_t unichar = (unsigned char) data[pos++];
248 [ + - ]: 62191 : if(unichar == 0xFE) return SS;
249 [ + + ]: 62191 : if(unichar == 0xFF) return TERM;
250 [ + + ]: 33787 : if(unichar >= 0x80)
251 : : {
252 : : /*
253 : : UTF-8 encodings:
254 : : 7 bits: 0xxxxxxx (7)
255 : : 11 bits: 110xxxxx 10xxxxxx (5 + 6)
256 : : 16 bits: 1110xxxx 10xxxxxx 10xxxxxx (4 + 6 + 6)
257 : : 21 bits: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (3 + 6 + 6 + 6)
258 : : */
259 [ - + ]: 28153 : if((unichar & 0xF8) == 0xF0) unichar &= 0x07; // 3 bits
260 [ + + ]: 28153 : else if((unichar & 0xF0) == 0xE0) unichar &= 0x0F; // 4 bits
261 [ + - ]: 15825 : else if((unichar & 0xE0) == 0xC0) unichar &= 0x1F; // 5 bits
262 : 0 : else unichar &= 0x7F; // 7 bits
263 [ + + + + ]: 68630 : while(pos < data.size() && ((data[pos] & 0xC0) == 0x80))
264 : 40477 : unichar = (unichar << 6) | (data[pos++] & 0x3F);
265 : : }
266 : 33787 : return unichar;
267 : : }
268 : : else
269 : : {
270 [ + + - ]: 65461 : char32_t unichar = (unsigned char) data[pos++];
271 : 65461 : unichar |= (unsigned char)data[pos++] << 8;
272 [ + + - ]: 65461 : switch(unichar)
273 : : {
274 : : case 0xFFFE: return SS;
275 : 30698 : case 0xFFFF: return TERM;
276 : 34763 : default: return unichar;
277 : : }
278 : : }
279 : 140 : };
280 [ + + ]: 59271 : for(unsigned n=0; n<h.fontlen; ++n)
281 : : {
282 : : // Grammar:
283 : : // UC* (SS UC+)* TERM
284 : 59131 : std::set<char32_t> list;
285 : : char32_t code;
286 : : // Single-codepoint alternatives for this glyph
287 : : //if(pos >= data.size()) std::cout << "EOF " << n << '\n';
288 : : //else std::cout << "Pos " << n << ' ' << std::hex << pos << std::dec << '\t';
289 [ + # ]: 127524 : while(pos < data.size())
290 : : {
291 : 127534 : code = ReadUC();
292 [ + + ]: 127602 : if(code == TERM || code == SS) break;
293 : : //std::cout << "Code " << n << ": " << std::hex << code << std::dec << '\n';
294 [ + - ]: 68529 : list.insert(code);
295 : : }
296 : : // Multi-codepoint alternatives for this glyph (for e.g. combining diacritics)
297 [ + + # + ]: 59063 : while(pos < data.size() && code == SS)
298 : : {
299 : 0 : std::vector<char32_t> seq;
300 : 0 : for(;;)
301 : : {
302 : 0 : code = ReadUC();
303 [ # # ]: 0 : if(code == SS || code == TERM) break;
304 [ # # ]: 0 : seq.push_back(code);
305 : : }
306 : : //std::cout << "Seq " << n << ":"; for(auto c: seq) { std::cout << ' ' << std::hex << c; } std::cout << std::dec << '\n';
307 : : // We ignore all of them if they have more than 1 codepoint.
308 [ # # # # ]: 0 : if(seq.size() == 1) list.insert(seq[0]); //else { print 'seq'; print_r($seq); }
309 : 0 : }
310 : : /*
311 : : Line-drawing characters:
312 : : If this symbol claims simultaneously to accomplish
313 : : multiple line-styles, call out the bluff.
314 : : */
315 : 59070 : static const std::u32string_view bluff_list[] =
316 : : {
317 : : // Single-lines
318 : : U"─━┄┅┈┉╌╍╴╶╸╺╼╾"sv,
319 : : U"│┃┆┇┊┋╎╏╵╷╹╻╽╿"sv,
320 : : // Single-line corners
321 : : U"┌┍┎┏╭"sv,
322 : : U"┐┑┒┓╮"sv,
323 : : U"└┕┖┗╰"sv,
324 : : U"┘┙┚┛╯"sv,
325 : : // Double-line corners
326 : : U"╔╔╒┏"sv,
327 : : U"╗╕╖┓"sv,
328 : : U"╚╘╙┗"sv,
329 : : U"╝╛╜┛"sv,
330 : : // Single-line junctions
331 : : U"┬┭┮┯┰┱┲┳"sv,
332 : : U"┴┵┶┷┸┹┺┻"sv,
333 : : U"├┝┞┟┠┡┢┣"sv,
334 : : U"┤┥┦┧┨┩┪┫"sv,
335 : : U"┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋"sv,
336 : : // Double-line junctions
337 : : U"╦╤╥┳"sv,
338 : : U"╩╧╨┻"sv,
339 : : U"╠╞╟┣"sv,
340 : : U"╣╡╢┫"sv,
341 : : U"╬╪╫╋"sv,
342 : : };
343 [ + + ]: 59070 : if(list.size() > 1)
344 [ + + ]: 143646 : for(auto s: bluff_list)
345 [ + + ]: 136792 : if(list.find(s[0]) != list.end())
346 [ + + ]: 6271 : for(auto i = s.begin()+1; i != s.end(); ++i)
347 : 5469 : list.erase(*i);
348 [ + + ]: 123272 : for(auto u: list)
349 [ + - ]: 64181 : table.emplace(n, u);
350 : 59131 : }
351 : : }
352 : 158 : return table;
353 : 0 : }
354 : :
355 : : std::multimap<std::size_t, char32_t>
356 : 1 : Read_PSFgzEncoding(std::string_view filename)
357 : : {
358 : 1 : std::string data = ReadGZ(filename);
359 [ + - ]: 1 : const PSFheader h = ReadPSFheader(data.data());
360 [ + - ]: 1 : return Read_PSFgzEncoding(data, h);
361 : 1 : }
362 : :
363 : 157 : GlyphList Read_PSFgz(std::string_view filename, unsigned /*width*/, unsigned /*height*/, std::string_view assume_encoding)
364 : : {
365 : 157 : std::string data = ReadGZ(filename);
366 [ + - ]: 157 : const PSFheader h = ReadPSFheader(data.data());
367 [ + - ]: 157 : auto table = Read_PSFgzEncoding(data, h);
368 : : //print_r($table);
369 : 157 : GlyphList result;
370 : 157 : result.height = h.height;
371 [ + - ]: 157 : result.widths.push_back(h.width);
372 : 157 : result.unicode = !table.empty();
373 : :
374 : : //std::cout << "assuming " << assume_encoding << ", table is " << (table.empty() ? "empty" : "not empty") << '\n';
375 : :
376 [ + + ]: 61463 : for(std::size_t n=0; n<h.fontlen; ++n)
377 : : {
378 : : // List of unicode characters fulfilled by this glyph
379 [ + + ]: 61306 : std::vector<char32_t> index;
380 [ + + ]: 61306 : if(!table.empty())
381 : : {
382 : 57589 : auto [begin, end] = table.equal_range(n);
383 : : // if(begin == end) { std::cout << "Glyph " << n << " empty range?\n"; }
384 [ + - + + ]: 120570 : for(; begin != end; ++begin) index.push_back(begin->second);
385 : : }
386 : :
387 [ + + + + ]: 61802 : if(index.empty() && !assume_encoding.empty())
388 : : {
389 [ + - + + ]: 13413 : for(auto code: BDFtranslateToUnicode(n, ""sv, assume_encoding))
390 [ + - ]: 13423 : index.push_back(code);
391 : : }
392 : :
393 [ + + ]: 61813 : if(index.empty())
394 : : {
395 : : //std::cout << "Index on " << n << " is empty?\n";
396 [ + - ]: 4391 : index.push_back( n + 0xF300 );
397 : : }
398 : :
399 [ + - ]: 61813 : std::size_t start_offset = result.bitmap.size();
400 [ + - ]: 61813 : result.bitmap.reserve(start_offset + h.charsize);
401 [ + - + + ]: 137544 : for(auto c: index) result.glyphs.emplace(c, start_offset);
402 : : // Read the bitmap
403 : 62255 : std::size_t pos = h.offset + n * h.charsize;
404 : 62255 : std::size_t bytesperchar = h.charsize / h.height;
405 [ + + ]: 979690 : for(std::size_t m=0; m<h.charsize; m += bytesperchar)
406 : : {
407 : : std::uint_fast64_t w = 0;
408 : : // Read big-endian
409 [ + + ]: 2375522 : for(std::size_t a=0; a<bytesperchar; ++a)
410 : 1456576 : w |= ((std::uint_fast64_t)(unsigned char)data[pos + m + bytesperchar - a - 1]) << (a*8);
411 [ + + ]: 918946 : if(h.width % 8)
412 : 344341 : w >>= (8 - h.width%8);
413 : : // Write little-endian
414 [ + + ]: 2356570 : for(std::size_t a=0; a<bytesperchar; ++a)
415 [ + - ]: 1439135 : result.bitmap.push_back( w >> (a*8) );
416 : : }
417 : 61306 : }
418 : 157 : return result;
419 : 157 : }
420 : :
421 : 83 : GlyphList Read_BDF(std::string_view filename, unsigned width, unsigned /*height*/, std::string_view guess_encoding)
422 : : {
423 : 83 : std::vector<char32_t> chno = {0};
424 [ + - ]: 83 : std::vector<std::size_t> data;
425 : 83 : unsigned mode = 0;
426 : 83 : unsigned matrix_row_size = (8 + 7) >> 3; // 1 byte
427 : 83 : int ascent = 0, descent = 0, shiftbits = 0, beforebox = 0, afterbox = 0;
428 [ + - ]: 83 : std::string registry, encoding(guess_encoding);
429 : 83 : unsigned fontwidth = width, fontheight = 1, avg_width = 0;
430 : 83 : GlyphList result;
431 : 83 : result.unicode = false;
432 : :
433 : : #define r(str) rmatch<__LINE__>(str##sv, mat, line)
434 : :
435 : : // Ignore width for variable-width fonts
436 [ + - + - : 83 : bool ignore_width = std::regex_search(std::string(filename), "monak1[246]|mona6x12|mona7x14"_r);
+ - ]
437 : :
438 [ + - + - ]: 83 : std::ifstream f{ std::string(filename) };
439 : 83 : auto space = " \t\r"sv;
440 [ + - + + ]: 17700216 : for(std::string line; std::getline(f,line), f; )
441 : : {
442 : : // Trim
443 : 17849587 : std::size_t begin = line.find_first_not_of(space);
444 [ + + ]: 17814920 : if(begin == line.npos) continue;
445 : 17261956 : std::size_t end = line.find_last_not_of(space)+1;
446 [ + - + + : 17261956 : if(begin > 0 || end < line.size()) line = line.substr(begin, end-begin);
+ - ]
447 : : // Identify
448 [ + - ]: 17261944 : std::smatch mat;
449 [ + - + + : 17261944 : /**/ if(r("FONT_ASCENT ([0-9]+)")) ascent = std::stoi(mat[1]);
+ - + - ]
450 [ + - + + : 16604026 : else if(r("FONT_DESCENT ([0-9]+)")) descent = std::stoi(mat[1]);
+ - + - ]
451 [ + - + + : 16844454 : else if(r("AVERAGE_WIDTH ([0-9]+)")) avg_width = std::stoi(mat[1]);
+ - + - ]
452 [ + - + + : 16749353 : else if(r("FONT -.*-([^-]*)-([^-]*)")) { registry = mat[1]; encoding = mat[2]; result.unicode = true; }
+ - + - ]
453 [ + - + + : 17504483 : else if(r("ENCODING ([0-9]+)")) { chno = BDFtranslateToUnicode(std::stoi(mat[1]), registry, encoding); }
+ - + - +
- ]
454 [ + - - + : 16140179 : else if(r("CHARSET_REGISTRY \"?([^\"]*)")) registry = mat[1];
- - ]
455 [ + - - + : 15991000 : else if(r("CHARSET_ENCODING \"?([^\"]*)")) encoding = mat[1];
- - ]
456 [ + - + + ]: 16037036 : else if(r("FONTBOUNDINGBOX ([0-9]+) ([0-9]+).*"))
457 : : {
458 : : //fontwidth = std::stoi(mat[1]);
459 [ + - + - ]: 78 : fontheight = std::stoi(mat[2]);
460 : : //matrix_row_size = (fontwidth + 7) >> 3; // 1 byte
461 : 78 : result.height = fontheight;
462 : : }
463 [ + - + + ]: 15967477 : else if(r("DWIDTH ([0-9]+) ([0-9]+).*"))
464 : : {
465 [ + - + - ]: 787161 : fontwidth = std::stoi(mat[1]);
466 : 788244 : matrix_row_size = (fontwidth + 7) >> 3; // 1 byte
467 [ + + ]: 788244 : if(std::find(result.widths.begin(), result.widths.end(), fontwidth) == result.widths.end())
468 [ + - ]: 164 : result.widths.push_back(fontwidth);
469 : : }
470 [ + - + + ]: 15396975 : else if(r("BBX (-?[0-9]+) (-?[0-9]+) (-?[0-9]+) (-?[0-9]+).*"))
471 : : {
472 [ + - + - ]: 787222 : int x = std::stoi(mat[1]);
473 [ + - + - ]: 788120 : int y = std::stoi(mat[2]);
474 [ + - + - ]: 788371 : int xo = std::stoi(mat[3]);
475 [ + - + - ]: 788456 : int yo = std::stoi(mat[4]);
476 : 788356 : shiftbits = (matrix_row_size - ((x + 7) >> 3) ) * 8 - xo;
477 : 788356 : beforebox = (ascent - yo - y) * matrix_row_size;
478 : 788356 : afterbox = (descent + yo) * matrix_row_size;
479 [ + + + - ]: 788356 : if(width == 9 && filename.find("gb16st") != filename.npos)
480 : : {
481 : : // The autodetected value seems bad, manually override
482 : : beforebox = 1;
483 : : afterbox = 0;
484 : : }
485 [ + + + + ]: 788356 : if(width == 10 && filename.find("gb24st") != filename.npos)
486 : : {
487 : : // The autodetected value seems bad, manually override
488 : : beforebox = 4;
489 : : afterbox = 0;
490 : : }
491 : : }
492 [ + + ]: 14367227 : else if(line == "BITMAP")
493 : : mode = 1;
494 [ + + ]: 13765201 : else if(line == "ENDCHAR")
495 : : {
496 : 786793 : mode = 0;
497 [ + + + + ]: 786793 : if(fontwidth == width || ignore_width)
498 : : {
499 [ - + ]: 409908 : std::vector<std::uint_fast64_t> map;
500 [ - + ]: 409908 : if(beforebox < 0)
501 : : {
502 [ # # ]: 0 : if(data.size() >= std::size_t(-beforebox))
503 : 0 : data.erase(data.begin(), data.begin() + (-beforebox));
504 : : else
505 [ # # ]: 0 : data.clear();
506 : : beforebox = 0;
507 : : }
508 [ - + ]: 409908 : if(afterbox < 0)
509 : : {
510 [ # # ]: 0 : if(data.size() >= std::size_t(-afterbox))
511 : 0 : data.erase(data.end() - (-afterbox), data.end());
512 : : else
513 [ # # ]: 0 : data.clear();
514 : : afterbox = 0;
515 : : }
516 [ + + ]: 409908 : if(beforebox > 0)
517 [ + - ]: 901 : map.resize(map.size() + beforebox);
518 [ + - ]: 409908 : map.insert(map.end(), data.begin(), data.end());
519 [ + + ]: 411193 : if(afterbox > 0)
520 [ + - ]: 36140 : map.resize(map.size() + afterbox);
521 [ - + ]: 411192 : if(map.size() < fontheight)
522 [ # # ]: 0 : map.resize(fontheight);
523 : :
524 : 411192 : unsigned bytes = (width + 7) >> 3;
525 : 411192 : unsigned sbytes = (fontwidth + 7) >> 3;
526 : :
527 [ + - ]: 411192 : std::size_t start_offset = result.bitmap.size();
528 [ + - ]: 411192 : result.bitmap.reserve(start_offset + fontheight * bytes);
529 : :
530 [ + - + + ]: 823068 : for(auto ch: chno) { result.glyphs.emplace(ch, start_offset); }
531 : :
532 [ + + ]: 6608172 : for(unsigned y=0; y<fontheight; ++y)
533 : : {
534 : 6196747 : auto m = map[y];
535 [ + + ]: 18967608 : for(unsigned b=0; b<bytes; ++b)
536 [ + - ]: 12770818 : result.bitmap.push_back( ((m >> ((sbytes*8)-fontwidth)) >> (b*8)) & 0xFF );
537 : : }
538 [ + + ]: 823117 : for(auto& ch: chno) ++ch;
539 : 411412 : }
540 [ + # ]: 17787211 : data.clear();
541 : : }
542 [ + + ]: 12978408 : else if(mode)
543 : : {
544 [ + - ]: 11540637 : std::uint_fast64_t v = std::stoll(line, nullptr, 16);
545 [ + + ]: 11818602 : if(shiftbits > 0)
546 : 48 : v <<= shiftbits;
547 : : else
548 : 11818554 : v >>= -shiftbits;
549 [ + - ]: 11818602 : data.push_back(v);
550 : : }
551 : : #undef r
552 : 17147169 : }
553 : :
554 : 83 : std::sort(result.widths.begin(), result.widths.end());
555 [ + + ]: 83 : if(ignore_width)
556 : : {
557 [ + - ]: 9 : if(avg_width)
558 [ + - ]: 9 : result.widths.assign(1, (avg_width + 5) / 10);
559 : : else
560 : 0 : result.widths.erase(result.widths.begin(), std::prev(result.widths.end()));
561 : : }
562 : 166 : return result;
563 : 83 : }
564 : :
565 : 4 : static const std::regex& GetHeightPat()
566 : : {
567 [ + + + - : 4 : static const std::regex heightpat(".*[^0-9]([0-9]+)[^0-9]*", Ropt);
+ - ]
568 : 4 : return heightpat;
569 : : }
570 : :
571 : 7 : GlyphList Read_Inc(std::string_view filename, unsigned width, unsigned height, std::string_view guess_encoding)
572 : : {
573 [ + + ]: 7 : std::smatch mat;
574 [ + + ]: 7 : if(!height)
575 : : {
576 [ + - ]: 3 : std::string tmp(filename);
577 [ + - + - : 3 : if(std::regex_match(tmp, mat, GetHeightPat())) { height = std::stoi(mat[1]); }
+ - + - +
- ]
578 : 3 : }
579 : :
580 : 7 : GlyphList result;
581 : 7 : result.height = height;
582 [ + - ]: 7 : result.widths.push_back(8);
583 : 7 : result.unicode = false;
584 : :
585 [ + + ]: 7 : if(width != 8) return result;
586 [ + + + + : 4 : static const std::regex hexpat("0x[0-9A-Fa-f]+", Ropt);
+ - ]
587 : :
588 [ + - + - ]: 4 : std::ifstream f{ std::string(filename) };
589 : 4 : unsigned glyph=0, row=0;
590 [ + - + + ]: 521 : for(std::string line; std::getline(f,line), f; )
591 [ + - + + ]: 12845 : for(auto b = line.cbegin(), e = line.cend(); std::regex_search(b,e, mat, hexpat); b = mat[0].second)
592 : : {
593 [ + - + - ]: 12474 : int val = std::stoi(mat[0], nullptr, 16); // accepts 0x prefix
594 [ + + ]: 12311 : if(!row)
595 [ + - + + ]: 2181 : for(auto unichar: BDFtranslateToUnicode(glyph, {}, guess_encoding))
596 [ + - ]: 2186 : result.glyphs.emplace(unichar, result.bitmap.size());
597 [ + - ]: 12313 : result.bitmap.push_back(val);
598 [ + + ]: 12325 : if(++row >= height) { row=0; ++glyph; }
599 : 0 : }
600 : 4 : return result;
601 : 11 : }
602 : :
603 : 2 : GlyphList Read_ASM(std::string_view filename, unsigned width, unsigned height, std::string_view guess_encoding)
604 : : {
605 [ + + ]: 2 : std::smatch mat;
606 [ + + ]: 2 : if(!height)
607 : : {
608 [ + - ]: 1 : std::string tmp(filename);
609 [ + - + - : 1 : if(std::regex_match(tmp, mat, GetHeightPat())) { height = std::stoi(mat[1]); }
+ - + - +
- ]
610 : 1 : }
611 : :
612 : 2 : GlyphList result;
613 : 2 : result.height = height;
614 [ + - ]: 2 : result.widths.push_back(8);
615 : 2 : result.unicode = false;
616 : :
617 [ + - ]: 2 : if(width != 8) return result;
618 [ + + + - : 2 : static const std::regex dbpat("[ \t]+[dD][bB][ \t]+([^;]+).*\r?\n?", Ropt);
+ - ]
619 [ + + + - : 2 : static const std::regex hexpat("[0-9A-Fa-f]+", Ropt);
+ - ]
620 : :
621 : 2 : unsigned glyph=0, row=0;
622 [ + - + - ]: 2 : std::ifstream f{ std::string(filename) };
623 [ + - + + ]: 556 : for(std::string line; std::getline(f,line), f; )
624 [ + - + + ]: 552 : if(std::regex_match(line, mat, dbpat))
625 : : {
626 [ + - + + ]: 7680 : for(auto b = mat[1].first, e = mat[1].second; std::regex_search(b,e, mat, hexpat); b = mat[0].second)
627 : : {
628 [ + - + - ]: 7168 : int val = std::stoi(std::string(mat[0].first, mat[0].second), nullptr, 16);
629 [ + + ]: 7168 : if(!row)
630 [ + - + + ]: 1154 : for(auto unichar: BDFtranslateToUnicode(glyph, {}, guess_encoding))
631 [ + - ]: 1154 : result.glyphs.emplace(unichar, result.bitmap.size());
632 [ + - ]: 7168 : result.bitmap.push_back(val);
633 [ + + ]: 7168 : if(++row >= height) { row=0; ++glyph; }
634 : : }
635 : 0 : }
636 : 2 : return result;
637 : 4 : }
638 : :
639 : 249 : GlyphList::GlyphList()
640 : : {
641 : 249 : }
642 : :
643 : 246 : GlyphList Read_Font(std::filesystem::path filename, unsigned width, unsigned height,
644 : : bool find, std::string_view guess_encoding)
645 : : {
646 [ + + ]: 246 : if(find)
647 : : {
648 [ + - + - : 225 : auto [location, status] = FindShareFile(std::filesystem::path("fonts") / "files" / filename);
+ - + - +
- ]
649 [ + - ]: 45 : filename = location;
650 : 45 : }
651 : :
652 : 492 : std::string ext = filename.extension();
653 [ + + + - : 253 : if(ext == ".inc"sv) return Read_Inc(std::string(filename), width, height, guess_encoding);
+ - ]
654 [ + + + - : 240 : if(ext == ".asm"sv) return Read_ASM(std::string(filename), width, height, guess_encoding);
+ - ]
655 [ + + + - : 319 : if(ext == ".bdf"sv) return Read_BDF(std::string(filename), width, height, guess_encoding);
+ - ]
656 [ + - + - ]: 314 : return Read_PSFgz(std::string(filename), width, height, guess_encoding);
657 : 246 : }
658 : :
659 : : #ifdef RUN_TESTS
660 : : // Do tests for endian.hh . Use volatile to avoid inline-switch-case thwarting coverage tests.
661 : 4 : TEST(endian, bswap16) { EXPECT_EQ(BSwap16(0x1234u), 0x3412u); }
662 : 4 : TEST(endian, bswap32) { EXPECT_EQ(BSwap32(0x12345678u), 0x78563412u); }
663 : 4 : TEST(endian, bswap64) { EXPECT_EQ(BSwap64(0x12345678ABCDEF01ull), 0x01EFCDAB78563412ull); }
664 : 4 : TEST(endian, R8) { EXPECT_EQ(R8("K"), 0x4Bu); }
665 : 4 : TEST(endian, R16) { EXPECT_EQ(R16(" A"), 0x4120u); }
666 : 4 : TEST(endian, R24) { EXPECT_EQ(R24(" AB"), 0x424120u); }
667 : 4 : TEST(endian, R32) { EXPECT_EQ(R32(" ABC"), 0x43424120u); }
668 : 4 : TEST(endian, R64) { EXPECT_EQ(R64(" ABC0123"), 0x3332313043424120ull); }
669 : 4 : TEST(endian, R16r) { EXPECT_EQ(R16r(" A"), 0x2041u); }
670 : 4 : TEST(endian, R24r) { EXPECT_EQ(R24r(" AB"), 0x204142u); }
671 : 4 : TEST(endian, R32r) { EXPECT_EQ(R32r(" ABC"), 0x20414243u); }
672 : 4 : TEST(endian, R64r) { EXPECT_EQ(R64r(" ABC0123"), 0x2041424330313233ull); }
673 : 3 : TEST(endian, Rn) { char data[] = " ABC0123";
674 : 1 : volatile unsigned n=0;
675 : 1 : n=n+1; EXPECT_EQ(Rn(data, n), 0x20u);
676 : 1 : n=n+1; EXPECT_EQ(Rn(data, n), 0x4120u);
677 : 1 : n=n+1; EXPECT_EQ(Rn(data, n), 0x424120u);
678 : 1 : n=n+1; EXPECT_EQ(Rn(data, n), 0x43424120u);
679 : 1 : n=n+1; EXPECT_EQ(Rn(data, n), 0x3043424120u);
680 : 1 : n=n+1; EXPECT_EQ(Rn(data, n), 0x313043424120u);
681 : 1 : n=n+1; EXPECT_EQ(Rn(data, n), 0x32313043424120u);
682 : 2 : n=n+1; EXPECT_EQ(Rn(data, n), 0x3332313043424120u); }
683 : 3 : TEST(endian, Wn) { char data[] = " ABC0123";
684 : 1 : volatile unsigned n=0;
685 : 2 : { char buf[16]{}; n=n+1; Wn(buf, 0x20u, n); EXPECT_EQ(std::string(buf,n), std::string(data,n)); }
686 : 2 : { char buf[16]{}; n=n+1; Wn(buf, 0x4120u, n); EXPECT_EQ(std::string(buf,n), std::string(data,n)); }
687 : 2 : { char buf[16]{}; n=n+1; Wn(buf, 0x424120u, n); EXPECT_EQ(std::string(buf,n), std::string(data,n)); }
688 : 2 : { char buf[16]{}; n=n+1; Wn(buf, 0x43424120u, n); EXPECT_EQ(std::string(buf,n), std::string(data,n)); }
689 : 2 : { char buf[16]{}; n=n+1; Wn(buf, 0x3043424120u, n); EXPECT_EQ(std::string(buf,n), std::string(data,n)); }
690 : 2 : { char buf[16]{}; n=n+1; Wn(buf, 0x313043424120u, n); EXPECT_EQ(std::string(buf,n), std::string(data,n)); }
691 : 2 : { char buf[16]{}; n=n+1; Wn(buf, 0x32313043424120u, n); EXPECT_EQ(std::string(buf,n), std::string(data,n)); }
692 : 2 : { char buf[16]{}; n=n+1; Wn(buf, 0x3332313043424120u, n); EXPECT_EQ(std::string(buf,n), std::string(data,n)); }
693 : 1 : }
694 : 3 : TEST(read_font, coverage)
695 : : {
696 : 1 : Read_PSFgzEncoding("share/fonts/files/iso01.f08.psf.gz"sv);
697 : 1 : Read_BDF("share/fonts/files/share/fonts/files/4x5.bdf"sv, 0,0, "iso10646-1"sv);
698 : 1 : Read_BDF("share/fonts/files/share/fonts/files/4x5.bdf"sv, 4,0, "ascii"sv);
699 : 1 : Read_ASM("test/860-8x14.asm"sv, 8, 14, "cp860"sv); // Give height
700 [ + - ]: 1 : Read_Font("test/860-8x14.asm"sv, 8, 0, false, "cp860"sv); // Test height guessing
701 : 1 : }
702 : : #endif
|