1 /*****************************************************************************
2  * Copyright (c) 2014-2020 OpenRCT2 developers
3  *
4  * For a complete list of all authors, please refer to contributors.md
5  * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6  *
7  * OpenRCT2 is licensed under the GNU General Public License version 3.
8  *****************************************************************************/
9 
10 #include "Font.h"
11 
12 #include "../localisation/FormatCodes.h"
13 #include "../localisation/Language.h"
14 #include "../localisation/LocalisationService.h"
15 #include "../sprites.h"
16 #include "Drawing.h"
17 #include "TTF.h"
18 
19 #include <iterator>
20 #include <unordered_map>
21 
22 static constexpr const int32_t SpriteFontLineHeight[FONT_SIZE_COUNT] = {
23     6,
24     10,
25     10,
26 };
27 
28 static uint8_t _spriteFontCharacterWidths[FONT_SIZE_COUNT][FONT_SPRITE_GLYPH_COUNT];
29 static uint8_t _additionalSpriteFontCharacterWidth[FONT_SIZE_COUNT][SPR_G2_GLYPH_COUNT] = {};
30 
31 #ifndef NO_TTF
32 TTFFontSetDescriptor* gCurrentTTFFontSet;
33 #endif // NO_TTF
34 
35 static const std::unordered_map<char32_t, int32_t> codepointOffsetMap = {
36     { UnicodeChar::ae_uc, SPR_G2_AE_UPPER - SPR_CHAR_START },
37     { UnicodeChar::o_stroke_uc, SPR_G2_O_STROKE_UPPER - SPR_CHAR_START },
38     { UnicodeChar::y_acute_uc, SPR_G2_Y_ACUTE_UPPER - SPR_CHAR_START },
39     { UnicodeChar::ae, SPR_G2_AE_LOWER - SPR_CHAR_START },
40     { UnicodeChar::o_stroke, SPR_G2_O_STROKE_LOWER - SPR_CHAR_START },
41     { UnicodeChar::y_acute, SPR_G2_Y_ACUTE_LOWER - SPR_CHAR_START },
42     { UnicodeChar::a_breve_uc, SPR_G2_A_BREVE_UPPER - SPR_CHAR_START },
43     { UnicodeChar::a_breve, 226 - CS_SPRITE_FONT_OFFSET }, // Render as â, no visual difference in the RCT font
44     { UnicodeChar::a_ogonek_uc, CSChar::a_ogonek_uc - CS_SPRITE_FONT_OFFSET },
45     { UnicodeChar::a_ogonek, CSChar::a_ogonek - CS_SPRITE_FONT_OFFSET },
46     { UnicodeChar::c_acute_uc, CSChar::c_acute_uc - CS_SPRITE_FONT_OFFSET },
47     { UnicodeChar::c_acute, CSChar::c_acute - CS_SPRITE_FONT_OFFSET },
48     { UnicodeChar::c_caron_uc, SPR_G2_C_CARON_UPPER - SPR_CHAR_START },
49     { UnicodeChar::c_caron, SPR_G2_C_CARON_LOWER - SPR_CHAR_START },
50     { UnicodeChar::d_caron_uc, SPR_G2_D_CARON_UPPER - SPR_CHAR_START },
51     { UnicodeChar::d_caron, SPR_G2_D_CARON_LOWER - SPR_CHAR_START },
52     { UnicodeChar::e_ogonek_uc, CSChar::e_ogonek_uc - CS_SPRITE_FONT_OFFSET },
53     { UnicodeChar::e_ogonek, CSChar::e_ogonek - CS_SPRITE_FONT_OFFSET },
54     { UnicodeChar::e_caron_uc, SPR_G2_E_CARON_UPPER - SPR_CHAR_START },
55     { UnicodeChar::e_caron, SPR_G2_E_CARON_LOWER - SPR_CHAR_START },
56     { UnicodeChar::g_breve_uc, SPR_G2_G_BREVE_UPPER - SPR_CHAR_START },
57     { UnicodeChar::g_breve, SPR_G2_G_BREVE_LOWER - SPR_CHAR_START },
58     { UnicodeChar::i_with_dot_uc, SPR_G2_I_WITH_DOT_UPPER - SPR_CHAR_START },
59     { UnicodeChar::i_without_dot, SPR_G2_I_WITHOUT_DOT_LOWER - SPR_CHAR_START },
60     { UnicodeChar::j, SPR_G2_J - SPR_CHAR_START },
61     { UnicodeChar::l, SPR_G2_L - SPR_CHAR_START },
62     { UnicodeChar::l_stroke_uc, CSChar::l_stroke_uc - CS_SPRITE_FONT_OFFSET },
63     { UnicodeChar::l_stroke, CSChar::l_stroke - CS_SPRITE_FONT_OFFSET },
64     { UnicodeChar::n_acute_uc, CSChar::n_acute_uc - CS_SPRITE_FONT_OFFSET },
65     { UnicodeChar::n_acute, CSChar::n_acute - CS_SPRITE_FONT_OFFSET },
66     { UnicodeChar::n_caron_uc, SPR_G2_N_CARON_UPPER - SPR_CHAR_START },
67     { UnicodeChar::n_caron, SPR_G2_N_CARON_LOWER - SPR_CHAR_START },
68     { UnicodeChar::o_macron, CSChar::o_circumflex - CS_SPRITE_FONT_OFFSET }, // No visual difference
69     { UnicodeChar::o_double_acute_uc, SPR_G2_O_DOUBLE_ACUTE_UPPER - SPR_CHAR_START },
70     { UnicodeChar::o_double_acute, SPR_G2_O_DOUBLE_ACUTE_LOWER - SPR_CHAR_START },
71     { UnicodeChar::oe_uc, SPR_G2_OE_UPPER - SPR_CHAR_START },
72     { UnicodeChar::oe, SPR_G2_OE_LOWER - SPR_CHAR_START },
73     { UnicodeChar::r_caron_uc, SPR_G2_R_CARON_UPPER - SPR_CHAR_START },
74     { UnicodeChar::r_caron, SPR_G2_R_CARON_LOWER - SPR_CHAR_START },
75     { UnicodeChar::s_acute_uc, CSChar::s_acute_uc - CS_SPRITE_FONT_OFFSET },
76     { UnicodeChar::s_acute, CSChar::s_acute - CS_SPRITE_FONT_OFFSET },
77     { UnicodeChar::s_cedilla_uc, SPR_G2_S_CEDILLA_UPPER - SPR_CHAR_START },
78     { UnicodeChar::s_cedilla, SPR_G2_S_CEDILLA_LOWER - SPR_CHAR_START },
79     { UnicodeChar::s_caron_uc, SPR_G2_S_CARON_UPPER - SPR_CHAR_START },
80     { UnicodeChar::s_caron, SPR_G2_S_CARON_LOWER - SPR_CHAR_START },
81     { UnicodeChar::t_caron_uc, SPR_G2_T_CARON_UPPER - SPR_CHAR_START },
82     { UnicodeChar::t_caron, SPR_G2_T_CARON_LOWER - SPR_CHAR_START },
83     { UnicodeChar::u_ring_uc, SPR_G2_U_RING_UPPER - SPR_CHAR_START },
84     { UnicodeChar::u_ring, SPR_G2_U_RING_LOWER - SPR_CHAR_START },
85     { UnicodeChar::u_double_acute_uc, SPR_G2_U_DOUBLE_ACUTE_UPPER - SPR_CHAR_START },
86     { UnicodeChar::u_double_acute, SPR_G2_U_DOUBLE_ACUTE_LOWER - SPR_CHAR_START },
87     { UnicodeChar::z_acute_uc, CSChar::z_acute_uc - CS_SPRITE_FONT_OFFSET },
88     { UnicodeChar::z_acute, CSChar::z_acute - CS_SPRITE_FONT_OFFSET },
89     { UnicodeChar::z_dot_uc, CSChar::z_dot_uc - CS_SPRITE_FONT_OFFSET },
90     { UnicodeChar::z_dot, CSChar::z_dot - CS_SPRITE_FONT_OFFSET },
91     { UnicodeChar::z_caron_uc, SPR_G2_Z_CARON_UPPER - SPR_CHAR_START },
92     { UnicodeChar::z_caron, SPR_G2_Z_CARON_LOWER - SPR_CHAR_START },
93     { UnicodeChar::f_with_hook_uc, 'F' - CS_SPRITE_FONT_OFFSET },
94     { UnicodeChar::s_comma_uc, SPR_G2_S_CEDILLA_UPPER - SPR_CHAR_START }, // No visual difference
95     { UnicodeChar::s_comma, SPR_G2_S_CEDILLA_LOWER - SPR_CHAR_START },    // Ditto
96     { UnicodeChar::t_comma_uc, SPR_G2_T_COMMA_UPPER - SPR_CHAR_START },
97     { UnicodeChar::t_comma, SPR_G2_T_COMMA_LOWER - SPR_CHAR_START },
98     { UnicodeChar::sharp_s_uc, 223 - CS_SPRITE_FONT_OFFSET },
99     { UnicodeChar::c_circumflex_uc, SPR_G2_C_CIRCUMFLEX_UPPER - SPR_CHAR_START },
100     { UnicodeChar::c_circumflex, SPR_G2_C_CIRCUMFLEX_LOWER - SPR_CHAR_START },
101     { UnicodeChar::g_circumflex_uc, SPR_G2_G_CIRCUMFLEX_UPPER - SPR_CHAR_START },
102     { UnicodeChar::g_circumflex, SPR_G2_G_CIRCUMFLEX_LOWER - SPR_CHAR_START },
103     { UnicodeChar::h_circumflex_uc, SPR_G2_H_CIRCUMFLEX_UPPER - SPR_CHAR_START },
104     { UnicodeChar::h_circumflex, SPR_G2_H_CIRCUMFLEX_LOWER - SPR_CHAR_START },
105     { UnicodeChar::j_circumflex_uc, SPR_G2_J_CIRCUMFLEX_UPPER - SPR_CHAR_START },
106     { UnicodeChar::j_circumflex, SPR_G2_J_CIRCUMFLEX_LOWER - SPR_CHAR_START },
107     { UnicodeChar::s_circumflex_uc, SPR_G2_S_CIRCUMFLEX_UPPER - SPR_CHAR_START },
108     { UnicodeChar::s_circumflex, SPR_G2_S_CIRCUMFLEX_LOWER - SPR_CHAR_START },
109     { UnicodeChar::u_breve_uc, SPR_G2_U_BREVE_UPPER - SPR_CHAR_START },
110     { UnicodeChar::u_breve, SPR_G2_U_BREVE_LOWER - SPR_CHAR_START },
111 
112     // Cyrillic alphabet
113     { UnicodeChar::cyrillic_io_uc, 203 - CS_SPRITE_FONT_OFFSET }, // Looks just like Ë
114     { UnicodeChar::cyrillic_a_uc, 'A' - CS_SPRITE_FONT_OFFSET },
115     { UnicodeChar::cyrillic_be_uc, SPR_G2_CYRILLIC_BE_UPPER - SPR_CHAR_START },
116     { UnicodeChar::cyrillic_ve_uc, 'B' - CS_SPRITE_FONT_OFFSET },
117     { UnicodeChar::cyrillic_ghe_uc, SPR_G2_CYRILLIC_GHE_UPPER - SPR_CHAR_START },
118     { UnicodeChar::cyrillic_de_uc, SPR_G2_CYRILLIC_DE_UPPER - SPR_CHAR_START },
119     { UnicodeChar::cyrillic_ie_uc, 'E' - CS_SPRITE_FONT_OFFSET },
120     { UnicodeChar::cyrillic_zhe_uc, SPR_G2_CYRILLIC_ZHE_UPPER - SPR_CHAR_START },
121     { UnicodeChar::cyrillic_ze_uc, SPR_G2_CYRILLIC_ZE_UPPER - SPR_CHAR_START },
122     { UnicodeChar::cyrillic_i_uc, SPR_G2_CYRILLIC_I_UPPER - SPR_CHAR_START },
123     { UnicodeChar::cyrillic_short_i_uc, SPR_G2_CYRILLIC_SHORT_I_UPPER - SPR_CHAR_START },
124     { UnicodeChar::cyrillic_ka_uc, 'K' - CS_SPRITE_FONT_OFFSET },
125     { UnicodeChar::cyrillic_el_uc, SPR_G2_CYRILLIC_EL_UPPER - SPR_CHAR_START },
126     { UnicodeChar::cyrillic_em_uc, 'M' - CS_SPRITE_FONT_OFFSET },
127     { UnicodeChar::cyrillic_en_uc, 'H' - CS_SPRITE_FONT_OFFSET },
128     { UnicodeChar::cyrillic_o_uc, 'O' - CS_SPRITE_FONT_OFFSET },
129     { UnicodeChar::cyrillic_pe_uc, SPR_G2_CYRILLIC_PE_UPPER - SPR_CHAR_START },
130     { UnicodeChar::cyrillic_er_uc, 'P' - CS_SPRITE_FONT_OFFSET },
131     { UnicodeChar::cyrillic_es_uc, 'C' - CS_SPRITE_FONT_OFFSET },
132     { UnicodeChar::cyrillic_te_uc, 'T' - CS_SPRITE_FONT_OFFSET },
133     { UnicodeChar::cyrillic_u_uc, SPR_G2_CYRILLIC_U_UPPER - SPR_CHAR_START },
134     { UnicodeChar::cyrillic_ef_uc, SPR_G2_CYRILLIC_EF_UPPER - SPR_CHAR_START },
135     { UnicodeChar::cyrillic_ha_uc, 'X' - CS_SPRITE_FONT_OFFSET },
136     { UnicodeChar::cyrillic_tse_uc, SPR_G2_CYRILLIC_TSE_UPPER - SPR_CHAR_START },
137     { UnicodeChar::cyrillic_che_uc, SPR_G2_CYRILLIC_CHE_UPPER - SPR_CHAR_START },
138     { UnicodeChar::cyrillic_sha_uc, SPR_G2_CYRILLIC_SHA_UPPER - SPR_CHAR_START },
139     { UnicodeChar::cyrillic_shcha_uc, SPR_G2_CYRILLIC_SHCHA_UPPER - SPR_CHAR_START },
140     { UnicodeChar::cyrillic_hard_sign_uc, SPR_G2_CYRILLIC_HARD_SIGN_UPPER - SPR_CHAR_START },
141     { UnicodeChar::cyrillic_yeru_uc, SPR_G2_CYRILLIC_YERU_UPPER - SPR_CHAR_START },
142     { UnicodeChar::cyrillic_soft_sign_uc, SPR_G2_CYRILLIC_SOFT_SIGN_UPPER - SPR_CHAR_START },
143     { UnicodeChar::cyrillic_e_uc, SPR_G2_CYRILLIC_E_UPPER - SPR_CHAR_START },
144     { UnicodeChar::cyrillic_yu_uc, SPR_G2_CYRILLIC_YU_UPPER - SPR_CHAR_START },
145     { UnicodeChar::cyrillic_ya_uc, SPR_G2_CYRILLIC_YA_UPPER - SPR_CHAR_START },
146 
147     { UnicodeChar::cyrillic_a, 'a' - CS_SPRITE_FONT_OFFSET },
148     { UnicodeChar::cyrillic_be, SPR_G2_CYRILLIC_BE_LOWER - SPR_CHAR_START },
149     { UnicodeChar::cyrillic_ve, SPR_G2_CYRILLIC_VE_LOWER - SPR_CHAR_START },
150     { UnicodeChar::cyrillic_ghe, SPR_G2_CYRILLIC_GHE_LOWER - SPR_CHAR_START },
151     { UnicodeChar::cyrillic_de, SPR_G2_CYRILLIC_DE_LOWER - SPR_CHAR_START },
152     { UnicodeChar::cyrillic_ie, 'e' - CS_SPRITE_FONT_OFFSET },
153     { UnicodeChar::cyrillic_zhe, SPR_G2_CYRILLIC_ZHE_LOWER - SPR_CHAR_START },
154     { UnicodeChar::cyrillic_ze, SPR_G2_CYRILLIC_ZE_LOWER - SPR_CHAR_START },
155     { UnicodeChar::cyrillic_i, SPR_G2_CYRILLIC_I_LOWER - SPR_CHAR_START },
156     { UnicodeChar::cyrillic_short_i, SPR_G2_CYRILLIC_SHORT_I_LOWER - SPR_CHAR_START },
157     { UnicodeChar::cyrillic_ka, SPR_G2_CYRILLIC_KA_LOWER - SPR_CHAR_START },
158     { UnicodeChar::cyrillic_el, SPR_G2_CYRILLIC_EL_LOWER - SPR_CHAR_START },
159     { UnicodeChar::cyrillic_em, SPR_G2_CYRILLIC_EM_LOWER - SPR_CHAR_START },
160     { UnicodeChar::cyrillic_en, SPR_G2_CYRILLIC_EN_LOWER - SPR_CHAR_START },
161     { UnicodeChar::cyrillic_o, 'o' - CS_SPRITE_FONT_OFFSET },
162     { UnicodeChar::cyrillic_pe, SPR_G2_CYRILLIC_PE_LOWER - SPR_CHAR_START },
163     { UnicodeChar::cyrillic_er, 'p' - CS_SPRITE_FONT_OFFSET },
164     { UnicodeChar::cyrillic_es, 'c' - CS_SPRITE_FONT_OFFSET },
165     { UnicodeChar::cyrillic_te, SPR_G2_CYRILLIC_TE_LOWER - SPR_CHAR_START },
166     { UnicodeChar::cyrillic_u, 'y' - CS_SPRITE_FONT_OFFSET },
167     { UnicodeChar::cyrillic_ef, SPR_G2_CYRILLIC_EF_LOWER - SPR_CHAR_START },
168     { UnicodeChar::cyrillic_ha, 'x' - CS_SPRITE_FONT_OFFSET },
169     { UnicodeChar::cyrillic_tse, SPR_G2_CYRILLIC_TSE_LOWER - SPR_CHAR_START },
170     { UnicodeChar::cyrillic_che, SPR_G2_CYRILLIC_CHE_LOWER - SPR_CHAR_START },
171     { UnicodeChar::cyrillic_sha, SPR_G2_CYRILLIC_SHA_LOWER - SPR_CHAR_START },
172     { UnicodeChar::cyrillic_shcha, SPR_G2_CYRILLIC_SHCHA_LOWER - SPR_CHAR_START },
173     { UnicodeChar::cyrillic_hard_sign, SPR_G2_CYRILLIC_HARD_SIGN_UPPER - SPR_CHAR_START },
174     { UnicodeChar::cyrillic_yeru, SPR_G2_CYRILLIC_YERU_LOWER - SPR_CHAR_START },
175     { UnicodeChar::cyrillic_soft_sign, SPR_G2_CYRILLIC_SOFT_SIGN_LOWER - SPR_CHAR_START },
176     { UnicodeChar::cyrillic_e, SPR_G2_CYRILLIC_E_LOWER - SPR_CHAR_START },
177     { UnicodeChar::cyrillic_yu, SPR_G2_CYRILLIC_YU_LOWER - SPR_CHAR_START },
178     { UnicodeChar::cyrillic_ya, SPR_G2_CYRILLIC_YA_LOWER - SPR_CHAR_START },
179     { UnicodeChar::cyrillic_io, 235 - CS_SPRITE_FONT_OFFSET }, // Looks just like ë
180 
181     // Punctuation
182     { UnicodeChar::non_breaking_space, ' ' - CS_SPRITE_FONT_OFFSET },
183     { UnicodeChar::interpunct, SPR_G2_INTERPUNCT - SPR_CHAR_START },
184     { UnicodeChar::multiplication_sign, CSChar::cross - CS_SPRITE_FONT_OFFSET },
185     { UnicodeChar::en_dash, '-' - CS_SPRITE_FONT_OFFSET },
186     { UnicodeChar::em_dash, '-' - CS_SPRITE_FONT_OFFSET },
187     { UnicodeChar::single_quote_open, '`' - CS_SPRITE_FONT_OFFSET },
188     { UnicodeChar::single_quote_end, '\'' - CS_SPRITE_FONT_OFFSET },
189     { UnicodeChar::single_german_quote_open, ',' - CS_SPRITE_FONT_OFFSET },
190     { UnicodeChar::german_quote_open, SPR_G2_GERMAN_OPENQUOTES - SPR_CHAR_START },
191     { UnicodeChar::bullet, CSChar::bullet - CS_SPRITE_FONT_OFFSET },
192     { UnicodeChar::ellipsis, SPR_G2_ELLIPSIS - SPR_CHAR_START },
193     { UnicodeChar::narrow_non_breaking_space, ' ' - CS_SPRITE_FONT_OFFSET },
194     { UnicodeChar::quote_open, CSChar::quote_open - CS_SPRITE_FONT_OFFSET },
195     { UnicodeChar::quote_close, CSChar::quote_close - CS_SPRITE_FONT_OFFSET },
196 
197     // Currency
198     { UnicodeChar::guilder, SPR_G2_GUILDER_SIGN - SPR_CHAR_START },
199     { UnicodeChar::euro, CSChar::euro - CS_SPRITE_FONT_OFFSET },
200     { UnicodeChar::rouble, SPR_G2_ROUBLE_SIGN - SPR_CHAR_START },
201 
202     // Dingbats
203     { UnicodeChar::up, CSChar::up - CS_SPRITE_FONT_OFFSET },
204     { UnicodeChar::small_up, CSChar::small_up - CS_SPRITE_FONT_OFFSET },
205     { UnicodeChar::right, CSChar::right - CS_SPRITE_FONT_OFFSET },
206     { UnicodeChar::down, CSChar::down - CS_SPRITE_FONT_OFFSET },
207     { UnicodeChar::small_down, CSChar::small_down - CS_SPRITE_FONT_OFFSET },
208     { UnicodeChar::left, CSChar::left - CS_SPRITE_FONT_OFFSET },
209     { UnicodeChar::air, CSChar::air - CS_SPRITE_FONT_OFFSET },
210     { UnicodeChar::tick, CSChar::tick - CS_SPRITE_FONT_OFFSET },
211     { UnicodeChar::plus, '+' - CS_SPRITE_FONT_OFFSET },
212     { UnicodeChar::minus, '-' - CS_SPRITE_FONT_OFFSET },
213 
214     // Emoji
215     { UnicodeChar::cross, CSChar::cross - CS_SPRITE_FONT_OFFSET },
216     { UnicodeChar::water, CSChar::water - CS_SPRITE_FONT_OFFSET },
217     { UnicodeChar::road, CSChar::road - CS_SPRITE_FONT_OFFSET },
218     { UnicodeChar::railway, CSChar::railway - CS_SPRITE_FONT_OFFSET },
219 
220     // Misc
221     { UnicodeChar::superscript_minus_one, CSChar::superscript_minus_one - CS_SPRITE_FONT_OFFSET },
222 };
223 
224 static char32_t _smallestCodepointValue = 0;
225 static char32_t _biggestCodepointValue = 0;
226 
227 /**
228  *
229  *  rct2: 0x006C19AC
230  */
font_sprite_initialise_characters()231 void font_sprite_initialise_characters()
232 {
233     // Compute min and max that helps avoiding lookups for no reason.
234     _smallestCodepointValue = std::numeric_limits<char32_t>::max();
235     for (const auto& entry : codepointOffsetMap)
236     {
237         _smallestCodepointValue = std::min(_smallestCodepointValue, entry.first);
238         _biggestCodepointValue = std::max(_biggestCodepointValue, entry.first);
239     }
240 
241     for (int32_t fontSize = 0; fontSize < FONT_SIZE_COUNT; fontSize++)
242     {
243         int32_t glyphOffset = fontSize * FONT_SPRITE_GLYPH_COUNT;
244         for (uint8_t glyphIndex = 0; glyphIndex < FONT_SPRITE_GLYPH_COUNT; glyphIndex++)
245         {
246             const rct_g1_element* g1 = gfx_get_g1_element(glyphIndex + SPR_CHAR_START + glyphOffset);
247             int32_t width = 0;
248             if (g1 != nullptr)
249             {
250                 width = g1->width + (2 * g1->x_offset) - 1;
251             }
252 
253             _spriteFontCharacterWidths[fontSize][glyphIndex] = static_cast<uint8_t>(width);
254         }
255     }
256 
257     for (uint8_t fontSize : { FONT_SIZE_SMALL, FONT_SIZE_MEDIUM, FONT_SIZE_TINY })
258     {
259         int32_t glyphOffset = fontSize * SPR_G2_GLYPH_COUNT;
260         for (int32_t glyphIndex = 0; glyphIndex < SPR_G2_GLYPH_COUNT; glyphIndex++)
261         {
262             const rct_g1_element* g1 = gfx_get_g1_element(glyphIndex + SPR_G2_CHAR_BEGIN + glyphOffset);
263             int32_t width = 0;
264             if (g1 != nullptr)
265             {
266                 width = g1->width + (2 * g1->x_offset) - 1;
267             }
268 
269             _additionalSpriteFontCharacterWidth[fontSize][glyphIndex] = static_cast<uint8_t>(width);
270         }
271     }
272 
273     scrolling_text_initialise_bitmaps();
274 }
275 
font_sprite_get_codepoint_offset(int32_t codepoint)276 int32_t font_sprite_get_codepoint_offset(int32_t codepoint)
277 {
278     // Only search the table when its in range of the map.
279     if (static_cast<char32_t>(codepoint) >= _smallestCodepointValue
280         && static_cast<char32_t>(codepoint) <= _biggestCodepointValue)
281     {
282         auto result = codepointOffsetMap.find(codepoint);
283         if (result != codepointOffsetMap.end())
284             return result->second;
285     }
286 
287     if (codepoint < 32 || codepoint >= 256)
288         codepoint = '?';
289 
290     return codepoint - 32;
291 }
292 
font_sprite_get_codepoint_width(FontSpriteBase fontSpriteBase,int32_t codepoint)293 int32_t font_sprite_get_codepoint_width(FontSpriteBase fontSpriteBase, int32_t codepoint)
294 {
295     if (fontSpriteBase == FontSpriteBase::MEDIUM_DARK || fontSpriteBase == FontSpriteBase::MEDIUM_EXTRA_DARK)
296     {
297         fontSpriteBase = FontSpriteBase::MEDIUM;
298     }
299 
300     int32_t glyphIndex = font_sprite_get_codepoint_offset(codepoint);
301     int32_t baseFontIndex = font_get_font_index_from_sprite_base(fontSpriteBase);
302     if (glyphIndex >= FONT_SPRITE_GLYPH_COUNT)
303     {
304         glyphIndex = (SPR_CHAR_START + glyphIndex) - SPR_G2_CHAR_BEGIN;
305 
306         if (glyphIndex >= static_cast<int32_t>(std::size(_additionalSpriteFontCharacterWidth[baseFontIndex])))
307         {
308             log_warning("Invalid glyph index %u", glyphIndex);
309             glyphIndex = 0;
310         }
311         return _additionalSpriteFontCharacterWidth[baseFontIndex][glyphIndex];
312     }
313 
314     if (glyphIndex < 0 || glyphIndex >= static_cast<int32_t>(FONT_SPRITE_GLYPH_COUNT))
315     {
316         log_warning("Invalid glyph index %u", glyphIndex);
317         glyphIndex = 0;
318     }
319     return _spriteFontCharacterWidths[baseFontIndex][glyphIndex];
320 }
321 
font_sprite_get_codepoint_sprite(FontSpriteBase fontSpriteBase,int32_t codepoint)322 int32_t font_sprite_get_codepoint_sprite(FontSpriteBase fontSpriteBase, int32_t codepoint)
323 {
324     int32_t offset = static_cast<int32_t>(fontSpriteBase);
325     auto codePointOffset = font_sprite_get_codepoint_offset(codepoint);
326     if (codePointOffset > FONT_SPRITE_GLYPH_COUNT)
327     {
328         offset = font_get_font_index_from_sprite_base(fontSpriteBase) * SPR_G2_GLYPH_COUNT;
329     }
330 
331     return SPR_CHAR_START + (IMAGE_TYPE_REMAP | (offset + codePointOffset));
332 }
333 
font_get_font_index_from_sprite_base(FontSpriteBase spriteBase)334 int32_t font_get_font_index_from_sprite_base(FontSpriteBase spriteBase)
335 {
336     switch (spriteBase)
337     {
338         case FontSpriteBase::TINY:
339             return FONT_SIZE_TINY;
340         case FontSpriteBase::SMALL:
341             return FONT_SIZE_SMALL;
342         default:
343         case FontSpriteBase::MEDIUM:
344             return FONT_SIZE_MEDIUM;
345     }
346 }
347 
font_get_size_from_sprite_base(FontSpriteBase spriteBase)348 int32_t font_get_size_from_sprite_base(FontSpriteBase spriteBase)
349 {
350     switch (spriteBase)
351     {
352         case FontSpriteBase::TINY:
353             return 0;
354         case FontSpriteBase::SMALL:
355             return 1;
356         default:
357         case FontSpriteBase::MEDIUM:
358             return 2;
359     }
360 }
361 
font_get_line_height(FontSpriteBase fontSpriteBase)362 int32_t font_get_line_height(FontSpriteBase fontSpriteBase)
363 {
364     int32_t fontSize = font_get_size_from_sprite_base(fontSpriteBase);
365 #ifndef NO_TTF
366     if (LocalisationService_UseTrueTypeFont())
367     {
368         return gCurrentTTFFontSet->size[fontSize].line_height;
369     }
370 #endif // NO_TTF
371     return SpriteFontLineHeight[fontSize];
372 }
373 
font_get_line_height_small(FontSpriteBase fontSpriteBase)374 int32_t font_get_line_height_small(FontSpriteBase fontSpriteBase)
375 {
376     return font_get_line_height(fontSpriteBase) / 2;
377 }
378 
font_supports_string_sprite(const utf8 * text)379 bool font_supports_string_sprite(const utf8* text)
380 {
381     const utf8* src = text;
382 
383     uint32_t codepoint;
384     while ((codepoint = utf8_get_next(src, &src)) != 0)
385     {
386         bool supported = false;
387 
388         if ((codepoint >= 32 && codepoint < 256)
389             || (codepoint >= UnicodeChar::cyrillic_a_uc && codepoint <= UnicodeChar::cyrillic_ya))
390         {
391             supported = true;
392         }
393 
394         auto result = codepointOffsetMap.find(codepoint);
395         if (result != codepointOffsetMap.end())
396             supported = true;
397 
398         if (!supported)
399         {
400             return false;
401         }
402     }
403     return true;
404 }
405 
font_supports_string_ttf(const utf8 * text,int32_t fontSize)406 bool font_supports_string_ttf(const utf8* text, int32_t fontSize)
407 {
408 #ifndef NO_TTF
409     const utf8* src = text;
410     const TTF_Font* font = gCurrentTTFFontSet->size[fontSize].font;
411     if (font == nullptr)
412     {
413         return false;
414     }
415 
416     uint32_t codepoint;
417     while ((codepoint = utf8_get_next(src, &src)) != 0)
418     {
419         bool supported = ttf_provides_glyph(font, codepoint);
420         if (!supported)
421         {
422             return false;
423         }
424     }
425     return true;
426 #else
427     return false;
428 #endif // NO_TTF
429 }
430 
font_supports_string(const utf8 * text,int32_t fontSize)431 bool font_supports_string(const utf8* text, int32_t fontSize)
432 {
433     if (LocalisationService_UseTrueTypeFont())
434     {
435         return font_supports_string_ttf(text, fontSize);
436     }
437 
438     return font_supports_string_sprite(text);
439 }
440