Redo text rendering (adds halo support to gui.text())
[lsnes.git] / src / library / framebuffer-font2.cpp
blob5d48d4fbe58b1a5a98ec593077e6719c0c7ec7cf
1 #include "framebuffer-font2.hpp"
2 #include "range.hpp"
3 #include "serialization.hpp"
4 #include <cstring>
5 #include "zip.hpp"
6 #include "string.hpp"
8 namespace framebuffer
10 namespace
12 inline bool readfont(const font2::glyph& fglyph, uint32_t xp1, uint32_t yp1)
14 if(xp1 < 1 || xp1 > fglyph.width || yp1 < 1 || yp1 > fglyph.height)
15 return false;
16 xp1--;
17 yp1--;
18 size_t ge = yp1 * fglyph.stride + (xp1 / 32);
19 size_t gb = 31 - xp1 % 32;
20 return ((fglyph.fglyph[ge] >> gb) & 1);
23 template<bool T> void _render(const font2::glyph& fglyph, fb<T>& fb, int32_t x, int32_t y,
24 color fg, color bg, color hl)
26 uint32_t _x = x;
27 uint32_t _y = y;
28 if(hl) {
29 _x--;
30 _y--;
31 range bX = (range::make_w(fb.get_width()) - _x) & range::make_w(fglyph.width + 2);
32 range bY = (range::make_w(fb.get_height()) - _y) & range::make_w(fglyph.height + 2);
33 for(unsigned i = bY.low(); i < bY.high(); i++) {
34 auto p = fb.rowptr(i + _y) + (_x + bX.low());
35 for(unsigned j = bX.low(); j < bX.high(); j++) {
36 bool in_halo = false;
37 in_halo |= readfont(fglyph, j - 1, i - 1);
38 in_halo |= readfont(fglyph, j, i - 1);
39 in_halo |= readfont(fglyph, j + 1, i - 1);
40 in_halo |= readfont(fglyph, j - 1, i );
41 in_halo |= readfont(fglyph, j + 1, i );
42 in_halo |= readfont(fglyph, j - 1, i + 1);
43 in_halo |= readfont(fglyph, j, i + 1);
44 in_halo |= readfont(fglyph, j + 1, i + 1);
45 if(readfont(fglyph, j, i))
46 fg.apply(p[j]);
47 else if(in_halo)
48 hl.apply(p[j]);
49 else
50 bg.apply(p[j]);
54 } else {
55 range bX = (range::make_w(fb.get_width()) - _x) & range::make_w(fglyph.width);
56 range bY = (range::make_w(fb.get_height()) - _y) & range::make_w(fglyph.height);
57 for(unsigned i = bY.low(); i < bY.high(); i++) {
58 auto p = fb.rowptr(i + _y) + (_x + bX.low());
59 for(unsigned j = bX.low(); j < bX.high(); j++) {
60 size_t ge = i * fglyph.stride + (j / 32);
61 size_t gb = 31 - j % 32;
62 if((fglyph.fglyph[ge] >> gb) & 1)
63 fg.apply(p[j]);
64 else
65 bg.apply(p[j]);
72 font2::glyph::glyph()
74 stride = width = height = 0;
77 font2::glyph::glyph(std::istream& s)
79 char header[40];
80 bool old = true;
81 bool upside_down = true;
82 size_t rcount = 26;
83 s.read(header, 26);
84 if(!s)
85 throw std::runtime_error("Can't read glyph bitmap header");
86 if(serialization::u16l(header + 0) != 0x4D42)
87 throw std::runtime_error("Bad glyph BMP magic");
88 if(serialization::u16l(header + 14) != 12) {
89 //Not OS/2 format.
90 old = false;
91 rcount = 40;
92 s.read(header + 26, 14);
93 if(!s)
94 throw std::runtime_error("Can't read glyph bitmap header");
97 uint32_t startoff = serialization::u32l(header + 10);
98 if(old) {
99 width = serialization::u16l(header + 18);
100 height = serialization::u16l(header + 20);
101 if(serialization::u16l(header + 22) != 1)
102 throw std::runtime_error("Bad glyph BMP planecount");
103 if(serialization::u16l(header + 24) != 1)
104 throw std::runtime_error("Bad glyph BMP bitdepth");
105 if(startoff < 26)
106 throw std::runtime_error("Glyph BMP data can't overlap header");
107 } else {
108 long _width = serialization::s32l(header + 18);
109 long _height = serialization::s32l(header + 22);
110 if(_width < 0)
111 throw std::runtime_error("Bad glyph BMP size");
112 if(_height < 0)
113 upside_down = false;
114 width = _width;
115 height = (_height >= 0) ? height : -height;
117 if(serialization::u16l(header + 26) != 1)
118 throw std::runtime_error("Bad glyph BMP planecount");
119 if(serialization::u16l(header + 28) != 1)
120 throw std::runtime_error("Bad glyph BMP bitdepth");
121 if(serialization::u32l(header + 30) != 0)
122 throw std::runtime_error("Bad glyph BMP compression method");
123 if(startoff < 40)
124 throw std::runtime_error("Glyph BMP data can't overlap header");
126 //Discard data until start of bitmap.
127 while(rcount < startoff) {
128 s.get();
129 if(!s)
130 throw std::runtime_error("EOF while skipping to BMP data");
131 rcount++;
133 stride = (width + 31) / 32;
134 fglyph.resize(stride * height);
135 memset(&fglyph[0], 0, sizeof(uint32_t) * fglyph.size());
136 size_t toskip = (4 - ((width + 7) / 8) % 4) % 4;
137 for(size_t i = 0; i < height; i++) {
138 size_t y = upside_down ? (height - i - 1) : i;
139 size_t bpos = y * stride * 32;
140 for(size_t j = 0; j < width; j += 8) {
141 size_t e = (bpos + j) / 32;
142 size_t b = (bpos + j) % 32;
143 int c = s.get();
144 if(!s)
145 throw std::runtime_error("EOF while reading BMP data");
146 fglyph[e] |= ((uint32_t)c << (24 - b));
148 for(size_t j = 0; j < toskip; j++) {
149 s.get();
150 if(!s)
151 throw std::runtime_error("EOF while reading BMP data");
156 void font2::glyph::render(fb<false>& fb, int32_t x, int32_t y, color fg,
157 color bg, color hl) const
159 _render(*this, fb, x, y, fg, bg, hl);
162 void font2::glyph::render(fb<true>& fb, int32_t x, int32_t y, color fg,
163 color bg, color hl) const
165 _render(*this, fb, x, y, fg, bg, hl);
168 void font2::glyph::render(uint8_t* buf, size_t _stride, uint32_t u, uint32_t v, uint32_t w, uint32_t h) const
170 //Clip the bounding box to valid range.
171 u = std::min(u, (uint32_t)width);
172 v = std::min(v, (uint32_t)height);
173 w = std::min(w, (uint32_t)width);
174 h = std::min(h, (uint32_t)height);
175 if(u + w > width) w = width - u;
176 if(v + h > height) h = height - v;
177 if(!w || !h) return;
178 //Do the actual render.
179 size_t ge = v * stride;
180 for(unsigned j = 0; j < h; j++) {
181 buf += _stride;
182 ge += stride;
183 for(unsigned i = 0; i < w; i++) {
184 unsigned dx = u + i;
185 size_t gb = 31 - (dx & 31);
186 buf[i] = (fglyph[ge + (dx >> 5)] >> gb) & 1;
193 font2::font2()
195 rowadvance = 0;
198 font2::font2(const std::string& file)
200 std::istream* toclose = NULL;
201 rowadvance = 0;
202 try {
203 zip::reader r(file);
204 for(auto member : r) {
205 //Parse the key out of filename.
206 std::u32string key;
207 std::string tname = member;
208 std::string tmp;
209 if(tname == "bad") {
210 //Special, no key.
211 } else if(regex_match("[0-9]+(-[0-9]+)*", tname))
212 for(auto& tmp : token_iterator<char>::foreach(tname, {"-"}))
213 key.append(1, parse_value<uint32_t>(tmp));
214 else {
215 delete toclose;
216 toclose = NULL;
217 continue;
219 std::istream& s = r[member];
220 toclose = &s;
221 try {
222 add(key, glyph(s));
223 } catch(std::bad_alloc& e) {
224 throw;
225 } catch(std::exception& e) {
226 throw std::runtime_error(tname + std::string(": ") + e.what());
228 delete toclose;
229 toclose = NULL;
231 } catch(std::bad_alloc& e) {
232 if(toclose)
233 delete toclose;
234 throw;
235 } catch(std::exception& e) {
236 if(toclose)
237 delete toclose;
238 throw std::runtime_error(std::string("Error reading font: ") + e.what());
242 font2::font2(struct font& bfont)
244 auto s = bfont.get_glyphs_set();
245 for(auto i = s.begin();;i++) {
246 const font::glyph& j = (i != s.end()) ? bfont.get_glyph(*i) : bfont.get_bad_glyph();
247 glyph k;
248 k.width = j.wide ? 16 : 8;
249 k.height = 16;
250 k.stride = 1;
251 k.fglyph.resize(16);
252 for(size_t y = 0; y < 16; y++) {
253 k.fglyph[y] = 0;
254 uint32_t r = j.data[y / (j.wide ? 2 : 4)];
255 if(j.wide)
256 r >>= 16 - ((y & 1) << 4);
257 else
258 r >>= 24 - ((y & 3) << 3);
259 for(size_t x = 0; x < k.width; x++) {
260 uint32_t b = (j.wide ? 15 : 7) - x;
261 if(((r >> b) & 1) != 0)
262 k.fglyph[y] |= 1UL << (31 - x);
265 std::u32string key = (i != s.end()) ? std::u32string(1, *i) : std::u32string();
266 glyphs[key] = k;
267 if(i == s.end()) break;
269 rowadvance = 16;
272 std::ostream& operator<<(std::ostream& os, const std::u32string& lkey)
274 if(!lkey.length())
275 return (os << "bad");
276 for(size_t i = 0; i < lkey.length(); i++) {
277 if(i)
278 os << "-";
279 os << static_cast<uint32_t>(lkey[i]);
281 return os;
284 void font2::add(const std::u32string& key, const glyph& fglyph) throw(std::bad_alloc)
286 glyphs[key] = fglyph;
287 if(fglyph.height > rowadvance)
288 rowadvance = fglyph.height;
291 std::u32string font2::best_ligature_match(const std::u32string& codepoints, size_t start) const
292 throw(std::bad_alloc)
294 std::u32string tmp;
295 if(start >= codepoints.length())
296 return tmp; //Bad.
297 std::u32string best = tmp;
298 for(size_t i = 1; i <= codepoints.size() - start; i++) {
299 tmp.append(1, codepoints[start + i - 1]);
300 std::u32string lkey = tmp;
301 if(glyphs.count(lkey))
302 best = lkey;
303 auto j = glyphs.lower_bound(lkey);
304 //If lower_bound is greater than equivalent length of string, there can be no better match.
305 if(j == glyphs.end())
306 break;
307 const std::u32string& tmp2 = j->first;
308 bool best_found = false;
309 for(size_t k = 0; k < tmp2.length() && start + k < codepoints.length(); k++)
310 if(tmp2[k] > codepoints[start + k]) {
311 best_found = true;
312 break;
313 } else if(tmp2[k] < codepoints[start + k])
314 break;
315 if(best_found)
316 break;
318 return best;
321 const font2::glyph& font2::lookup_glyph(const std::u32string& key) const throw()
323 static glyph empty_glyph;
324 auto i = glyphs.find(key);
325 return (i == glyphs.end()) ? empty_glyph : i->second;
328 std::pair<uint32_t, uint32_t> font2::get_metrics(const std::u32string& str, uint32_t xalign) const
330 uint32_t w = 0;
331 uint32_t h = 0;
332 for_each_glyph(str, xalign, [&w, &h](uint32_t x, uint32_t y, const glyph& g) {
333 w = std::max(w, x + (uint32_t)g.width);
334 h = std::max(h, y + (uint32_t)g.height);
336 return std::make_pair(w, h);
339 void font2::for_each_glyph(const std::u32string& str, uint32_t xalign, std::function<void(uint32_t x, uint32_t y,
340 const glyph& g)> cb) const
342 uint32_t drawx = 0;
343 uint32_t orig_x = 0;
344 uint32_t drawy = 0;
345 for(size_t i = 0; i < str.size();) {
346 uint32_t cp = str[i];
347 std::u32string k = best_ligature_match(str, i);
348 const glyph& g = lookup_glyph(k);
349 if(k.length())
350 i += k.length();
351 else
352 i++;
353 if(cp == 9) {
354 drawx = (((drawx + xalign) + 64) >> 6 << 6) - xalign;
355 } else if(cp == 10) {
356 drawx = orig_x;
357 drawy += get_rowadvance();
358 } else {
359 cb(drawx, drawy, g);
360 drawx += g.width;