Translations update
[openttd/fttd.git] / src / strings.cpp
blob18f6c761db134496d98e8e53ebf6e88eb5467bcb
1 /* $Id$ */
3 /*
4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 */
10 /** @file strings.cpp Handling of translated strings. */
12 #include "stdafx.h"
13 #include "currency.h"
14 #include "station_base.h"
15 #include "town.h"
16 #include "screenshot.h"
17 #include "waypoint_base.h"
18 #include "depot_base.h"
19 #include "industry.h"
20 #include "newgrf_text.h"
21 #include "fileio_func.h"
22 #include "signs_base.h"
23 #include "font.h"
24 #include "error.h"
25 #include "strings_func.h"
26 #include "rev.h"
27 #include "core/endian_func.hpp"
28 #include "date_func.h"
29 #include "vehicle_base.h"
30 #include "engine_base.h"
31 #include "language.h"
32 #include "townnamegen.h"
33 #include "townname_func.h"
34 #include "string.h"
35 #include "company_base.h"
36 #include "smallmap_gui.h"
37 #include "window_func.h"
38 #include "debug.h"
39 #include "game/game_text.hpp"
40 #include "network/network_content_gui.h"
41 #include <stack>
43 #include "table/strings.h"
44 #include "table/control_codes.h"
46 char _config_language_file[MAX_PATH]; ///< The file (name) stored in the configuration.
47 LanguageList _languages; ///< The actual list of language meta data.
48 const LanguageMetadata *_current_language = NULL; ///< The currently loaded language.
50 TextDirection _current_text_dir; ///< Text direction of the currently selected language.
52 #ifdef WITH_ICU_SORT
53 Collator *_current_collator = NULL; ///< Collator for the language currently in use.
54 #endif /* WITH_ICU_SORT */
56 static uint64 _global_string_params_data[20]; ///< Global array of string parameters. To access, use #SetDParam.
57 static WChar _global_string_params_type[20]; ///< Type of parameters stored in #_decode_parameters
58 StringParameters _global_string_params(_global_string_params_data, 20, _global_string_params_type);
60 /** Reset the type array. */
61 void StringParameters::ClearTypeInformation()
63 assert(this->type != NULL);
64 MemSetT(this->type, 0, this->num_param);
68 /**
69 * Read an int64 from the argument array. The offset is increased
70 * so the next time GetInt64 is called the next value is read.
72 int64 StringParameters::GetInt64(WChar type)
74 if (this->offset >= this->num_param) {
75 DEBUG(misc, 0, "Trying to read invalid string parameter");
76 return 0;
78 if (this->type != NULL) {
79 assert(this->type[this->offset] == 0 || this->type[this->offset] == type);
80 this->type[this->offset] = type;
82 return this->data[this->offset++];
85 /**
86 * Set DParam n to some number that is suitable for string size computations.
87 * @param n Index of the string parameter.
88 * @param max_value The biggest value which shall be displayed.
89 * For the result only the number of digits of \a max_value matter.
90 * @param min_count Minimum number of digits independent of \a max.
91 * @param size Font of the number
93 void SetDParamMaxValue(uint n, uint64 max_value, uint min_count, FontSize size)
95 uint num_digits = 1;
96 while (max_value >= 10) {
97 num_digits++;
98 max_value /= 10;
100 SetDParamMaxDigits(n, max(min_count, num_digits), size);
104 * Set DParam n to some number that is suitable for string size computations.
105 * @param n Index of the string parameter.
106 * @param count Number of digits which shall be displayable.
107 * @param size Font of the number
109 void SetDParamMaxDigits(uint n, uint count, FontSize size)
111 SetDParam (n, FontCache::Get(size)->GetBroadestValue (count));
115 * Copy \a num string parameters from array \a src into the global string parameter array.
116 * @param offs Index in the global array to copy the first string parameter to.
117 * @param src Source array of string parameters.
118 * @param num Number of string parameters to copy.
120 void CopyInDParam(int offs, const uint64 *src, int num)
122 MemCpyT(_global_string_params.GetPointerToOffset(offs), src, num);
126 * Copy \a num string parameters from the global string parameter array to the \a dst array.
127 * @param dst Destination array of string parameters.
128 * @param offs Index in the global array to copy the first string parameter from.
129 * @param num Number of string parameters to copy.
131 void CopyOutDParam(uint64 *dst, int offs, int num)
133 MemCpyT(dst, _global_string_params.GetPointerToOffset(offs), num);
137 * Copy \a num string parameters from the global string parameter array to the \a dst array.
138 * Furthermore clone raw string parameters into \a strings and amend the data in \a dst.
139 * @param dst Destination array of string parameters.
140 * @param strings Destination array for clone of the raw strings. Must be of same length as dst. Deallocation left to the caller.
141 * @param string The string used to determine where raw strings are and where there are no raw strings.
142 * @param num Number of string parameters to copy.
144 void CopyOutDParam(uint64 *dst, const char **strings, StringID string, int num)
146 char buf[DRAW_STRING_BUFFER];
147 GetString (buf, string);
149 MemCpyT(dst, _global_string_params.GetPointerToOffset(0), num);
150 for (int i = 0; i < num; i++) {
151 if (_global_string_params.HasTypeInformation() && _global_string_params.GetTypeAtOffset(i) == SCC_RAW_STRING_POINTER) {
152 strings[i] = xstrdup((const char *)(size_t)_global_string_params.GetParam(i));
153 dst[i] = (size_t)strings[i];
154 } else {
155 strings[i] = NULL;
160 static void AppendStationSpecialString (stringb *buf, int x);
161 static void AppendSpecialNameString (stringb *buf, int ind, StringParameters *args);
163 static void FormatString (stringb *buf, const char *str, StringParameters *args, uint case_index = 0, bool game_script = false, bool dry_run = false);
165 struct LanguagePack : public LanguagePackHeader {
166 char data[]; // list of strings
169 static char **_langpack_offs;
170 static LanguagePack *_langpack;
171 static uint _langtab_num[TEXT_TAB_END]; ///< Offset into langpack offs
172 static uint _langtab_start[TEXT_TAB_END]; ///< Offset into langpack offs
173 static bool _scan_for_gender_data = false; ///< Are we scanning for the gender of the current string? (instead of formatting it)
176 const char *GetStringPtr(StringID string)
178 switch (GetStringTab(string)) {
179 case TEXT_TAB_GAMESCRIPT_START: return GetGameStringPtr(GetStringIndex(string));
180 /* 0xD0xx and 0xD4xx IDs have been converted earlier. */
181 case TEXT_TAB_OLD_NEWGRF: NOT_REACHED();
182 case TEXT_TAB_NEWGRF_START: return GetGRFStringPtr(GetStringIndex(string));
183 default: return _langpack_offs[_langtab_start[GetStringTab(string)] + GetStringIndex(string)];
188 * Get a parsed string with most special stringcodes replaced by the string parameters.
189 * @param buf Buffer where the formatted string should be written to.
190 * @param string
191 * @param args Arguments for the string.
192 * @param case_index The "case index". This will only be set when FormatString wants to print the string in a different case.
193 * @param game_script The string is coming directly from a game script.
195 void AppendStringWithArgs (stringb *buf, StringID string, StringParameters *args, uint case_index, bool game_script)
197 if (string == 0) {
198 AppendStringWithArgs (buf, STR_UNDEFINED, args);
199 return;
202 uint index = GetStringIndex(string);
203 StringTab tab = GetStringTab(string);
205 switch (tab) {
206 case TEXT_TAB_TOWN:
207 if (index >= 0xC0 && !game_script) {
208 GenerateTownNameString (buf, index - 0xC0, args->GetInt32());
209 return;
211 break;
213 case TEXT_TAB_SPECIAL:
214 if (index >= 0xE4 && !game_script) {
215 AppendSpecialNameString (buf, index - 0xE4, args);
216 return;
218 break;
220 case TEXT_TAB_OLD_CUSTOM:
221 /* Old table for custom names. This is no longer used */
222 if (!game_script) {
223 error("Incorrect conversion of custom name string.");
225 break;
227 case TEXT_TAB_GAMESCRIPT_START:
228 FormatString (buf, GetGameStringPtr(index), args, case_index, true);
229 return;
231 case TEXT_TAB_OLD_NEWGRF:
232 NOT_REACHED();
234 case TEXT_TAB_NEWGRF_START:
235 FormatString (buf, GetGRFStringPtr(index), args, case_index);
236 return;
238 default:
239 break;
242 if (index >= _langtab_num[tab]) {
243 if (game_script) {
244 AppendStringWithArgs (buf, STR_UNDEFINED, args);
245 return;
247 error("String 0x%X is invalid. You are probably using an old version of the .lng file.\n", string);
250 FormatString (buf, GetStringPtr(string), args, case_index);
253 void AppendString (stringb *buf, StringID string)
255 _global_string_params.ClearTypeInformation();
256 _global_string_params.offset = 0;
257 AppendStringWithArgs (buf, string, &_global_string_params);
262 * This function is used to "bind" a C string to a OpenTTD dparam slot.
263 * @param n slot of the string
264 * @param str string to bind
266 void SetDParamStr(uint n, const char *str)
268 SetDParam(n, (uint64)(size_t)str);
272 * Format a number into a string.
273 * @param buf the buffer to write to
274 * @param number the number to write down
275 * @param separator the thousands-separator to use
276 * @param zerofill minimum number of digits to print for the integer part. The number will be filled with zeros at the front if necessary.
277 * @param fractional_digits number of fractional digits to display after a decimal separator. The decimal separator is inserted
278 * in front of the \a fractional_digits last digit of \a number.
280 static void FormatNumber (stringb *buf, int64 number, const char *separator, int zerofill = 1, int fractional_digits = 0)
282 static const int max_digits = 20;
283 uint64 divisor = 10000000000000000000ULL;
284 zerofill += fractional_digits;
285 int thousands_offset = (max_digits - fractional_digits - 1) % 3;
287 if (number < 0) {
288 buf->append ('-');
289 number = -number;
292 uint64 num = number;
293 uint64 tot = 0;
294 for (int i = 0; i < max_digits; i++) {
295 if (i == max_digits - fractional_digits) {
296 const char *decimal_separator = _settings_game.locale.digit_decimal_separator;
297 if (decimal_separator == NULL) decimal_separator = _langpack->digit_decimal_separator;
298 buf->append (decimal_separator);
301 uint64 quot = 0;
302 if (num >= divisor) {
303 quot = num / divisor;
304 num = num % divisor;
306 if ((tot |= quot) || i >= max_digits - zerofill) {
307 buf->append_fmt ("%i", (int)quot);
308 if ((i % 3) == thousands_offset && i < max_digits - 1 - fractional_digits) buf->append (separator);
311 divisor /= 10;
315 static void FormatCommaNumber (stringb *buf, int64 number, int fractional_digits = 0)
317 const char *separator = _settings_game.locale.digit_group_separator;
318 if (separator == NULL) separator = _langpack->digit_group_separator;
319 FormatNumber (buf, number, separator, 1, fractional_digits);
322 static void FormatNoCommaNumber (stringb *buf, int64 number)
324 FormatNumber (buf, number, "");
327 static void FormatZerofillNumber (stringb *buf, int64 number, int64 count)
329 FormatNumber (buf, number, "", count);
332 static void FormatHexNumber (stringb *buf, uint64 number)
334 buf->append_fmt ("0x" OTTD_PRINTFHEX64, number);
338 * Format a given number as a number of bytes with the SI prefix.
339 * @param buf the buffer to write to
340 * @param number the number of bytes to write down
342 static void FormatBytes (stringb *buf, int64 number)
344 assert(number >= 0);
346 if (number < 1024) {
347 buf->append_fmt ("%i" NBSP "B", (int)number);
348 return;
351 /* 2^10 2^20 2^30 2^40 2^50 2^60 */
352 static const char iec_prefixes[] = {'K', 'M', 'G', 'T', 'P', 'E'};
353 uint id = 0;
354 while (number >= 1024 * 1024) {
355 number /= 1024;
356 id++;
359 const char *decimal_separator = _settings_game.locale.digit_decimal_separator;
360 if (decimal_separator == NULL) decimal_separator = _langpack->digit_decimal_separator;
362 if (number < 1024 * 10) {
363 buf->append_fmt ("%i%s%02i", (int)number / 1024, decimal_separator, (int)(number % 1024) * 100 / 1024);
364 } else if (number < 1024 * 100) {
365 buf->append_fmt ("%i%s%01i", (int)number / 1024, decimal_separator, (int)(number % 1024) * 10 / 1024);
366 } else {
367 assert(number < 1024 * 1024);
368 buf->append_fmt ("%i", (int)number / 1024);
371 assert(id < lengthof(iec_prefixes));
372 buf->append_fmt (NBSP "%ciB", iec_prefixes[id]);
375 static void FormatYmdString (stringb *buf, Date date, uint case_index)
377 YearMonthDay ymd;
378 ConvertDateToYMD(date, &ymd);
380 int64 args[] = {ymd.day + STR_DAY_NUMBER_1ST - 1, STR_MONTH_ABBREV_JAN + ymd.month, ymd.year};
381 StringParameters tmp_params(args);
382 FormatString (buf, GetStringPtr(STR_FORMAT_DATE_LONG), &tmp_params, case_index);
385 static void FormatMonthAndYear (stringb *buf, Date date, uint case_index)
387 YearMonthDay ymd;
388 ConvertDateToYMD(date, &ymd);
390 int64 args[] = {STR_MONTH_JAN + ymd.month, ymd.year};
391 StringParameters tmp_params(args);
392 FormatString (buf, GetStringPtr(STR_FORMAT_DATE_SHORT), &tmp_params, case_index);
395 static void FormatTinyOrISODate (stringb *buf, Date date, StringID str)
397 YearMonthDay ymd;
398 ConvertDateToYMD(date, &ymd);
400 char day[3];
401 char month[3];
402 /* We want to zero-pad the days and months */
403 snprintf(day, lengthof(day), "%02i", ymd.day);
404 snprintf(month, lengthof(month), "%02i", ymd.month + 1);
406 int64 args[] = {(int64)(size_t)day, (int64)(size_t)month, ymd.year};
407 StringParameters tmp_params(args);
408 FormatString (buf, GetStringPtr(str), &tmp_params);
411 static void FormatGenericCurrency (stringb *buf, const CurrencySpec *spec, Money number, bool compact)
413 /* We are going to make number absolute for printing, so
414 * keep this piece of data as we need it later on */
415 bool negative = number < 0;
416 const char *multiplier = "";
418 number *= spec->rate;
420 /* convert from negative */
421 if (number < 0) {
422 if (!buf->append_utf8(SCC_RED)) return;
423 buf->append ('-');
424 number = -number;
427 /* Add prefix part, following symbol_pos specification.
428 * Here, it can can be either 0 (prefix) or 2 (both prefix and suffix).
429 * The only remaining value is 1 (suffix), so everything that is not 1 */
430 if (spec->symbol_pos != 1) buf->append (spec->prefix);
432 /* for huge numbers, compact the number into k or M */
433 if (compact) {
434 /* Take care of the 'k' rounding. Having 1 000 000 k
435 * and 1 000 M is inconsistent, so always use 1 000 M. */
436 if (number >= 1000000000 - 500) {
437 number = (number + 500000) / 1000000;
438 multiplier = NBSP "M";
439 } else if (number >= 1000000) {
440 number = (number + 500) / 1000;
441 multiplier = NBSP "k";
445 const char *separator = _settings_game.locale.digit_group_separator_currency;
446 if (separator == NULL && !StrEmpty(_currency->separator)) separator = _currency->separator;
447 if (separator == NULL) separator = _langpack->digit_group_separator_currency;
448 FormatNumber (buf, number, separator);
449 buf->append (multiplier);
451 /* Add suffix part, following symbol_pos specification.
452 * Here, it can can be either 1 (suffix) or 2 (both prefix and suffix).
453 * The only remaining value is 1 (prefix), so everything that is not 0 */
454 if (spec->symbol_pos != 0) buf->append (spec->suffix);
456 if (negative) buf->append_utf8(SCC_PREVIOUS_COLOUR);
460 * Determine the "plural" index given a plural form and a number.
461 * @param count The number to get the plural index of.
462 * @param plural_form The plural form we want an index for.
463 * @return The plural index for the given form.
465 static int DeterminePluralForm(int64 count, int plural_form)
467 /* The absolute value determines plurality */
468 uint64 n = abs(count);
470 switch (plural_form) {
471 default:
472 NOT_REACHED();
474 /* Two forms: singular used for one only.
475 * Used in:
476 * Danish, Dutch, English, German, Norwegian, Swedish, Estonian, Finnish,
477 * Greek, Hebrew, Italian, Portuguese, Spanish, Esperanto */
478 case 0:
479 return n != 1 ? 1 : 0;
481 /* Only one form.
482 * Used in:
483 * Hungarian, Japanese, Korean, Turkish */
484 case 1:
485 return 0;
487 /* Two forms: singular used for 0 and 1.
488 * Used in:
489 * French, Brazilian Portuguese */
490 case 2:
491 return n > 1 ? 1 : 0;
493 /* Three forms: special cases for 0, and numbers ending in 1 except when ending in 11.
494 * Note: Cases are out of order for hysterical reasons. '0' is last.
495 * Used in:
496 * Latvian */
497 case 3:
498 return n % 10 == 1 && n % 100 != 11 ? 0 : n != 0 ? 1 : 2;
500 /* Five forms: special cases for 1, 2, 3 to 6, and 7 to 10.
501 * Used in:
502 * Gaelige (Irish) */
503 case 4:
504 return n == 1 ? 0 : n == 2 ? 1 : n < 7 ? 2 : n < 11 ? 3 : 4;
506 /* Three forms: special cases for numbers ending in 1 except when ending in 11, and 2 to 9 except when ending in 12 to 19.
507 * Used in:
508 * Lithuanian */
509 case 5:
510 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
512 /* Three forms: special cases for numbers ending in 1 except when ending in 11, and 2 to 4 except when ending in 12 to 14.
513 * Used in:
514 * Croatian, Russian, Ukrainian */
515 case 6:
516 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
518 /* Three forms: special cases for 1, and numbers ending in 2 to 4 except when ending in 12 to 14.
519 * Used in:
520 * Polish */
521 case 7:
522 return n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
524 /* Four forms: special cases for numbers ending in 01, 02, and 03 to 04.
525 * Used in:
526 * Slovenian */
527 case 8:
528 return n % 100 == 1 ? 0 : n % 100 == 2 ? 1 : n % 100 == 3 || n % 100 == 4 ? 2 : 3;
530 /* Two forms: singular used for numbers ending in 1 except when ending in 11.
531 * Used in:
532 * Icelandic */
533 case 9:
534 return n % 10 == 1 && n % 100 != 11 ? 0 : 1;
536 /* Three forms: special cases for 1, and 2 to 4
537 * Used in:
538 * Czech, Slovak */
539 case 10:
540 return n == 1 ? 0 : n >= 2 && n <= 4 ? 1 : 2;
542 /* Two forms: cases for numbers ending with a consonant, and with a vowel.
543 * Korean doesn't have the concept of plural, but depending on how a
544 * number is pronounced it needs another version of a particle.
545 * As such the plural system is misused to give this distinction.
547 case 11:
548 switch (n % 10) {
549 case 0: // yeong
550 case 1: // il
551 case 3: // sam
552 case 6: // yuk
553 case 7: // chil
554 case 8: // pal
555 return 0;
557 case 2: // i
558 case 4: // sa
559 case 5: // o
560 case 9: // gu
561 return 1;
563 default:
564 NOT_REACHED();
567 /* Four forms: special cases for 1, 0 and numbers ending in 02 to 10, and numbers ending in 11 to 19.
568 * Used in:
569 * Maltese */
570 case 12:
571 return (n == 1 ? 0 : n == 0 || (n % 100 > 1 && n % 100 < 11) ? 1 : (n % 100 > 10 && n % 100 < 20) ? 2 : 3);
572 /* Four forms: special cases for 1 and 11, 2 and 12, 3 .. 10 and 13 .. 19, other
573 * Used in:
574 * Scottish Gaelic */
575 case 13:
576 return ((n == 1 || n == 11) ? 0 : (n == 2 || n == 12) ? 1 : ((n > 2 && n < 11) || (n > 12 && n < 20)) ? 2 : 3);
580 static const char *ParseStringChoice (const char *b, uint form, stringb *dst)
582 /* <NUM> {Length of each string} {each string} */
583 uint n = (byte)*b++;
584 uint pos, i, mypos = 0;
586 for (i = pos = 0; i != n; i++) {
587 uint len = (byte)*b++;
588 if (i == form) mypos = pos;
589 pos += len;
592 dst->append (b + mypos);
593 return b + pos;
596 /** Helper for unit conversion. */
597 struct UnitConversion {
598 int multiplier; ///< Amount to multiply upon conversion.
599 int shift; ///< Amount to shift upon conversion.
602 * Convert value from OpenTTD's internal unit into the displayed value.
603 * @param input The input to convert.
604 * @param round Whether to round the value or not.
605 * @return The converted value.
607 int64 ToDisplay(int64 input, bool round = true) const
609 return ((input * this->multiplier) + (round && this->shift != 0 ? 1 << (this->shift - 1) : 0)) >> this->shift;
613 * Convert the displayed value back into a value of OpenTTD's internal unit.
614 * @param input The input to convert.
615 * @param round Whether to round the value up or not.
616 * @param divider Divide the return value by this.
617 * @return The converted value.
619 int64 FromDisplay(int64 input, bool round = true, int64 divider = 1) const
621 return ((input << this->shift) + (round ? (this->multiplier * divider) - 1 : 0)) / (this->multiplier * divider);
625 /** Information about a specific unit system. */
626 struct Units {
627 UnitConversion c; ///< Conversion
628 StringID s; ///< String for the unit
631 /** Information about a specific unit system with a long variant. */
632 struct UnitsLong {
633 UnitConversion c; ///< Conversion
634 StringID s; ///< String for the short variant of the unit
635 StringID l; ///< String for the long variant of the unit
638 /** Unit conversions for velocity. */
639 static const Units _units_velocity[] = {
640 { { 1, 0}, STR_UNITS_VELOCITY_IMPERIAL },
641 { { 103, 6}, STR_UNITS_VELOCITY_METRIC },
642 { {1831, 12}, STR_UNITS_VELOCITY_SI },
645 /** Unit conversions for velocity. */
646 static const Units _units_power[] = {
647 { { 1, 0}, STR_UNITS_POWER_IMPERIAL },
648 { {4153, 12}, STR_UNITS_POWER_METRIC },
649 { {6109, 13}, STR_UNITS_POWER_SI },
652 /** Unit conversions for weight. */
653 static const UnitsLong _units_weight[] = {
654 { {4515, 12}, STR_UNITS_WEIGHT_SHORT_IMPERIAL, STR_UNITS_WEIGHT_LONG_IMPERIAL },
655 { { 1, 0}, STR_UNITS_WEIGHT_SHORT_METRIC, STR_UNITS_WEIGHT_LONG_METRIC },
656 { {1000, 0}, STR_UNITS_WEIGHT_SHORT_SI, STR_UNITS_WEIGHT_LONG_SI },
659 /** Unit conversions for volume. */
660 static const UnitsLong _units_volume[] = {
661 { {4227, 4}, STR_UNITS_VOLUME_SHORT_IMPERIAL, STR_UNITS_VOLUME_LONG_IMPERIAL },
662 { {1000, 0}, STR_UNITS_VOLUME_SHORT_METRIC, STR_UNITS_VOLUME_LONG_METRIC },
663 { { 1, 0}, STR_UNITS_VOLUME_SHORT_SI, STR_UNITS_VOLUME_LONG_SI },
666 /** Unit conversions for force. */
667 static const Units _units_force[] = {
668 { {3597, 4}, STR_UNITS_FORCE_IMPERIAL },
669 { {3263, 5}, STR_UNITS_FORCE_METRIC },
670 { { 1, 0}, STR_UNITS_FORCE_SI },
673 /** Unit conversions for height. */
674 static const Units _units_height[] = {
675 { { 3, 0}, STR_UNITS_HEIGHT_IMPERIAL }, // "Wrong" conversion factor for more nicer GUI values
676 { { 1, 0}, STR_UNITS_HEIGHT_METRIC },
677 { { 1, 0}, STR_UNITS_HEIGHT_SI },
681 * Convert the given (internal) speed to the display speed.
682 * @param speed the speed to convert
683 * @return the converted speed.
685 uint ConvertSpeedToDisplaySpeed(uint speed)
687 /* For historical reasons we don't want to mess with the
688 * conversion for speed. So, don't round it and keep the
689 * original conversion factors instead of the real ones. */
690 return _units_velocity[_settings_game.locale.units_velocity].c.ToDisplay(speed, false);
694 * Convert the given display speed to the (internal) speed.
695 * @param speed the speed to convert
696 * @return the converted speed.
698 uint ConvertDisplaySpeedToSpeed(uint speed)
700 return _units_velocity[_settings_game.locale.units_velocity].c.FromDisplay(speed);
704 * Convert the given km/h-ish speed to the display speed.
705 * @param speed the speed to convert
706 * @return the converted speed.
708 uint ConvertKmhishSpeedToDisplaySpeed(uint speed)
710 return _units_velocity[_settings_game.locale.units_velocity].c.ToDisplay(speed * 10, false) / 16;
714 * Convert the given display speed to the km/h-ish speed.
715 * @param speed the speed to convert
716 * @return the converted speed.
718 uint ConvertDisplaySpeedToKmhishSpeed(uint speed)
720 return _units_velocity[_settings_game.locale.units_velocity].c.FromDisplay(speed * 16, true, 10);
723 * Parse most format codes within a string and write the result to a buffer.
724 * @param buf The buffer to write the final string to.
725 * @param str The original string with format codes.
726 * @param args Pointer to extra arguments used by various string codes.
727 * @param case_index
728 * @param dry_run True when the argt array is not yet initialized.
730 static void FormatString (stringb *buf, const char *str_arg, StringParameters *args, uint case_index, bool game_script, bool dry_run)
732 uint orig_offset = args->offset;
734 /* When there is no array with types there is no need to do a dry run. */
735 if (args->HasTypeInformation() && !dry_run) {
736 size_t length = buf->length();
737 if (UsingNewGRFTextStack()) {
738 /* Values from the NewGRF text stack are only copied to the normal
739 * argv array at the time they are encountered. That means that if
740 * another string command references a value later in the string it
741 * would fail. We solve that by running FormatString twice. The first
742 * pass makes sure the argv array is correctly filled and the second
743 * pass can reference later values without problems. */
744 struct TextRefStack *backup = CreateTextRefStackBackup();
745 FormatString (buf, str_arg, args, case_index, game_script, true);
746 RestoreTextRefStackBackup(backup);
747 } else {
748 FormatString (buf, str_arg, args, case_index, game_script, true);
750 buf->truncate (length);
751 /* We have to restore the original offset here to to read the correct values. */
752 args->offset = orig_offset;
754 WChar b = '\0';
755 uint next_substr_case_index = 0;
756 std::stack<const char *> str_stack;
757 str_stack.push(str_arg);
759 for (;;) {
760 while (!str_stack.empty() && (b = Utf8Consume(&str_stack.top())) == '\0') {
761 str_stack.pop();
763 if (str_stack.empty()) break;
764 const char *&str = str_stack.top();
766 if (SCC_NEWGRF_FIRST <= b && b <= SCC_NEWGRF_LAST) {
767 /* We need to pass some stuff as it might be modified; oh boy. */
768 //todo: should argve be passed here too?
769 b = RemapNewGRFStringControlCode (b, buf, &str, (int64 *)args->GetDataPointer(), args->GetDataLeft(), dry_run);
770 if (b == 0) continue;
773 switch (b) {
774 case SCC_ENCODED: {
775 uint64 sub_args_data[20];
776 WChar sub_args_type[20];
777 bool sub_args_need_free[20];
778 StringParameters sub_args(sub_args_data, 20, sub_args_type);
780 sub_args.ClearTypeInformation();
781 memset(sub_args_need_free, 0, sizeof(sub_args_need_free));
783 const char *s = str;
784 char *p;
785 uint32 stringid = strtoul(str, &p, 16);
786 if (*p != ':' && *p != '\0') {
787 while (*p != '\0') p++;
788 str = p;
789 buf->append ("(invalid SCC_ENCODED)");
790 break;
792 if (stringid >= TAB_SIZE_GAMESCRIPT) {
793 while (*p != '\0') p++;
794 str = p;
795 buf->append ("(invalid StringID)");
796 break;
799 int i = 0;
800 while (*p != '\0' && i < 20) {
801 uint64 param;
802 s = ++p;
804 /* Find the next value */
805 bool instring = false;
806 bool escape = false;
807 for (;; p++) {
808 if (*p == '\\') {
809 escape = true;
810 continue;
812 if (*p == '"' && escape) {
813 escape = false;
814 continue;
816 escape = false;
818 if (*p == '"') {
819 instring = !instring;
820 continue;
822 if (instring) {
823 continue;
826 if (*p == ':') break;
827 if (*p == '\0') break;
830 if (*s != '"') {
831 /* Check if we want to look up another string */
832 WChar l;
833 size_t len = Utf8Decode(&l, s);
834 bool lookup = (l == SCC_ENCODED);
835 if (lookup) s += len;
837 param = strtoull(s, &p, 16);
839 if (lookup) {
840 if (param >= TAB_SIZE_GAMESCRIPT) {
841 while (*p != '\0') p++;
842 str = p;
843 buf->append ("(invalid sub-StringID)");
844 break;
846 param = MakeStringID(TEXT_TAB_GAMESCRIPT_START, param);
849 sub_args.SetParam(i++, param);
850 } else {
851 char *g = xstrmemdup (s, p - s);
853 sub_args_need_free[i] = true;
854 sub_args.SetParam(i++, (uint64)(size_t)g);
857 /* If we didn't error out, we can actually print the string. */
858 if (*str != '\0') {
859 str = p;
860 AppendStringWithArgs (buf, MakeStringID(TEXT_TAB_GAMESCRIPT_START, stringid), &sub_args, true);
863 for (int i = 0; i < 20; i++) {
864 if (sub_args_need_free[i]) free((void *)sub_args.GetParam(i));
866 break;
869 case SCC_NEWGRF_STRINL: {
870 StringID substr = Utf8Consume(&str);
871 str_stack.push(GetStringPtr(substr));
872 break;
875 case SCC_NEWGRF_PRINT_WORD_STRING_ID: {
876 StringID substr = args->GetInt32(SCC_NEWGRF_PRINT_WORD_STRING_ID);
877 str_stack.push(GetStringPtr(substr));
878 case_index = next_substr_case_index;
879 next_substr_case_index = 0;
880 break;
884 case SCC_GENDER_LIST: { // {G 0 Der Die Das}
885 /* First read the meta data from the language file. */
886 uint offset = orig_offset + (byte)*str++;
887 int gender = 0;
888 if (!dry_run && args->GetTypeAtOffset(offset) != 0) {
889 /* Now we need to figure out what text to resolve, i.e.
890 * what do we need to draw? So get the actual raw string
891 * first using the control code to get said string. */
892 char input[4 + 1];
893 char *p = input + Utf8Encode(input, args->GetTypeAtOffset(offset));
894 *p = '\0';
896 /* Now do the string formatting. */
897 sstring<256> tmp;
898 bool old_sgd = _scan_for_gender_data;
899 _scan_for_gender_data = true;
900 StringParameters tmp_params(args->GetPointerToOffset(offset), args->num_param - offset, NULL);
901 FormatString (&tmp, input, &tmp_params);
902 _scan_for_gender_data = old_sgd;
904 /* And determine the string. */
905 const char *s = tmp.c_str();
906 WChar c = Utf8Consume(&s);
907 /* Does this string have a gender, if so, set it */
908 if (c == SCC_GENDER_INDEX) gender = (byte)s[0];
910 str = ParseStringChoice(str, gender, buf);
911 break;
914 /* This sets up the gender for the string.
915 * We just ignore this one. It's used in {G 0 Der Die Das} to determine the case. */
916 case SCC_GENDER_INDEX: // {GENDER 0}
917 if (_scan_for_gender_data) {
918 buf->append_utf8 (SCC_GENDER_INDEX);
919 buf->append (*str++);
920 } else {
921 str++;
923 break;
925 case SCC_PLURAL_LIST: { // {P}
926 int plural_form = *str++; // contains the plural form for this string
927 uint offset = orig_offset + (byte)*str++;
928 int64 v = *args->GetPointerToOffset(offset); // contains the number that determines plural
929 str = ParseStringChoice(str, DeterminePluralForm(v, plural_form), buf);
930 break;
933 case SCC_ARG_INDEX: { // Move argument pointer
934 args->offset = orig_offset + (byte)*str++;
935 break;
938 case SCC_SET_CASE: { // {SET_CASE}
939 /* This is a pseudo command, it's outputted when someone does {STRING.ack}
940 * The modifier is added to all subsequent AppendStringWithArgs that accept the modifier. */
941 next_substr_case_index = (byte)*str++;
942 break;
945 case SCC_SWITCH_CASE: { // {Used to implement case switching}
946 /* <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
947 * Each LEN is printed using 2 bytes in big endian order. */
948 uint num = (byte)*str++;
949 while (num) {
950 if ((byte)str[0] == case_index) {
951 /* Found the case, adjust str pointer and continue */
952 str += 3;
953 break;
955 /* Otherwise skip to the next case */
956 str += 3 + (str[1] << 8) + str[2];
957 num--;
959 break;
962 case SCC_REVISION: // {REV}
963 buf->append (_openttd_revision);
964 break;
966 case SCC_RAW_STRING_POINTER: { // {RAW_STRING}
967 if (game_script) break;
968 const char *str = (const char *)(size_t)args->GetInt64(SCC_RAW_STRING_POINTER);
969 FormatString (buf, str, args);
970 break;
973 case SCC_STRING: {// {STRING}
974 StringID str = args->GetInt32(SCC_STRING);
975 if (game_script && GetStringTab(str) != TEXT_TAB_GAMESCRIPT_START) break;
976 /* WARNING. It's prohibited for the included string to consume any arguments.
977 * For included strings that consume argument, you should use STRING1, STRING2 etc.
978 * To debug stuff you can set argv to NULL and it will tell you */
979 StringParameters tmp_params(args->GetDataPointer(), args->GetDataLeft(), NULL);
980 AppendStringWithArgs (buf, str, &tmp_params, next_substr_case_index, game_script);
981 next_substr_case_index = 0;
982 break;
985 case SCC_STRING1:
986 case SCC_STRING2:
987 case SCC_STRING3:
988 case SCC_STRING4:
989 case SCC_STRING5:
990 case SCC_STRING6:
991 case SCC_STRING7: { // {STRING1..7}
992 /* Strings that consume arguments */
993 StringID str = args->GetInt32(b);
994 if (game_script && GetStringTab(str) != TEXT_TAB_GAMESCRIPT_START) break;
995 uint size = b - SCC_STRING1 + 1;
996 if (game_script && size > args->GetDataLeft()) {
997 buf->append ("(too many parameters)");
998 } else {
999 StringParameters sub_args(*args, size);
1000 AppendStringWithArgs (buf, str, &sub_args, next_substr_case_index, game_script);
1002 next_substr_case_index = 0;
1003 break;
1006 case SCC_COMMA: // {COMMA}
1007 FormatCommaNumber (buf, args->GetInt64(SCC_COMMA));
1008 break;
1010 case SCC_DECIMAL: {// {DECIMAL}
1011 int64 number = args->GetInt64(SCC_DECIMAL);
1012 int digits = args->GetInt32(SCC_DECIMAL);
1013 FormatCommaNumber (buf, number, digits);
1014 break;
1017 case SCC_NUM: // {NUM}
1018 FormatNoCommaNumber (buf, args->GetInt64(SCC_NUM));
1019 break;
1021 case SCC_ZEROFILL_NUM: { // {ZEROFILL_NUM}
1022 int64 num = args->GetInt64();
1023 FormatZerofillNumber (buf, num, args->GetInt64());
1024 break;
1027 case SCC_HEX: // {HEX}
1028 FormatHexNumber (buf, (uint64)args->GetInt64(SCC_HEX));
1029 break;
1031 case SCC_BYTES: // {BYTES}
1032 FormatBytes (buf, args->GetInt64());
1033 break;
1035 case SCC_CARGO_TINY: { // {CARGO_TINY}
1036 /* Tiny description of cargotypes. Layout:
1037 * param 1: cargo type
1038 * param 2: cargo count */
1039 CargoID cargo = args->GetInt32(SCC_CARGO_TINY);
1040 if (cargo >= CargoSpec::GetArraySize()) break;
1042 StringID cargo_str = CargoSpec::Get(cargo)->units_volume;
1043 int64 amount = 0;
1044 switch (cargo_str) {
1045 case STR_TONS:
1046 amount = _units_weight[_settings_game.locale.units_weight].c.ToDisplay(args->GetInt64());
1047 break;
1049 case STR_LITERS:
1050 amount = _units_volume[_settings_game.locale.units_volume].c.ToDisplay(args->GetInt64());
1051 break;
1053 default: {
1054 amount = args->GetInt64();
1055 break;
1059 FormatCommaNumber (buf, amount);
1060 break;
1063 case SCC_CARGO_SHORT: { // {CARGO_SHORT}
1064 /* Short description of cargotypes. Layout:
1065 * param 1: cargo type
1066 * param 2: cargo count */
1067 CargoID cargo = args->GetInt32(SCC_CARGO_SHORT);
1068 if (cargo >= CargoSpec::GetArraySize()) break;
1070 StringID cargo_str = CargoSpec::Get(cargo)->units_volume;
1071 switch (cargo_str) {
1072 case STR_TONS: {
1073 assert(_settings_game.locale.units_weight < lengthof(_units_weight));
1074 int64 args_array[] = {_units_weight[_settings_game.locale.units_weight].c.ToDisplay(args->GetInt64())};
1075 StringParameters tmp_params(args_array);
1076 FormatString (buf, GetStringPtr(_units_weight[_settings_game.locale.units_weight].l), &tmp_params);
1077 break;
1080 case STR_LITERS: {
1081 assert(_settings_game.locale.units_volume < lengthof(_units_volume));
1082 int64 args_array[] = {_units_volume[_settings_game.locale.units_volume].c.ToDisplay(args->GetInt64())};
1083 StringParameters tmp_params(args_array);
1084 FormatString (buf, GetStringPtr(_units_volume[_settings_game.locale.units_volume].l), &tmp_params);
1085 break;
1088 default: {
1089 StringParameters tmp_params(*args, 1);
1090 AppendStringWithArgs (buf, cargo_str, &tmp_params);
1091 break;
1094 break;
1097 case SCC_CARGO_LONG: { // {CARGO_LONG}
1098 /* First parameter is cargo type, second parameter is cargo count */
1099 CargoID cargo = args->GetInt32(SCC_CARGO_LONG);
1100 if (cargo != CT_INVALID && cargo >= CargoSpec::GetArraySize()) break;
1102 StringID cargo_str = (cargo == CT_INVALID) ? STR_QUANTITY_N_A : CargoSpec::Get(cargo)->quantifier;
1103 StringParameters tmp_args(*args, 1);
1104 AppendStringWithArgs (buf, cargo_str, &tmp_args);
1105 break;
1108 case SCC_CARGO_LIST: { // {CARGO_LIST}
1109 uint32 cmask = args->GetInt32(SCC_CARGO_LIST);
1110 bool first = true;
1112 const CargoSpec *cs;
1113 FOR_ALL_SORTED_CARGOSPECS(cs) {
1114 if (!HasBit(cmask, cs->Index())) continue;
1116 if (buf->length() >= buf->capacity - 3) break; // ',' and ' '
1118 if (first) {
1119 first = false;
1120 } else {
1121 /* Add a comma if this is not the first item */
1122 buf->append (", ");
1125 AppendStringWithArgs (buf, cs->name, args, next_substr_case_index, game_script);
1128 /* If first is still true then no cargo is accepted */
1129 if (first) AppendStringWithArgs (buf, STR_JUST_NOTHING, args, next_substr_case_index, game_script);
1131 next_substr_case_index = 0;
1132 break;
1135 case SCC_CURRENCY_SHORT: // {CURRENCY_SHORT}
1136 FormatGenericCurrency (buf, _currency, args->GetInt64(), true);
1137 break;
1139 case SCC_CURRENCY_LONG: // {CURRENCY_LONG}
1140 FormatGenericCurrency (buf, _currency, args->GetInt64(SCC_CURRENCY_LONG), false);
1141 break;
1143 case SCC_DATE_TINY: // {DATE_TINY}
1144 FormatTinyOrISODate (buf, args->GetInt32(SCC_DATE_TINY), STR_FORMAT_DATE_TINY);
1145 break;
1147 case SCC_DATE_SHORT: // {DATE_SHORT}
1148 FormatMonthAndYear (buf, args->GetInt32(SCC_DATE_SHORT), next_substr_case_index);
1149 next_substr_case_index = 0;
1150 break;
1152 case SCC_DATE_LONG: // {DATE_LONG}
1153 FormatYmdString (buf, args->GetInt32(SCC_DATE_LONG), next_substr_case_index);
1154 next_substr_case_index = 0;
1155 break;
1157 case SCC_DATE_ISO: // {DATE_ISO}
1158 FormatTinyOrISODate (buf, args->GetInt32(), STR_FORMAT_DATE_ISO);
1159 break;
1161 case SCC_FORCE: { // {FORCE}
1162 assert(_settings_game.locale.units_force < lengthof(_units_force));
1163 int64 args_array[1] = {_units_force[_settings_game.locale.units_force].c.ToDisplay(args->GetInt64())};
1164 StringParameters tmp_params(args_array);
1165 FormatString (buf, GetStringPtr(_units_force[_settings_game.locale.units_force].s), &tmp_params);
1166 break;
1169 case SCC_HEIGHT: { // {HEIGHT}
1170 assert(_settings_game.locale.units_height < lengthof(_units_height));
1171 int64 args_array[] = {_units_height[_settings_game.locale.units_height].c.ToDisplay(args->GetInt64())};
1172 StringParameters tmp_params(args_array);
1173 FormatString (buf, GetStringPtr(_units_height[_settings_game.locale.units_height].s), &tmp_params);
1174 break;
1177 case SCC_POWER: { // {POWER}
1178 assert(_settings_game.locale.units_power < lengthof(_units_power));
1179 int64 args_array[1] = {_units_power[_settings_game.locale.units_power].c.ToDisplay(args->GetInt64())};
1180 StringParameters tmp_params(args_array);
1181 FormatString (buf, GetStringPtr(_units_power[_settings_game.locale.units_power].s), &tmp_params);
1182 break;
1185 case SCC_VELOCITY: { // {VELOCITY}
1186 assert(_settings_game.locale.units_velocity < lengthof(_units_velocity));
1187 int64 args_array[] = {ConvertKmhishSpeedToDisplaySpeed(args->GetInt64(SCC_VELOCITY))};
1188 StringParameters tmp_params(args_array);
1189 FormatString (buf, GetStringPtr(_units_velocity[_settings_game.locale.units_velocity].s), &tmp_params);
1190 break;
1193 case SCC_VOLUME_SHORT: { // {VOLUME_SHORT}
1194 assert(_settings_game.locale.units_volume < lengthof(_units_volume));
1195 int64 args_array[1] = {_units_volume[_settings_game.locale.units_volume].c.ToDisplay(args->GetInt64())};
1196 StringParameters tmp_params(args_array);
1197 FormatString (buf, GetStringPtr(_units_volume[_settings_game.locale.units_volume].s), &tmp_params);
1198 break;
1201 case SCC_VOLUME_LONG: { // {VOLUME_LONG}
1202 assert(_settings_game.locale.units_volume < lengthof(_units_volume));
1203 int64 args_array[1] = {_units_volume[_settings_game.locale.units_volume].c.ToDisplay(args->GetInt64(SCC_VOLUME_LONG))};
1204 StringParameters tmp_params(args_array);
1205 FormatString (buf, GetStringPtr(_units_volume[_settings_game.locale.units_volume].l), &tmp_params);
1206 break;
1209 case SCC_WEIGHT_SHORT: { // {WEIGHT_SHORT}
1210 assert(_settings_game.locale.units_weight < lengthof(_units_weight));
1211 int64 args_array[1] = {_units_weight[_settings_game.locale.units_weight].c.ToDisplay(args->GetInt64())};
1212 StringParameters tmp_params(args_array);
1213 FormatString (buf, GetStringPtr(_units_weight[_settings_game.locale.units_weight].s), &tmp_params);
1214 break;
1217 case SCC_WEIGHT_LONG: { // {WEIGHT_LONG}
1218 assert(_settings_game.locale.units_weight < lengthof(_units_weight));
1219 int64 args_array[1] = {_units_weight[_settings_game.locale.units_weight].c.ToDisplay(args->GetInt64(SCC_WEIGHT_LONG))};
1220 StringParameters tmp_params(args_array);
1221 FormatString (buf, GetStringPtr(_units_weight[_settings_game.locale.units_weight].l), &tmp_params);
1222 break;
1225 case SCC_COMPANY_NAME: { // {COMPANY}
1226 const Company *c = Company::GetIfValid(args->GetInt32());
1227 if (c == NULL) break;
1229 if (c->name != NULL) {
1230 int64 args_array[] = {(int64)(size_t)c->name};
1231 StringParameters tmp_params(args_array);
1232 AppendStringWithArgs (buf, STR_JUST_RAW_STRING, &tmp_params);
1233 } else {
1234 int64 args_array[] = {c->name_2};
1235 StringParameters tmp_params(args_array);
1236 AppendStringWithArgs (buf, c->name_1, &tmp_params);
1238 break;
1241 case SCC_COMPANY_NUM: { // {COMPANY_NUM}
1242 CompanyID company = (CompanyID)args->GetInt32();
1244 /* Nothing is added for AI or inactive companies */
1245 if (Company::IsValidHumanID(company)) {
1246 int64 args_array[] = {company + 1};
1247 StringParameters tmp_params(args_array);
1248 AppendStringWithArgs (buf, STR_FORMAT_COMPANY_NUM, &tmp_params);
1250 break;
1253 case SCC_DEPOT_NAME: { // {DEPOT}
1254 VehicleType vt = (VehicleType)args->GetInt32(SCC_DEPOT_NAME);
1255 if (vt == VEH_AIRCRAFT) {
1256 uint64 args_array[] = {(uint64)args->GetInt32()};
1257 WChar types_array[] = {SCC_STATION_NAME};
1258 StringParameters tmp_params(args_array, 1, types_array);
1259 AppendStringWithArgs (buf, STR_FORMAT_DEPOT_NAME_AIRCRAFT, &tmp_params);
1260 break;
1263 const Depot *d = Depot::Get(args->GetInt32());
1264 if (d->name != NULL) {
1265 int64 args_array[] = {(int64)(size_t)d->name};
1266 StringParameters tmp_params(args_array);
1267 AppendStringWithArgs (buf, STR_JUST_RAW_STRING, &tmp_params);
1268 } else {
1269 int64 args_array[] = {d->town->index, d->town_cn + 1};
1270 StringParameters tmp_params(args_array);
1271 AppendStringWithArgs (buf, STR_FORMAT_DEPOT_NAME_TRAIN + 2 * vt + (d->town_cn == 0 ? 0 : 1), &tmp_params);
1273 break;
1276 case SCC_ENGINE_NAME: { // {ENGINE}
1277 const Engine *e = Engine::GetIfValid(args->GetInt32(SCC_ENGINE_NAME));
1278 if (e == NULL) break;
1280 if (e->name != NULL && e->IsEnabled()) {
1281 int64 args_array[] = {(int64)(size_t)e->name};
1282 StringParameters tmp_params(args_array);
1283 AppendStringWithArgs (buf, STR_JUST_RAW_STRING, &tmp_params);
1284 } else {
1285 StringParameters tmp_params(NULL, 0, NULL);
1286 AppendStringWithArgs (buf, e->info.string_id, &tmp_params);
1288 break;
1291 case SCC_GROUP_NAME: { // {GROUP}
1292 const Group *g = Group::GetIfValid(args->GetInt32());
1293 if (g == NULL) break;
1295 if (g->name != NULL) {
1296 int64 args_array[] = {(int64)(size_t)g->name};
1297 StringParameters tmp_params(args_array);
1298 AppendStringWithArgs (buf, STR_JUST_RAW_STRING, &tmp_params);
1299 } else {
1300 int64 args_array[] = {g->index};
1301 StringParameters tmp_params(args_array);
1303 AppendStringWithArgs (buf, STR_FORMAT_GROUP_NAME, &tmp_params);
1305 break;
1308 case SCC_INDUSTRY_NAME: { // {INDUSTRY}
1309 const Industry *i = Industry::GetIfValid(args->GetInt32(SCC_INDUSTRY_NAME));
1310 if (i == NULL) break;
1312 if (_scan_for_gender_data) {
1313 /* Gender is defined by the industry type.
1314 * STR_FORMAT_INDUSTRY_NAME may have the town first, so it would result in the gender of the town name */
1315 StringParameters tmp_params(NULL, 0, NULL);
1316 FormatString (buf, GetStringPtr(GetIndustrySpec(i->type)->name), &tmp_params, next_substr_case_index);
1317 } else {
1318 /* First print the town name and the industry type name. */
1319 int64 args_array[2] = {i->town->index, GetIndustrySpec(i->type)->name};
1320 StringParameters tmp_params(args_array);
1322 FormatString (buf, GetStringPtr(STR_FORMAT_INDUSTRY_NAME), &tmp_params, next_substr_case_index);
1324 next_substr_case_index = 0;
1325 break;
1328 case SCC_PRESIDENT_NAME: { // {PRESIDENT_NAME}
1329 const Company *c = Company::GetIfValid(args->GetInt32(SCC_PRESIDENT_NAME));
1330 if (c == NULL) break;
1332 if (c->president_name != NULL) {
1333 int64 args_array[] = {(int64)(size_t)c->president_name};
1334 StringParameters tmp_params(args_array);
1335 AppendStringWithArgs (buf, STR_JUST_RAW_STRING, &tmp_params);
1336 } else {
1337 int64 args_array[] = {c->president_name_2};
1338 StringParameters tmp_params(args_array);
1339 AppendStringWithArgs (buf, c->president_name_1, &tmp_params);
1341 break;
1344 case SCC_STATION_NAME: { // {STATION}
1345 StationID sid = args->GetInt32(SCC_STATION_NAME);
1346 const Station *st = Station::GetIfValid(sid);
1348 if (st == NULL) {
1349 /* The station doesn't exist anymore. The only place where we might
1350 * be "drawing" an invalid station is in the case of cargo that is
1351 * in transit. */
1352 StringParameters tmp_params(NULL, 0, NULL);
1353 AppendStringWithArgs (buf, STR_UNKNOWN_STATION, &tmp_params);
1354 break;
1357 if (st->name != NULL) {
1358 int64 args_array[] = {(int64)(size_t)st->name};
1359 StringParameters tmp_params(args_array);
1360 AppendStringWithArgs (buf, STR_JUST_RAW_STRING, &tmp_params);
1361 } else {
1362 StringID str = st->string_id;
1363 if (st->indtype != IT_INVALID) {
1364 /* Special case where the industry provides the name for the station */
1365 const IndustrySpec *indsp = GetIndustrySpec(st->indtype);
1367 /* Industry GRFs can change which might remove the station name and
1368 * thus cause very strange things. Here we check for that before we
1369 * actually set the station name. */
1370 if (indsp->station_name != STR_NULL && indsp->station_name != STR_UNDEFINED) {
1371 str = indsp->station_name;
1375 int64 args_array[] = {STR_TOWN_NAME, st->town->index, st->index};
1376 StringParameters tmp_params(args_array);
1377 AppendStringWithArgs (buf, str, &tmp_params);
1379 break;
1382 case SCC_TOWN_NAME: { // {TOWN}
1383 const Town *t = Town::GetIfValid(args->GetInt32(SCC_TOWN_NAME));
1384 if (t == NULL) break;
1386 if (t->name != NULL) {
1387 int64 args_array[] = {(int64)(size_t)t->name};
1388 StringParameters tmp_params(args_array);
1389 AppendStringWithArgs (buf, STR_JUST_RAW_STRING, &tmp_params);
1390 } else {
1391 AppendTownName (buf, t);
1393 break;
1396 case SCC_WAYPOINT_NAME: { // {WAYPOINT}
1397 Waypoint *wp = Waypoint::GetIfValid(args->GetInt32(SCC_WAYPOINT_NAME));
1398 if (wp == NULL) break;
1400 if (wp->name != NULL) {
1401 int64 args_array[] = {(int64)(size_t)wp->name};
1402 StringParameters tmp_params(args_array);
1403 AppendStringWithArgs (buf, STR_JUST_RAW_STRING, &tmp_params);
1404 } else {
1405 int64 args_array[] = {wp->town->index, wp->town_cn + 1};
1406 StringParameters tmp_params(args_array);
1407 StringID str = ((wp->string_id == STR_SV_STNAME_BUOY) ? STR_FORMAT_BUOY_NAME : STR_FORMAT_WAYPOINT_NAME);
1408 if (wp->town_cn != 0) str++;
1409 AppendStringWithArgs (buf, str, &tmp_params);
1411 break;
1414 case SCC_VEHICLE_NAME: { // {VEHICLE}
1415 const Vehicle *v = Vehicle::GetIfValid(args->GetInt32(SCC_VEHICLE_NAME));
1416 if (v == NULL) break;
1418 if (v->name != NULL) {
1419 int64 args_array[] = {(int64)(size_t)v->name};
1420 StringParameters tmp_params(args_array);
1421 AppendStringWithArgs (buf, STR_JUST_RAW_STRING, &tmp_params);
1422 } else {
1423 int64 args_array[] = {v->unitnumber};
1424 StringParameters tmp_params(args_array);
1426 StringID str;
1427 switch (v->type) {
1428 default: str = STR_INVALID_VEHICLE; break;
1429 case VEH_TRAIN: str = STR_SV_TRAIN_NAME; break;
1430 case VEH_ROAD: str = STR_SV_ROAD_VEHICLE_NAME; break;
1431 case VEH_SHIP: str = STR_SV_SHIP_NAME; break;
1432 case VEH_AIRCRAFT: str = STR_SV_AIRCRAFT_NAME; break;
1435 AppendStringWithArgs (buf, str, &tmp_params);
1437 break;
1440 case SCC_SIGN_NAME: { // {SIGN}
1441 const Sign *si = Sign::GetIfValid(args->GetInt32());
1442 if (si == NULL) break;
1444 if (si->name != NULL) {
1445 int64 args_array[] = {(int64)(size_t)si->name};
1446 StringParameters tmp_params(args_array);
1447 AppendStringWithArgs (buf, STR_JUST_RAW_STRING, &tmp_params);
1448 } else {
1449 StringParameters tmp_params(NULL, 0, NULL);
1450 AppendStringWithArgs (buf, STR_DEFAULT_SIGN_NAME, &tmp_params);
1452 break;
1455 case SCC_STATION_FEATURES: { // {STATIONFEATURES}
1456 AppendStationSpecialString (buf, args->GetInt32(SCC_STATION_FEATURES));
1457 break;
1460 default:
1461 buf->append_utf8 (b);
1462 break;
1468 static void AppendStationSpecialString (stringb *buf, int x)
1470 if (x & FACIL_TRAIN ) buf->append_utf8 (SCC_TRAIN);
1471 if (x & FACIL_TRUCK_STOP) buf->append_utf8 (SCC_LORRY);
1472 if (x & FACIL_BUS_STOP ) buf->append_utf8 (SCC_BUS);
1473 if (x & FACIL_DOCK ) buf->append_utf8 (SCC_SHIP);
1474 if (x & FACIL_AIRPORT ) buf->append_utf8 (SCC_PLANE);
1478 static const char *GenSurname (uint32 arg)
1480 static const char * const surname_list[] = {
1481 "Adams", "Allan", "Baker", "Bigwig", "Black",
1482 "Bloggs", "Brown", "Campbell", "Gordon", "Hamilton",
1483 "Hawthorn", "Higgins", "Green", "Gribble", "Jones",
1484 "McAlpine", "MacDonald", "McIntosh", "Muir", "Murphy",
1485 "Nelson", "O'Donnell", "Parker", "Phillips", "Pilkington",
1486 "Quigley", "Sharkey", "Thomson", "Watkins",
1489 static const char * const silly_list[] = {
1490 "Grumpy", "Dozy", "Speedy", "Nosey", "Dribble", "Mushroom",
1491 "Cabbage", "Sniffle", "Fishy", "Swindle", "Sneaky", "Nutkins",
1494 const char * const *base;
1495 uint num;
1497 if (_settings_game.game_creation.landscape == LT_TOYLAND) {
1498 base = silly_list;
1499 num = lengthof(silly_list);
1500 } else {
1501 base = surname_list;
1502 num = lengthof(surname_list);
1505 return base[num * GB(arg, 16, 8) >> 8];
1508 static void GenAndCoName (stringb *buf, uint32 arg)
1510 buf->append (GenSurname (arg));
1511 buf->append (" & Co.");
1514 static void GenPresidentName (stringb *buf, uint32 x)
1516 static const char initials[] = {
1517 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
1518 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'W',
1521 char initial[] = "?. ";
1522 uint i;
1524 initial[0] = initials[sizeof(initials) * GB(x, 0, 8) >> 8];
1525 buf->append (initial);
1527 i = (sizeof(initials) + 35) * GB(x, 8, 8) >> 8;
1528 if (i < sizeof(initials)) {
1529 initial[0] = initials[i];
1530 buf->append (initial);
1533 buf->append (GenSurname (x));
1536 static void AppendSpecialNameString (stringb *buf, int ind, StringParameters *args)
1538 static const char * const silly_company_names[] = {
1539 "Bloggs Brothers",
1540 "Tiny Transport Ltd.",
1541 "Express Travel",
1542 "Comfy-Coach & Co.",
1543 "Crush & Bump Ltd.",
1544 "Broken & Late Ltd.",
1545 "Sam Speedy & Son",
1546 "Supersonic Travel",
1547 "Mike's Motors",
1548 "Lightning International",
1549 "Pannik & Loozit Ltd.",
1550 "Inter-City Transport",
1551 "Getout & Pushit Ltd.",
1554 switch (ind) {
1555 case 1: // not used
1556 buf->append (silly_company_names[min(args->GetInt32() & 0xFFFF, lengthof(silly_company_names) - 1)]);
1557 return;
1559 case 2: // used for Foobar & Co company names
1560 GenAndCoName (buf, args->GetInt32());
1561 return;
1563 case 3: // President name
1564 GenPresidentName (buf, args->GetInt32());
1565 return;
1568 /* town name? */
1569 if (IsInsideMM(ind - 6, 0, SPECSTR_TOWNNAME_LAST - SPECSTR_TOWNNAME_START + 1)) {
1570 GenerateTownNameString (buf, ind - 6, args->GetInt32());
1571 buf->append (" Transport");
1572 return;
1575 /* language name? */
1576 if (IsInsideMM(ind, (SPECSTR_LANGUAGE_START - 0x70E4), (SPECSTR_LANGUAGE_END - 0x70E4) + 1)) {
1577 int i = ind - (SPECSTR_LANGUAGE_START - 0x70E4);
1578 buf->append (&_languages[i] == _current_language ? _current_language->own_name : _languages[i].name);
1579 return;
1582 /* resolution size? */
1583 if (IsInsideMM(ind, (SPECSTR_RESOLUTION_START - 0x70E4), (SPECSTR_RESOLUTION_END - 0x70E4) + 1)) {
1584 int i = ind - (SPECSTR_RESOLUTION_START - 0x70E4);
1585 buf->append_fmt ("%ux%u", _resolutions[i].width, _resolutions[i].height);
1586 return;
1589 /* screenshot format name? */
1590 if (IsInsideMM(ind, (SPECSTR_SCREENSHOT_START - 0x70E4), (SPECSTR_SCREENSHOT_END - 0x70E4) + 1)) {
1591 int i = ind - (SPECSTR_SCREENSHOT_START - 0x70E4);
1592 buf->append (GetScreenshotFormatDesc(i));
1593 return;
1596 NOT_REACHED();
1599 #ifdef ENABLE_NETWORK
1600 extern void SortNetworkLanguages();
1601 #else /* ENABLE_NETWORK */
1602 static inline void SortNetworkLanguages() {}
1603 #endif /* ENABLE_NETWORK */
1606 * Check whether the header is a valid header for OpenTTD.
1607 * @return true iff the header is deemed valid.
1609 bool LanguagePackHeader::IsValid() const
1611 return this->ident == TO_LE32(LanguagePackHeader::IDENT) &&
1612 this->version == TO_LE32(LANGUAGE_PACK_VERSION) &&
1613 this->plural_form < LANGUAGE_MAX_PLURAL &&
1614 this->text_dir <= 1 &&
1615 this->newgrflangid < MAX_LANG &&
1616 this->num_genders < MAX_NUM_GENDERS &&
1617 this->num_cases < MAX_NUM_CASES &&
1618 StrValid(this->name, lastof(this->name)) &&
1619 StrValid(this->own_name, lastof(this->own_name)) &&
1620 StrValid(this->isocode, lastof(this->isocode)) &&
1621 StrValid(this->digit_group_separator, lastof(this->digit_group_separator)) &&
1622 StrValid(this->digit_group_separator_currency, lastof(this->digit_group_separator_currency)) &&
1623 StrValid(this->digit_decimal_separator, lastof(this->digit_decimal_separator));
1627 * Read a particular language.
1628 * @param lang The metadata about the language.
1629 * @return Whether the loading went okay or not.
1631 bool ReadLanguagePack(const LanguageMetadata *lang)
1633 /* Current language pack */
1634 size_t len;
1635 LanguagePack *lang_pack = (LanguagePack *)ReadFileToMem(lang->file, &len, 1U << 20);
1636 if (lang_pack == NULL) return false;
1638 /* End of read data (+ terminating zero added in ReadFileToMem()) */
1639 const char *end = (char *)lang_pack + len + 1;
1641 /* We need at least one byte of lang_pack->data */
1642 if (end <= lang_pack->data || !lang_pack->IsValid()) {
1643 free(lang_pack);
1644 return false;
1647 #if TTD_ENDIAN == TTD_BIG_ENDIAN
1648 for (uint i = 0; i < TEXT_TAB_END; i++) {
1649 lang_pack->offsets[i] = ReadLE16Aligned(&lang_pack->offsets[i]);
1651 #endif /* TTD_ENDIAN == TTD_BIG_ENDIAN */
1653 uint count = 0;
1654 for (uint i = 0; i < TEXT_TAB_END; i++) {
1655 uint16 num = lang_pack->offsets[i];
1656 if (num > TAB_SIZE) {
1657 free(lang_pack);
1658 return false;
1661 _langtab_start[i] = count;
1662 _langtab_num[i] = num;
1663 count += num;
1666 /* Allocate offsets */
1667 char **langpack_offs = xmalloct<char *>(count);
1669 /* Fill offsets */
1670 char *s = lang_pack->data;
1671 len = (byte)*s++;
1672 for (uint i = 0; i < count; i++) {
1673 if (s + len >= end) {
1674 free(lang_pack);
1675 free(langpack_offs);
1676 return false;
1678 if (len >= 0xC0) {
1679 len = ((len & 0x3F) << 8) + (byte)*s++;
1680 if (s + len >= end) {
1681 free(lang_pack);
1682 free(langpack_offs);
1683 return false;
1686 langpack_offs[i] = s;
1687 s += len;
1688 len = (byte)*s;
1689 *s++ = '\0'; // zero terminate the string
1692 free(_langpack);
1693 _langpack = lang_pack;
1695 free(_langpack_offs);
1696 _langpack_offs = langpack_offs;
1698 _current_language = lang;
1699 _current_text_dir = (TextDirection)_current_language->text_dir;
1700 const char *c_file = strrchr(_current_language->file, PATHSEPCHAR) + 1;
1701 bstrcpy (_config_language_file, c_file);
1702 SetCurrentGrfLangID(_current_language->newgrflangid);
1704 #ifdef WITH_ICU_SORT
1705 /* Delete previous collator. */
1706 if (_current_collator != NULL) {
1707 delete _current_collator;
1708 _current_collator = NULL;
1711 /* Create a collator instance for our current locale. */
1712 UErrorCode status = U_ZERO_ERROR;
1713 _current_collator = Collator::createInstance(Locale(_current_language->isocode), status);
1714 /* Sort number substrings by their numerical value. */
1715 if (_current_collator != NULL) _current_collator->setAttribute(UCOL_NUMERIC_COLLATION, UCOL_ON, status);
1716 /* Avoid using the collator if it is not correctly set. */
1717 if (U_FAILURE(status)) {
1718 delete _current_collator;
1719 _current_collator = NULL;
1721 #endif /* WITH_ICU_SORT */
1723 /* Some lists need to be sorted again after a language change. */
1724 ReconsiderGameScriptLanguage();
1725 InitializeSortedCargoSpecs();
1726 SortIndustryTypes();
1727 BuildIndustriesLegend();
1728 SortNetworkLanguages();
1729 BuildContentTypeStringList();
1730 InvalidateWindowClassesData(WC_BUILD_VEHICLE); // Build vehicle window.
1731 InvalidateWindowClassesData(WC_TRAINS_LIST); // Train group window.
1732 InvalidateWindowClassesData(WC_ROADVEH_LIST); // Road vehicle group window.
1733 InvalidateWindowClassesData(WC_SHIPS_LIST); // Ship group window.
1734 InvalidateWindowClassesData(WC_AIRCRAFT_LIST); // Aircraft group window.
1735 InvalidateWindowClassesData(WC_INDUSTRY_DIRECTORY); // Industry directory window.
1736 InvalidateWindowClassesData(WC_STATION_LIST); // Station list window.
1738 return true;
1741 /* Win32 implementation in win32.cpp.
1742 * OS X implementation in os/macosx/macos.mm. */
1743 #if !(defined(WIN32) || defined(__APPLE__))
1745 * Determine the current charset based on the environment
1746 * First check some default values, after this one we passed ourselves
1747 * and if none exist return the value for $LANG
1748 * @param param environment variable to check conditionally if default ones are not
1749 * set. Pass NULL if you don't want additional checks.
1750 * @return return string containing current charset, or NULL if not-determinable
1752 const char *GetCurrentLocale(const char *param)
1754 const char *env;
1756 env = getenv("LANGUAGE");
1757 if (env != NULL) return env;
1759 env = getenv("LC_ALL");
1760 if (env != NULL) return env;
1762 if (param != NULL) {
1763 env = getenv(param);
1764 if (env != NULL) return env;
1767 return getenv("LANG");
1769 #else
1770 const char *GetCurrentLocale(const char *param);
1771 #endif /* !(defined(WIN32) || defined(__APPLE__)) */
1773 int CDECL StringIDSorter(const StringID *a, const StringID *b)
1775 char stra[512];
1776 char strb[512];
1777 GetString (stra, *a);
1778 GetString (strb, *b);
1780 return strnatcmp(stra, strb);
1784 * Get the language with the given NewGRF language ID.
1785 * @param newgrflangid NewGRF languages ID to check.
1786 * @return The language's metadata, or NULL if it is not known.
1788 const LanguageMetadata *GetLanguage(byte newgrflangid)
1790 for (const LanguageMetadata *lang = _languages.Begin(); lang != _languages.End(); lang++) {
1791 if (newgrflangid == lang->newgrflangid) return lang;
1794 return NULL;
1798 * Reads the language file header and checks compatibility.
1799 * @param file the file to read
1800 * @param hdr the place to write the header information to
1801 * @return true if and only if the language file is of a compatible version
1803 static bool GetLanguageFileHeader(const char *file, LanguagePackHeader *hdr)
1805 FILE *f = fopen(file, "rb");
1806 if (f == NULL) return false;
1808 size_t read = fread(hdr, sizeof(*hdr), 1, f);
1809 fclose(f);
1811 bool ret = read == 1 && hdr->IsValid();
1813 /* Convert endianness for the windows language ID */
1814 if (ret) {
1815 hdr->missing = FROM_LE16(hdr->missing);
1816 hdr->winlangid = FROM_LE16(hdr->winlangid);
1818 return ret;
1822 * Gets a list of languages from the given directory.
1823 * @param path the base directory to search in
1825 static void GetLanguageList(const char *path)
1827 DIR *dir = ttd_opendir(path);
1828 if (dir != NULL) {
1829 struct dirent *dirent;
1830 while ((dirent = readdir(dir)) != NULL) {
1831 const char *d_name = FS2OTTD(dirent->d_name);
1832 const char *extension = strrchr(d_name, '.');
1834 /* Not a language file */
1835 if (extension == NULL || strcmp(extension, ".lng") != 0) continue;
1837 LanguageMetadata lmd;
1838 bstrfmt (lmd.file, "%s%s", path, d_name);
1840 /* Check whether the file is of the correct version */
1841 if (!GetLanguageFileHeader(lmd.file, &lmd)) {
1842 DEBUG(misc, 3, "%s is not a valid language file", lmd.file);
1843 } else if (GetLanguage(lmd.newgrflangid) != NULL) {
1844 DEBUG(misc, 3, "%s's language ID is already known", lmd.file);
1845 } else {
1846 *_languages.Append() = lmd;
1849 closedir(dir);
1854 * Make a list of the available language packs. Put the data in
1855 * #_languages list.
1857 void InitializeLanguagePacks()
1859 Searchpath sp;
1861 FOR_ALL_SEARCHPATHS(sp) {
1862 char path[MAX_PATH];
1863 FioGetFullPath (path, lengthof(path), sp, LANG_DIR);
1864 GetLanguageList(path);
1866 if (_languages.Length() == 0) usererror("No available language packs (invalid versions?)");
1868 /* Acquire the locale of the current system */
1869 const char *lang = GetCurrentLocale("LC_MESSAGES");
1870 if (lang == NULL) lang = "en_GB";
1872 const LanguageMetadata *chosen_language = NULL; ///< Matching the language in the configuration file or the current locale
1873 const LanguageMetadata *language_fallback = NULL; ///< Using pt_PT for pt_BR locale when pt_BR is not available
1874 const LanguageMetadata *en_GB_fallback = _languages.Begin(); ///< Fallback when no locale-matching language has been found
1876 /* Find a proper language. */
1877 for (const LanguageMetadata *lng = _languages.Begin(); lng != _languages.End(); lng++) {
1878 /* We are trying to find a default language. The priority is by
1879 * configuration file, local environment and last, if nothing found,
1880 * English. */
1881 const char *lang_file = strrchr(lng->file, PATHSEPCHAR) + 1;
1882 if (strcmp(lang_file, _config_language_file) == 0) {
1883 chosen_language = lng;
1884 break;
1887 if (strcmp (lng->isocode, "en_GB") == 0) en_GB_fallback = lng;
1888 if (strncmp(lng->isocode, lang, 5) == 0) chosen_language = lng;
1889 if (strncmp(lng->isocode, lang, 2) == 0) language_fallback = lng;
1892 /* We haven't found the language in the config nor the one in the locale.
1893 * Now we set it to one of the fallback languages */
1894 if (chosen_language == NULL) {
1895 chosen_language = (language_fallback != NULL) ? language_fallback : en_GB_fallback;
1898 if (!ReadLanguagePack(chosen_language)) usererror("Can't read language pack '%s'", chosen_language->file);
1902 * Get the ISO language code of the currently loaded language.
1903 * @return the ISO code.
1905 const char *GetCurrentLanguageIsoCode()
1907 return _langpack->isocode;
1911 * Check whether there are glyphs missing in the current language.
1912 * @return If glyphs are missing, return \c true, else return \c false.
1914 bool MissingGlyphSearcher::FindMissingGlyphs (void)
1916 InitFreeType(this->Monospace());
1917 const Sprite *question_mark[FS_END];
1919 for (FontSize size = this->Monospace() ? FS_MONO : FS_BEGIN; size < (this->Monospace() ? FS_END : FS_MONO); size++) {
1920 question_mark[size] = FontCache::Get(size)->GetCharGlyph('?');
1923 this->Reset();
1924 for (const char *text = this->NextString(); text != NULL; text = this->NextString()) {
1925 FontSize size = this->DefaultSize();
1926 for (;;) {
1927 WChar c = Utf8Consume(&text);
1928 if (c == '\0') {
1929 break;
1930 } else if (c == SCC_TINYFONT) {
1931 size = FS_SMALL;
1932 } else if (c == SCC_BIGFONT) {
1933 size = FS_LARGE;
1934 } else if (!IsInsideMM (c, SCC_SPRITE_START, SCC_SPRITE_END)
1935 && IsPrintable (c)
1936 && !IsTextDirectionChar (c)
1937 && c != '?'
1938 && FontCache::Get(size)->GetCharGlyph(c) == question_mark[size]) {
1939 /* The character is printable, but not in the normal font. This is the case we were testing for. */
1940 return true;
1944 return false;
1947 /** Helper for searching through the language pack. */
1948 class LanguagePackGlyphSearcher : public MissingGlyphSearcher {
1949 uint i; ///< Iterator for the primary language tables.
1950 uint j; ///< Iterator for the secondary language tables.
1952 public:
1953 CONSTEXPR LanguagePackGlyphSearcher()
1954 : MissingGlyphSearcher (FS_NORMAL, false), i(0), j(0)
1958 private:
1959 /* virtual */ void Reset()
1961 this->i = 0;
1962 this->j = 0;
1965 /* virtual */ const char *NextString()
1967 if (this->i >= TEXT_TAB_END) return NULL;
1969 const char *ret = _langpack_offs[_langtab_start[this->i] + this->j];
1971 this->j++;
1972 while (this->i < TEXT_TAB_END && this->j >= _langtab_num[this->i]) {
1973 this->i++;
1974 this->j = 0;
1977 return ret;
1982 * Check whether the currently loaded language pack
1983 * uses characters that the currently loaded font
1984 * does not support. If this is the case an error
1985 * message will be shown in English. The error
1986 * message will not be localized because that would
1987 * mean it might use characters that are not in the
1988 * font, which is the whole reason this check has
1989 * been added.
1990 * @param base_font Whether to look at the base font as well.
1991 * @param searcher The methods to use to search for strings to check.
1992 * If NULL the loaded language pack searcher is used.
1994 void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher)
1996 static LanguagePackGlyphSearcher pack_searcher;
1997 if (searcher == NULL) searcher = &pack_searcher;
1998 bool bad_font = !base_font || searcher->FindMissingGlyphs();
1999 #ifdef WITH_FREETYPE
2000 if (bad_font) {
2001 /* We found an unprintable character... lets try whether we can find
2002 * a fallback font that can print the characters in the current language. */
2003 FreeTypeSettings backup;
2004 memcpy(&backup, &_freetype, sizeof(backup));
2006 bad_font = !SetFallbackFont(&_freetype, _langpack->isocode, _langpack->winlangid, searcher);
2008 memcpy(&_freetype, &backup, sizeof(backup));
2010 if (bad_font && base_font) {
2011 /* Our fallback font does miss characters too, so keep the
2012 * user chosen font as that is more likely to be any good than
2013 * the wild guess we made */
2014 InitFreeType(searcher->Monospace());
2017 #endif
2019 if (bad_font) {
2020 /* All attempts have failed. Display an error. */
2021 SetDParamStr (0,
2022 SCCSTR_YELLOW "The current font is missing some of "
2023 "the characters used in the texts for this language. "
2024 "Read the readme to see how to solve this.");
2025 ShowErrorMessage(STR_JUST_RAW_STRING, INVALID_STRING_ID, WL_WARNING);
2027 /* Reset the font width */
2028 LoadStringWidthTable(searcher->Monospace());
2029 return;
2032 /* Update the font with cache */
2033 LoadStringWidthTable(searcher->Monospace());
2035 #if !defined(WITH_ICU_LAYOUT)
2037 * For right-to-left languages we need the ICU library. If
2038 * we do not have support for that library we warn the user
2039 * about it with a message.
2041 if (_current_text_dir != TD_LTR) {
2042 SetDParamStr (0,
2043 SCCSTR_YELLOW "This version of OpenTTD does not "
2044 "support right-to-left languages. "
2045 "Recompile with icu enabled.");
2046 ShowErrorMessage(STR_JUST_RAW_STRING, INVALID_STRING_ID, WL_ERROR);
2048 #endif /* !WITH_ICU_LAYOUT */