Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/import/resource_importer_imagefont.cpp
9896 views
1
/**************************************************************************/
2
/* resource_importer_imagefont.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "resource_importer_imagefont.h"
32
33
#include "core/io/image_loader.h"
34
#include "core/io/resource_saver.h"
35
36
String ResourceImporterImageFont::get_importer_name() const {
37
return "font_data_image";
38
}
39
40
String ResourceImporterImageFont::get_visible_name() const {
41
return "Font Data (Image Font)";
42
}
43
44
void ResourceImporterImageFont::get_recognized_extensions(List<String> *p_extensions) const {
45
if (p_extensions) {
46
ImageLoader::get_recognized_extensions(p_extensions);
47
}
48
}
49
50
String ResourceImporterImageFont::get_save_extension() const {
51
return "fontdata";
52
}
53
54
String ResourceImporterImageFont::get_resource_type() const {
55
return "FontFile";
56
}
57
58
bool ResourceImporterImageFont::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
59
return true;
60
}
61
62
void ResourceImporterImageFont::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const {
63
r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "character_ranges"), Vector<String>()));
64
r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "kerning_pairs"), Vector<String>()));
65
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "columns", PROPERTY_HINT_RANGE, "1,1024,1,or_greater"), 1));
66
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "rows", PROPERTY_HINT_RANGE, "1,1024,1,or_greater"), 1));
67
r_options->push_back(ImportOption(PropertyInfo(Variant::RECT2I, "image_margin"), Rect2i()));
68
r_options->push_back(ImportOption(PropertyInfo(Variant::RECT2I, "character_margin"), Rect2i()));
69
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "ascent"), 0));
70
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "descent"), 0));
71
72
r_options->push_back(ImportOption(PropertyInfo(Variant::ARRAY, "fallbacks", PROPERTY_HINT_ARRAY_TYPE, MAKE_RESOURCE_TYPE_HINT("Font")), Array()));
73
74
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress"), true));
75
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "scaling_mode", PROPERTY_HINT_ENUM, "Disabled,Enabled (Integer),Enabled (Fractional)"), TextServer::FIXED_SIZE_SCALE_ENABLED));
76
}
77
78
Error ResourceImporterImageFont::import(ResourceUID::ID p_source_id, const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
79
print_verbose("Importing image font from: " + p_source_file);
80
81
int columns = p_options["columns"];
82
int rows = p_options["rows"];
83
int ascent = p_options["ascent"];
84
int descent = p_options["descent"];
85
Vector<String> ranges = p_options["character_ranges"];
86
Vector<String> kern = p_options["kerning_pairs"];
87
Array fallbacks = p_options["fallbacks"];
88
Rect2i img_margin = p_options["image_margin"];
89
Rect2i char_margin = p_options["character_margin"];
90
TextServer::FixedSizeScaleMode smode = (TextServer::FixedSizeScaleMode)p_options["scaling_mode"].operator int();
91
92
Ref<Image> img;
93
img.instantiate();
94
Error err = ImageLoader::load_image(p_source_file, img);
95
ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, vformat("Can't load font texture: \"%s\".", p_source_file));
96
97
ERR_FAIL_COND_V_MSG(columns <= 0, ERR_FILE_CANT_READ, vformat("Columns (%d) must be positive.", columns));
98
ERR_FAIL_COND_V_MSG(rows <= 0, ERR_FILE_CANT_READ, vformat("Rows (%d) must be positive.", rows));
99
int remaining = columns * rows;
100
int chr_cell_width = (img->get_width() - img_margin.position.x - img_margin.size.x) / columns;
101
int chr_cell_height = (img->get_height() - img_margin.position.y - img_margin.size.y) / rows;
102
ERR_FAIL_COND_V_MSG(chr_cell_width <= 0 || chr_cell_height <= 0, ERR_FILE_CANT_READ, "Image margin too big.");
103
104
int chr_width = chr_cell_width - char_margin.position.x - char_margin.size.x;
105
int chr_height = chr_cell_height - char_margin.position.y - char_margin.size.y;
106
ERR_FAIL_COND_V_MSG(chr_width <= 0 || chr_height <= 0, ERR_FILE_CANT_READ, "Character margin too big.");
107
108
Ref<FontFile> font;
109
font.instantiate();
110
font->set_antialiasing(TextServer::FONT_ANTIALIASING_NONE);
111
font->set_generate_mipmaps(false);
112
font->set_multichannel_signed_distance_field(false);
113
font->set_fixed_size(chr_height);
114
font->set_subpixel_positioning(TextServer::SUBPIXEL_POSITIONING_DISABLED);
115
font->set_keep_rounding_remainders(true);
116
font->set_force_autohinter(false);
117
font->set_modulate_color_glyphs(false);
118
font->set_allow_system_fallback(false);
119
font->set_hinting(TextServer::HINTING_NONE);
120
font->set_fallbacks(fallbacks);
121
font->set_texture_image(0, Vector2i(chr_height, 0), 0, img);
122
font->set_fixed_size_scale_mode(smode);
123
124
int32_t pos = 0;
125
for (const String &range : ranges) {
126
Vector<int32_t> list;
127
int32_t start = -1;
128
int32_t end = -1;
129
int chr_adv = 0;
130
Vector2i chr_off;
131
132
{
133
enum RangeParseStep {
134
STEP_START_BEGIN,
135
STEP_START_READ_HEX,
136
STEP_START_READ_DEC,
137
STEP_END_BEGIN,
138
STEP_END_READ_HEX,
139
STEP_END_READ_DEC,
140
STEP_ADVANCE_BEGIN,
141
STEP_OFF_X_BEGIN,
142
STEP_OFF_Y_BEGIN,
143
STEP_FINISHED,
144
};
145
RangeParseStep step = STEP_START_BEGIN;
146
String token;
147
for (int c = 0; c < range.length(); c++) {
148
switch (step) {
149
case STEP_START_BEGIN:
150
case STEP_END_BEGIN: {
151
// Read range start/end first symbol.
152
if (range[c] == 'U' || range[c] == 'u') {
153
if ((c <= range.length() - 2) && range[c + 1] == '+') {
154
token = String();
155
if (step == STEP_START_BEGIN) {
156
step = STEP_START_READ_HEX;
157
} else {
158
step = STEP_END_READ_HEX;
159
}
160
c++; // Skip "+".
161
continue;
162
}
163
} else if (range[c] == '0' && (c <= range.length() - 2) && (range[c + 1] == 'x' || range[c + 1] == 'X')) {
164
// Read hexadecimal value, start.
165
token = String();
166
if (step == STEP_START_BEGIN) {
167
step = STEP_START_READ_HEX;
168
} else {
169
step = STEP_END_READ_HEX;
170
}
171
c++; // Skip "x".
172
continue;
173
} else if (range[c] == '\'' || range[c] == '\"') {
174
if ((c <= range.length() - 3) && (range[c + 2] == '\'' || range[c + 2] == '\"')) {
175
token = String();
176
if (step == STEP_START_BEGIN) {
177
start = range.unicode_at(c + 1);
178
if ((c <= range.length() - 4) && (range[c + 3] == ',')) {
179
list.push_back(start);
180
step = STEP_START_BEGIN;
181
} else {
182
step = STEP_END_BEGIN;
183
}
184
} else {
185
end = range.unicode_at(c + 1);
186
step = STEP_ADVANCE_BEGIN;
187
}
188
c = c + 2; // Skip the rest or token.
189
continue;
190
}
191
} else if (is_digit(range[c])) {
192
// Read decimal value, start.
193
token = String();
194
token += range[c];
195
if (step == STEP_START_BEGIN) {
196
step = STEP_START_READ_DEC;
197
} else {
198
step = STEP_END_READ_DEC;
199
}
200
continue;
201
}
202
[[fallthrough]];
203
}
204
case STEP_ADVANCE_BEGIN:
205
case STEP_OFF_X_BEGIN:
206
case STEP_OFF_Y_BEGIN: {
207
// Read advance and offset.
208
if (range[c] == ' ') {
209
int next = range.find_char(' ', c + 1);
210
if (next < c) {
211
next = range.length();
212
}
213
if (step == STEP_OFF_X_BEGIN) {
214
chr_off.x = range.substr(c + 1, next - (c + 1)).to_int();
215
step = STEP_OFF_Y_BEGIN;
216
} else if (step == STEP_OFF_Y_BEGIN) {
217
chr_off.y = range.substr(c + 1, next - (c + 1)).to_int();
218
step = STEP_FINISHED;
219
} else {
220
chr_adv = range.substr(c + 1, next - (c + 1)).to_int();
221
step = STEP_OFF_X_BEGIN;
222
}
223
c = next - 1;
224
continue;
225
}
226
} break;
227
case STEP_START_READ_HEX:
228
case STEP_END_READ_HEX: {
229
// Read hexadecimal value.
230
if (is_hex_digit(range[c])) {
231
token += range[c];
232
} else {
233
if (step == STEP_START_READ_HEX) {
234
start = token.hex_to_int();
235
if (range[c] == ',') {
236
list.push_back(start);
237
step = STEP_START_BEGIN;
238
} else {
239
step = STEP_END_BEGIN;
240
}
241
} else {
242
end = token.hex_to_int();
243
step = STEP_ADVANCE_BEGIN;
244
c--;
245
}
246
}
247
} break;
248
case STEP_START_READ_DEC:
249
case STEP_END_READ_DEC: {
250
// Read decimal value.
251
if (is_digit(range[c])) {
252
token += range[c];
253
} else {
254
if (step == STEP_START_READ_DEC) {
255
start = token.to_int();
256
if (range[c] == ',') {
257
list.push_back(start);
258
step = STEP_START_BEGIN;
259
} else {
260
step = STEP_END_BEGIN;
261
}
262
} else {
263
end = token.to_int();
264
step = STEP_ADVANCE_BEGIN;
265
c--;
266
}
267
}
268
} break;
269
default: {
270
WARN_PRINT(vformat("Invalid character \"%d\" in the range: \"%s\"", c, range));
271
} break;
272
}
273
}
274
if (step == STEP_START_READ_HEX) {
275
start = token.hex_to_int();
276
} else if (step == STEP_START_READ_DEC) {
277
start = token.to_int();
278
} else if (step == STEP_END_READ_HEX) {
279
end = token.hex_to_int();
280
} else if (step == STEP_END_READ_DEC) {
281
end = token.to_int();
282
}
283
if (end == -1) {
284
end = start;
285
}
286
if (!list.is_empty() && end != list[list.size() - 1]) {
287
list.push_back(end);
288
}
289
290
if (start == -1) {
291
WARN_PRINT(vformat("Invalid range: \"%s\"", range));
292
continue;
293
}
294
}
295
296
if (!list.is_empty()) {
297
ERR_FAIL_COND_V_MSG(list.size() > remaining, ERR_CANT_CREATE, vformat("Too many characters in range \"%s\", got %d but expected be %d.", range, list.size(), remaining));
298
for (int32_t idx : list) {
299
int x = pos % columns;
300
int y = pos / columns;
301
font->set_glyph_advance(0, chr_height, idx, Vector2(chr_width + chr_adv, 0));
302
font->set_glyph_offset(0, Vector2i(chr_height, 0), idx, Vector2i(0, -0.5 * chr_height) + chr_off);
303
font->set_glyph_size(0, Vector2i(chr_height, 0), idx, Vector2(chr_width, chr_height));
304
font->set_glyph_uv_rect(0, Vector2i(chr_height, 0), idx, Rect2(img_margin.position.x + chr_cell_width * x + char_margin.position.x, img_margin.position.y + chr_cell_height * y + char_margin.position.y, chr_width, chr_height));
305
font->set_glyph_texture_idx(0, Vector2i(chr_height, 0), idx, 0);
306
pos++;
307
}
308
remaining -= list.size();
309
} else {
310
ERR_FAIL_COND_V_MSG(Math::abs(end - start) > remaining, ERR_CANT_CREATE, vformat("Too many characters in range \"%s\", got %d but expected %d.", range, Math::abs(end - start), remaining));
311
for (int32_t idx = MIN(start, end); idx <= MAX(start, end); idx++) {
312
int x = pos % columns;
313
int y = pos / columns;
314
font->set_glyph_advance(0, chr_height, idx, Vector2(chr_width + chr_adv, 0));
315
font->set_glyph_offset(0, Vector2i(chr_height, 0), idx, Vector2i(0, -0.5 * chr_height) + chr_off);
316
font->set_glyph_size(0, Vector2i(chr_height, 0), idx, Vector2(chr_width, chr_height));
317
font->set_glyph_uv_rect(0, Vector2i(chr_height, 0), idx, Rect2(img_margin.position.x + chr_cell_width * x + char_margin.position.x, img_margin.position.y + chr_cell_height * y + char_margin.position.y, chr_width, chr_height));
318
font->set_glyph_texture_idx(0, Vector2i(chr_height, 0), idx, 0);
319
pos++;
320
}
321
remaining -= abs(end - start);
322
}
323
}
324
for (const String &kp : kern) {
325
const Vector<String> &kp_tokens = kp.split(" ");
326
if (kp_tokens.size() != 3) {
327
WARN_PRINT(vformat("Invalid kerning pairs string: \"%s\"", kp));
328
continue;
329
}
330
String from_tokens;
331
for (int i = 0; i < kp_tokens[0].length(); i++) {
332
if (i <= kp_tokens[0].length() - 6 && kp_tokens[0][i] == '\\' && kp_tokens[0][i + 1] == 'u' && is_hex_digit(kp_tokens[0][i + 2]) && is_hex_digit(kp_tokens[0][i + 3]) && is_hex_digit(kp_tokens[0][i + 4]) && is_hex_digit(kp_tokens[0][i + 5])) {
333
char32_t charcode = kp_tokens[0].substr(i + 2, 4).hex_to_int();
334
from_tokens += charcode;
335
i += 5;
336
} else {
337
from_tokens += kp_tokens[0][i];
338
}
339
}
340
String to_tokens;
341
for (int i = 0; i < kp_tokens[1].length(); i++) {
342
if (i <= kp_tokens[1].length() - 6 && kp_tokens[1][i] == '\\' && kp_tokens[1][i + 1] == 'u' && is_hex_digit(kp_tokens[1][i + 2]) && is_hex_digit(kp_tokens[1][i + 3]) && is_hex_digit(kp_tokens[1][i + 4]) && is_hex_digit(kp_tokens[1][i + 5])) {
343
char32_t charcode = kp_tokens[1].substr(i + 2, 4).hex_to_int();
344
to_tokens += charcode;
345
i += 5;
346
} else {
347
to_tokens += kp_tokens[1][i];
348
}
349
}
350
int offset = kp_tokens[2].to_int();
351
352
for (int a = 0; a < from_tokens.length(); a++) {
353
for (int b = 0; b < to_tokens.length(); b++) {
354
font->set_kerning(0, chr_height, Vector2i(from_tokens.unicode_at(a), to_tokens.unicode_at(b)), Vector2(offset, 0));
355
}
356
}
357
}
358
359
if (ascent > 0) {
360
font->set_cache_ascent(0, chr_height, ascent);
361
} else {
362
font->set_cache_ascent(0, chr_height, 0.5 * chr_height);
363
}
364
365
if (descent > 0) {
366
font->set_cache_descent(0, chr_height, descent);
367
} else {
368
font->set_cache_descent(0, chr_height, 0.5 * chr_height);
369
}
370
371
int flg = 0;
372
if ((bool)p_options["compress"]) {
373
flg |= ResourceSaver::SaverFlags::FLAG_COMPRESS;
374
}
375
376
print_verbose("Saving to: " + p_save_path + ".fontdata");
377
err = ResourceSaver::save(font, p_save_path + ".fontdata", flg);
378
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save font to file \"" + p_save_path + ".res\".");
379
print_verbose("Done saving to: " + p_save_path + ".fontdata");
380
return OK;
381
}
382
383