1 /*
2 * This file is part of EasyRPG Player.
3 *
4 * EasyRPG Player is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * EasyRPG Player is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with EasyRPG Player. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 // Headers
19 #include <map>
20 #include <type_traits>
21 #include <vector>
22 #include <iterator>
23
24 #include "system.h"
25 #include "game_system.h"
26 #include "main_data.h"
27
28 #ifdef HAVE_FREETYPE
29 # include <ft2build.h>
30 # include FT_FREETYPE_H
31 # include FT_BITMAP_H
32 # include FT_MODULE_H
33 #endif
34
35 #include <lcf/reader_util.h>
36 #include "bitmapfont.h"
37
38 #include "filefinder.h"
39 #include "output.h"
40 #include "font.h"
41 #include "bitmap.h"
42 #include "utils.h"
43 #include "cache.h"
44 #include "player.h"
45 #include "compiler.h"
46
47 // Static variables.
48 namespace {
49 template <typename T>
find_glyph(const T & glyphset,char32_t code)50 BitmapFontGlyph const* find_glyph(const T& glyphset, char32_t code) {
51 auto iter = std::lower_bound(std::begin(glyphset), std::end(glyphset), code);
52 if(iter != std::end(glyphset) && iter->code == code) {
53 return &*iter;
54 } else {
55 return NULL;
56 }
57 }
58
59 // This is the last-resort function for finding a glyph, all the other fonts should fallback on it.
60 // It tries to display a WenQuanYi glyph, and if it’s not found, returns a replacement glyph.
find_fallback_glyph(char32_t code)61 BitmapFontGlyph const* find_fallback_glyph(char32_t code) {
62 auto* wqy = find_glyph(BITMAPFONT_WQY, code);
63 if (wqy != NULL) {
64 return wqy;
65 }
66 else {
67 static BitmapFontGlyph const replacement_glyph = { 65533, true, { 96, 240, 504, 924, 1902, 3967, 4031, 1982, 1020, 440, 240, 96 } };
68 Output::Debug("glyph not found: {:#x}", uint32_t(code));
69 return &replacement_glyph;
70 }
71 }
72
find_gothic_glyph(char32_t code)73 BitmapFontGlyph const* find_gothic_glyph(char32_t code) {
74 auto* gothic = find_glyph(SHINONOME_GOTHIC, code);
75 return gothic != NULL ? gothic : find_fallback_glyph(code);
76 }
77
find_mincho_glyph(char32_t code)78 BitmapFontGlyph const* find_mincho_glyph(char32_t code) {
79 auto* mincho = find_glyph(SHINONOME_MINCHO, code);
80 return mincho == NULL ? find_gothic_glyph(code) : mincho;
81 }
82
find_rmg2000_glyph(char32_t code)83 BitmapFontGlyph const* find_rmg2000_glyph(char32_t code) {
84 auto* rmg2000 = find_glyph(BITMAPFONT_RMG2000, code);
85 if (rmg2000 != NULL) {
86 return rmg2000;
87 }
88
89 auto* ttyp0 = find_glyph(BITMAPFONT_TTYP0, code);
90 return ttyp0 != NULL ? ttyp0 : find_mincho_glyph(code);
91 }
92
find_ttyp0_glyph(char32_t code)93 BitmapFontGlyph const* find_ttyp0_glyph(char32_t code) {
94 auto* ttyp0 = find_glyph(BITMAPFONT_TTYP0, code);
95 return ttyp0 != NULL ? ttyp0 : find_gothic_glyph(code);
96 }
97
98 struct BitmapFont : public Font {
99 enum { HEIGHT = 12, FULL_WIDTH = HEIGHT, HALF_WIDTH = FULL_WIDTH / 2 };
100
101 using function_type = BitmapFontGlyph const*(*)(char32_t);
102
103 BitmapFont(const std::string& name, function_type func);
104
105 Rect GetSize(StringView txt) const override;
106 Rect GetSize(char32_t ch) const override;
107
108 GlyphRet Glyph(char32_t code) override;
109
110 private:
111 function_type func;
112 BitmapRef glyph_bm;
113 }; // class BitmapFont
114
115 #ifdef HAVE_FREETYPE
116 typedef std::map<std::string, std::weak_ptr<std::remove_pointer<FT_Face>::type>> face_cache_type;
117 face_cache_type face_cache;
118
delete_face(FT_Face f)119 void delete_face(FT_Face f) {
120 if(FT_Done_Face(f) != FT_Err_Ok) {
121 Output::Warning("FT_Face deleting error.");
122 }
123 }
124
delete_library(FT_Library f)125 void delete_library(FT_Library f) {
126 if(FT_Done_Library(f) != FT_Err_Ok) {
127 Output::Warning("FT_Library deleting error.");
128 }
129 }
130
131 struct FTFont : public Font {
132 FTFont(const std::string& name, int size, bool bold, bool italic);
133
134 Rect GetSize(StringView txt) const override;
135 Rect GetSize(char32_t ch) const override;
136
137 GlyphRet Glyph(char32_t code) override;
138
139 private:
140 static std::weak_ptr<std::remove_pointer<FT_Library>::type> library_checker_;
141 std::shared_ptr<std::remove_pointer<FT_Library>::type> library_;
142 std::shared_ptr<std::remove_pointer<FT_Face>::type> face_;
143 std::string face_name_;
144 unsigned current_size_;
145
146 bool check_face();
147 }; // class FTFont
148 #endif
149
150 /* Bitmap fonts used for the official Japanese version.
151 Compatible with MS Gothic and MS Mincho. Feature a closing quote in place of straight quote,
152 double-width Cyrillic letters (unusable for Russian, only useful for smileys and things like that)
153 and ellipsis in the middle of the line.
154 */
155 FontRef const gothic = std::make_shared<BitmapFont>("Shinonome Gothic", &find_gothic_glyph);
156 FontRef const mincho = std::make_shared<BitmapFont>("Shinonome Mincho", &find_mincho_glyph);
157
158 /* Bitmap fonts used for non-Japanese games.
159 Compatible with RMG2000 and RM2000 shipped with Don Miguel’s unofficial translation.
160 Feature a half-width Cyrillic and half-width ellipsis at the bottom of the line.
161 */
162 FontRef const rmg2000 = std::make_shared<BitmapFont>("RMG2000-compatible", &find_rmg2000_glyph);
163 FontRef const ttyp0 = std::make_shared<BitmapFont>("ttyp0", &find_ttyp0_glyph);
164
165 struct ExFont : public Font {
166 public:
167 enum { HEIGHT = 12, WIDTH = 12 };
168 ExFont();
169 Rect GetSize(StringView txt) const override;
170 Rect GetSize(char32_t ch) const override;
171 GlyphRet Glyph(char32_t code) override;
172 private:
173 BitmapRef bm;
174 };
175 } // anonymous namespace
176
BitmapFont(const std::string & name,function_type func)177 BitmapFont::BitmapFont(const std::string& name, function_type func)
178 : Font(name, HEIGHT, false, false), func(func)
179 {}
180
GetSize(char32_t ch) const181 Rect BitmapFont::GetSize(char32_t ch) const {
182 size_t units = 0;
183 if (EP_LIKELY(!Utils::IsControlCharacter(ch))) {
184 auto glyph = func(ch);
185 units += glyph->is_full? 2 : 1;
186 }
187 return Rect(0, 0, units * HALF_WIDTH, HEIGHT);
188 }
189
GetSize(StringView txt) const190 Rect BitmapFont::GetSize(StringView txt) const {
191 size_t units = 0;
192 const auto* iter = txt.data();
193 const auto* end = txt.data() + txt.size();
194 while (iter != end) {
195 auto resp = Utils::UTF8Next(iter, end);
196 auto ch = resp.ch;
197 iter = resp.next;
198 if (EP_LIKELY(!Utils::IsControlCharacter(resp.ch))) {
199 auto glyph = func(ch);
200 units += glyph->is_full? 2 : 1;
201 }
202 }
203 return Rect(0, 0, units * HALF_WIDTH, HEIGHT);
204 }
205
Glyph(char32_t code)206 Font::GlyphRet BitmapFont::Glyph(char32_t code) {
207 if (EP_UNLIKELY(!glyph_bm)) {
208 glyph_bm = Bitmap::Create(nullptr, FULL_WIDTH, HEIGHT, 0, DynamicFormat(8,8,0,8,0,8,0,8,0,PF::Alpha));
209 }
210 if (EP_UNLIKELY(Utils::IsControlCharacter(code))) {
211 return { glyph_bm, Rect(0, 0, 0, HEIGHT) };
212 }
213 auto glyph = func(code);
214 auto width = glyph->is_full? FULL_WIDTH : HALF_WIDTH;
215
216 glyph_bm->Clear();
217 uint8_t* data = reinterpret_cast<uint8_t*>(glyph_bm->pixels());
218 int pitch = glyph_bm->pitch();
219 for(size_t y_ = 0; y_ < HEIGHT; ++y_)
220 for(size_t x_ = 0; x_ < width; ++x_)
221 data[y_*pitch+x_] = (glyph->data[y_] & (0x1 << x_)) ? 255 : 0;
222
223 return { glyph_bm, Rect(0, 0, width, HEIGHT) };
224 }
225
226 #ifdef HAVE_FREETYPE
227 std::weak_ptr<std::remove_pointer<FT_Library>::type> FTFont::library_checker_;
228
FTFont(const std::string & name,int size,bool bold,bool italic)229 FTFont::FTFont(const std::string& name, int size, bool bold, bool italic)
230 : Font(name, size, bold, italic), current_size_(0) {}
231
GetSize(StringView txt) const232 Rect FTFont::GetSize(StringView txt) const {
233 int const s = Font::Default()->GetSize(txt).width;
234
235 if (s == -1) {
236 Output::Warning("Text contains invalid chars. Is the encoding correct?");
237
238 return Rect(0, 0, pixel_size() * txt.length() / 2, pixel_size());
239 } else {
240 return Rect(0, 0, s, pixel_size());
241 }
242 }
243
GetSize(char32_t ch) const244 Rect FTFont::GetSize(char32_t ch) const {
245 int const s = Font::Default()->GetSize(ch).width;
246
247 if (s == -1) {
248 Output::Warning("Text contains invalid chars. Is the encoding correct?");
249
250 return Rect(0, 0, pixel_size() / 2, pixel_size());
251 } else {
252 return Rect(0, 0, s, pixel_size());
253 }
254 }
255
256
257
Glyph(char32_t glyph)258 Font::GlyphRet FTFont::Glyph(char32_t glyph) {
259 if(!check_face()) {
260 return Font::Default()->Glyph(glyph);
261 }
262
263 if (FT_Load_Char(face_.get(), glyph, FT_LOAD_NO_BITMAP) != FT_Err_Ok) {
264 Output::Error("Couldn't load FreeType character {:#x}", uint32_t(glyph));
265 }
266
267 if (FT_Render_Glyph(face_->glyph, FT_RENDER_MODE_MONO) != FT_Err_Ok) {
268 Output::Error("Couldn't render FreeType character {:#x}", uint32_t(glyph));
269 }
270
271 FT_Bitmap const& ft_bitmap = face_->glyph->bitmap;
272 assert(face_->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO);
273
274 size_t const pitch = std::abs(ft_bitmap.pitch);
275 int const width = ft_bitmap.width;
276 int const height = ft_bitmap.rows;
277
278 BitmapRef bm = Bitmap::Create(nullptr, width, height, 0, DynamicFormat(8,8,0,8,0,8,0,8,0,PF::Alpha));
279 uint8_t* data = reinterpret_cast<uint8_t*>(bm->pixels());
280 int dst_pitch = bm->pitch();
281
282 for(int row = 0; row < height; ++row) {
283 for(int col = 0; col < width; ++col) {
284 unsigned c = ft_bitmap.buffer[pitch * row + (col/8)];
285 unsigned bit = 7 - (col%8);
286 data[row * dst_pitch + col] = (c & (0x01 << bit)) ? 255 : 0;
287 }
288 }
289
290 return { bm, Rect(0, 0, width, height) };
291 }
292
check_face()293 bool FTFont::check_face() {
294 if (!library_) {
295 if (library_checker_.expired()) {
296 FT_Library lib;
297 if (FT_Init_FreeType(&lib) != FT_Err_Ok) {
298 Output::Error("Couldn't initialize FreeType");
299 return false;
300 }
301 library_.reset(lib, delete_library);
302 library_checker_ = library_;
303 } else {
304 library_ = library_checker_.lock();
305 }
306 }
307
308 if (!face_ || face_name_ != name) {
309 face_cache_type::const_iterator it = face_cache.find(name);
310 if (it == face_cache.end() || it->second.expired()) {
311 std::string const face_path = FileFinder::FindFont(name);
312 FT_Face face;
313 if (FT_New_Face(library_.get(), face_path.c_str(), 0, &face) != FT_Err_Ok) {
314 Output::Error("Couldn't initialize FreeType face: {}({})",
315 name, face_path);
316 return false;
317 }
318
319 for (int i = 0; i < face_->num_fixed_sizes; i++) {
320 FT_Bitmap_Size* size = &face_->available_sizes[i];
321 Output::Debug("Font Size {}: {} {} {} {} {}", i,
322 size->width, size->height, size->size / 64.0,
323 size->x_ppem / 64.0, size->y_ppem / 64.0);
324 }
325
326 face_.reset(face, delete_face);
327 face_cache[name] = face_;
328 } else {
329 face_ = it->second.lock();
330 }
331 face_name_ = name;
332 }
333
334 face_->style_flags =
335 (bold ? FT_STYLE_FLAG_BOLD : 0) |
336 (italic ? FT_STYLE_FLAG_ITALIC : 0);
337
338 if (current_size_ != size) {
339 int sz, dpi;
340 if (face_->num_fixed_sizes == 1) {
341 sz = face_->available_sizes[0].size;
342 dpi = 96;
343 } else {
344 sz = size * 64;
345 dpi = 72;
346 }
347
348 if (FT_Set_Char_Size(face_.get(), sz, sz, dpi, dpi) != FT_Err_Ok) {
349 Output::Error("Couldn't set FreeType face size");
350 return false;
351 }
352 current_size_ = size;
353 }
354
355 return true;
356 }
357 #endif
358
Default()359 FontRef Font::Default() {
360 const bool mincho = (Main_Data::game_system && Main_Data::game_system->GetFontId() == lcf::rpg::System::Font_mincho);
361 return Default(mincho);
362 }
363
Default(bool const m)364 FontRef Font::Default(bool const m) {
365 if (Player::IsCJK()) {
366 return m ? mincho : gothic;
367 }
368 else {
369 return m ? rmg2000 : ttyp0;
370 }
371 }
372
Create(const std::string & name,int size,bool bold,bool italic)373 FontRef Font::Create(const std::string& name, int size, bool bold, bool italic) {
374 #ifdef HAVE_FREETYPE
375 return std::make_shared<FTFont>(name, size, bold, italic);
376 #else
377 (void)name; (void)size; (void)bold; (void)italic;
378 return Font::Default();
379 #endif
380 }
381
Dispose()382 void Font::Dispose() {
383 #ifdef HAVE_FREETYPE
384 for(face_cache_type::const_iterator i = face_cache.begin(); i != face_cache.end(); ++i) {
385 if(i->second.expired()) { continue; }
386 Output::Debug("possible leak in cached font face {}", i->first);
387 }
388 face_cache.clear();
389 #endif
390 }
391
392 // Constructor.
Font(const std::string & name,int size,bool bold,bool italic)393 Font::Font(const std::string& name, int size, bool bold, bool italic)
394 : name(name)
395 , size(size)
396 , bold(bold)
397 , italic(italic)
398 {
399 }
400
Render(Bitmap & dest,int const x,int const y,const Bitmap & sys,int color,char32_t code)401 Rect Font::Render(Bitmap& dest, int const x, int const y, const Bitmap& sys, int color, char32_t code) {
402 auto gret = Glyph(code);
403
404 auto rect = Rect(x, y, gret.rect.width, gret.rect.height);
405 if (EP_UNLIKELY(rect.width == 0)) {
406 return rect;
407 }
408
409 if(color != ColorShadow) {
410 auto shadow_rect = Rect(x + 1, y + 1, rect.width, rect.height);
411 dest.MaskedBlit(shadow_rect, *gret.bitmap, 0, 0, sys, 16, 32);
412 }
413
414 unsigned const
415 src_x = color == ColorShadow? 16 : color % 10 * 16 + 2,
416 src_y = color == ColorShadow? 32 : color / 10 * 16 + 48 + 16 - gret.bitmap->height();
417
418
419 dest.MaskedBlit(rect, *gret.bitmap, 0, 0, sys, src_x, src_y);
420
421 return rect;
422 }
423
Render(Bitmap & dest,int x,int y,Color const & color,char32_t code)424 Rect Font::Render(Bitmap& dest, int x, int y, Color const& color, char32_t code) {
425 auto gret = Glyph(code);
426
427 auto rect = Rect(x, y, gret.rect.width, gret.rect.height);
428 dest.MaskedBlit(rect, *gret.bitmap, 0, 0, color);
429
430 return rect;
431 }
432
ExFont()433 ExFont::ExFont() : Font("exfont", 12, false, false) {
434 }
435
436 FontRef Font::exfont = std::make_shared<ExFont>();
437
Glyph(char32_t code)438 Font::GlyphRet ExFont::Glyph(char32_t code) {
439 if (EP_UNLIKELY(!bm)) { bm = Bitmap::Create(WIDTH, HEIGHT, true); }
440 auto exfont = Cache::Exfont();
441
442 Rect const rect((code % 13) * WIDTH, (code / 13) * HEIGHT, WIDTH, HEIGHT);
443 bm->Clear();
444 bm->Blit(0, 0, *exfont, rect, Opacity::Opaque());
445 return { bm, Rect(0, 0, WIDTH, HEIGHT) };
446 }
447
GetSize(StringView) const448 Rect ExFont::GetSize(StringView) const {
449 return Rect(0, 0, 12, 12);
450 }
451
GetSize(char32_t) const452 Rect ExFont::GetSize(char32_t) const {
453 return Rect(0, 0, 12, 12);
454 }
455