Project Alice
Loading...
Searching...
No Matches
text.cpp
Go to the documentation of this file.
1#include <string_view>
2
3#include "nations.hpp"
4#include "text.hpp"
5#include "system_state.hpp"
6#include "parsers.hpp"
7#include "simple_fs.hpp"
8#include <type_traits>
9#ifdef _WIN32
10#include <icu.h>
11#else
12#include <unicode/ubrk.h>
13#include <unicode/utypes.h>
14#include <unicode/ubidi.h>
15#endif
16
17namespace text {
19 switch(in) {
20 case 'W':
21 return text_color::white;
22 case 'G':
23 return text_color::green;
24 case 'R':
25 return text_color::red;
26 case 'Y':
27 return text_color::yellow;
28 case 'b':
29 return text_color::black;
30 case 'B':
32 case 'g':
34 case 'L':
35 return text_color::lilac;
36 case 'O':
37 return text_color::orange;
38 case 'l':
40 case 'C':
42 case 'J':
44 case 'I':
45 return text_color::brown;
46 case '!':
47 return text_color::reset;
48 default:
50 }
51}
52
53inline bool is_qmark_color(char in) {
55}
56
57std::string lowercase_str(std::string_view sv) {
58 std::string result;
59 result.reserve(sv.length());
60 for(auto ch : sv) {
61 result += char(tolower(ch));
62 }
63 return result;
64}
65
66uint32_t codepoint_from_utf8(char const* start, char const* end) {
67 uint8_t byte1 = uint8_t(start + 0 < end ? start[0] : 0);
68 uint8_t byte2 = uint8_t(start + 1 < end ? start[1] : 0);
69 uint8_t byte3 = uint8_t(start + 2 < end ? start[2] : 0);
70 uint8_t byte4 = uint8_t(start + 3 < end ? start[3] : 0);
71 if((byte1 & 0x80) == 0) {
72 return uint32_t(byte1);
73 } else if((byte1 & 0xE0) == 0xC0) {
74 return uint32_t(byte2 & 0x3F) | (uint32_t(byte1 & 0x1F) << 6);
75 } else if((byte1 & 0xF0) == 0xE0) {
76 return uint32_t(byte3 & 0x3F) | (uint32_t(byte2 & 0x3F) << 6) | (uint32_t(byte1 & 0x0F) << 12);
77 } else if((byte1 & 0xF8) == 0xF0) {
78 return uint32_t(byte4 & 0x3F) | (uint32_t(byte3 & 0x3F) << 6) | (uint32_t(byte2 & 0x3F) << 12) | (uint32_t(byte1 & 0x07) << 18);
79 }
80 return 0;
81}
82size_t size_from_utf8(char const* start, char const*) {
83 uint8_t b = uint8_t(start[0]);
84 return ((b & 0x80) == 0) ? 1 : ((b & 0xE0) == 0xC0) ? 2
85 : ((b & 0xF0) == 0xE0) ? 3 : ((b & 0xF8) == 0xF0) ? 4
86 : 1;
87}
88bool codepoint_is_space(uint32_t c) noexcept {
89 return (c == 0x3000 || c == 0x205F || c == 0x202F || c == 0x2029 || c == 0x2028 || c == 0x00A0
90 || c == 0x0085 || c <= 0x0020 || (0x2000 <= c && c <= 0x200A));
91}
93 return c == 0x2029 || c == 0x2028 || c == uint32_t('\n') || c == uint32_t('\r');
94}
95
96void consume_csv_file(sys::state& state, char const* file_content, uint32_t file_size, int32_t target_column, bool as_unicode) {
97 auto cpos = file_content;
98
99 if(as_unicode) {
100 if(file_size >= 3) {
101 // skip utf8 BOM if present
102 // 0xEF, 0xBB, 0xBF)
103 if(int(file_content[0]) == 0xEF && int(file_content[1]) == 0xBB && int(file_content[2]) == 0xBF)
104 cpos += 3;
105 }
106 while(cpos < file_content + file_size) {
107 cpos = parsers::parse_fixed_amount_csv_values<14>(cpos, file_content + file_size, ';', [&](std::string_view const* values) {
108 auto key = state.add_key_utf8(values[0]);
109 auto entry = state.add_locale_data_utf8(values[target_column]);
110 state.locale_key_to_text_sequence.insert_or_assign(key, entry);
111 });
112 }
113 } else {
114 while(cpos < file_content + file_size) {
115 cpos = parsers::parse_fixed_amount_csv_values<14>(cpos, file_content + file_size, ';', [&](std::string_view const* values) {
116 auto key = state.add_key_win1252(values[0]);
117 auto entry = state.add_locale_data_win1252(values[target_column]);
118 state.locale_key_to_text_sequence.insert_or_assign(key, entry);
119 });
120 }
121 }
122}
123
124template<size_t N>
125bool is_fixed_token_ci(std::string_view v, char const (&t)[N]) {
126 if(v.length() != (N - 1))
127 return false;
128 for(unsigned int i = 0; i < N - 1; ++i) {
129 if(tolower(v[i]) != t[i])
130 return false;
131 }
132 return true;
133}
134
135#define CT_STRING_ENUM(X) else if(is_fixed_token_ci(v, #X)) return variable_type::X;
136
138 if(v.length() == 1) {
139 if(false) { }
145 } else if(v.length() == 2) {
146 if(false) { }
151 } else if(v.length() == 3) {
152 if(false) { }
168 else if(is_fixed_token_ci(v, "for")) return variable_type::vtype_for;
183 else if(is_fixed_token_ci(v, "new")) return variable_type::vtype_new;
206 } else if(v.length() == 4) {
207 if(false) { }
256 } else if(v.length() == 5) {
257 if(false) { }
296 else if(is_fixed_token_ci(v, "union")) return variable_type::vtype_union;
304 } else if(v.length() == 6) {
305 if(false) { }
315 else if(is_fixed_token_ci(v, "friend")) return variable_type::vtype_friend;
343 } else if(v.length() == 7) {
344 if(false) { }
388 } else if(v.length() == 8) {
389 if(false) { }
406 else if(is_fixed_token_ci(v, "operator")) return variable_type::vtype_operator;
428 } else if(v.length() == 9) {
429 if(false) { }
452 } else if(v.length() == 10) {
453 if(false) { }
476 } else if(v.length() == 11) {
477 if(false) { }
493 } else if(v.length() == 12) {
494 if(false) { }
505 } else if(v.length() == 13) {
506 if(false) { }
525 } else if(v.length() == 14) {
526 if(false) { }
536 } else if(v.length() == 15) {
537 if(false) { }
542 } else if(v.length() == 16) {
543 if(false) { }
548 } else if(v.length() == 17) {
549 if(false) { }
551 } else if(v.length() == 18) {
552 if(false) { }
558 } else if(v.length() == 19) {
559 if(false) { }
563 } else if(v.length() == 20) {
564 if(false) { }
565 } else if(v.length() == 21) {
566 if(false) { }
568 } else if(v.length() == 22) {
569 if(false) { }
573 } else if(v.length() == 23) {
574 if(false) { }
575 } else if(v.length() == 24) {
576 if(false) { }
579 } else if(is_fixed_token_ci(v, "invested_in_us_message")) {
581 }
582
584}
585#undef CT_STRING_ENUM
586
587char16_t win1250toUTF16(char in) {
588 constexpr static char16_t converted[256] =
589 // 0 1 2 3 4 5 6 7 8 9 A B C D E F
590 /*0*/ { u' ', u'\u0001', u'\u0002', u'\u0003', u'\u0004', u' ', u' ', u' ', u' ', u'\t', u'\n', u' ', u' ', u' ', u' ', u' ',
591 /*1*/ u' ', u' ', u' ', u' ', u' ', u' ', u' ', u' ', u' ', u' ', u' ', u' ', u' ', u' ', u' ', u' ',
592 /*2*/ u' ', u'!', u'\"', u'#', u'$', u'%', u'&', u'\'', u'(', u')', u'*', u'+', u',', u'-', u'.', u'/',
593 /*3*/ u'0', u'1', u'2', u'3', u'4', u'5', u'6', u'7', u'8', u'9', u':', u';', u'<', u'=', u'>', u'?',
594 /*4*/ u'@', u'A', u'B', u'C', u'D', u'E', u'F', u'G', u'H', u'I', u'J', u'K', u'L', u'M', u'N', u'O',
595 /*5*/ u'P', u'Q', u'R', u'S', u'T', u'U', u'V', u'W', u'X', u'Y', u'Z', u'[', u'\\', u']', u'^', u'_',
596 /*6*/ u'`', u'a', u'b', u'c', u'd', u'e', u'f', u'g', u'h', u'i', u'j', u'k', u'l', u'm', u'n', u'o',
597 /*7*/ u'p', u'q', u'r', u's', u't', u'u', u'v', u'w', u'x', u'y', u'z', u'{', u'|', u'}', u'~', u' ',
598 /*8*/ u'\u20AC', u' ', u'\u201A', u' ', u'\u201E', u'\u2026', u'\u2020', u'\u2021', u' ', u'\u2030', u'\u0160', u'\u2039', u'\u015A', u'\u0164', u'\u017D', u'\u0179',
599 /*9*/ u' ', u'\u2018', u'\u2019', u'\u201C', u'\u201D', u'\u2022', u'\u2013', u'\u2014', u' ', u'\u2122', u'\u0161',
600 u'\u203A', u'\u015B', u'\u0165', u'\u017E', u'\u017A',
601 /*A*/ u'\u00A0', u'\u02C7', u'\u02D8', u'\u00A2', u'\u00A3', u'\u0104', u'\u00A6', u'\u00A7', u'\u00A8', u'\u00A9',
602 u'\u015E', u'\u00AB', u'\u00AC', u'-', u'\u00AE', u'\u017B',
603 /*B*/ u'\u00B0', u'\u00B1', u'\u02DB', u'\u0142', u'\u00B4', u'\u00B5', u'\u00B6', u'\u00B7', u'\u00B8', u'\u0105',
604 u'\u015F', u'\u00BB', u'\u013D', u'\u02DD', u'\u013E', u'\u017C',
605 /*C*/ u'\u0154', u'\u00C1', u'\u00C2', u'\u0102', u'\u00C4', u'\u0139', u'\u0106', u'\u00C7', u'\u010C', u'\u00C9',
606 u'\u0118', u'\u00CB', u'\u011A', u'\u00CD', u'\u00CE', u'\u010E',
607 /*D*/ u'\u0110', u'\u0143', u'\u0147', u'\u00D3', u'\u00D4', u'\u0150', u'\u00D6', u'\u00D7', u'\u0158', u'\u016E',
608 u'\u00DA', u'\u0170', u'\u00DC', u'\u00DD', u'\u0162', u'\u00DF',
609 /*E*/ u'\u0115', u'\u00E1', u'\u00E2', u'\u0103', u'\u00E4', u'\u013A', u'\u0107', u'\u00E7', u'\u00E8', u'\u00E9',
610 u'\u0119', u'\u00EB', u'\u011B', u'\u00ED', u'\u00EE', u'\u010F',
611 /*F*/ u'\u0111', u'\u0144', u'\u0148', u'\u00F3', u'\u00F4', u'\u0151', u'\u00F6', u'\u00F7', u'\u0159', u'\u016F',
612 u'\u00FA', u'\u0171', u'\u00FC', u'\u00FD', u'\u0163', u'\u02D9'};
613
614 return converted[(uint8_t)in];
615}
616
617std::string produce_simple_string(sys::state const& state, dcon::text_key id) {
618 std::string result;
619
620 if(!id)
621 return result;
622
623 std::string_view sv;
624 if(auto it = state.locale_key_to_text_sequence.find(id); it != state.locale_key_to_text_sequence.end()) {
625 sv = state.locale_string_view(it->second);
626 } else {
627 sv = state.to_string_view(id);
628 }
629
630 char const* section_start = sv.data();
631 for(char const* pos = sv.data(); pos < sv.data() + sv.length();) {
632 bool colour_esc = false;
633 if(pos + 1 < sv.data() + sv.length() && uint8_t(*pos) == 0xC2 && uint8_t(*(pos + 1)) == 0xA7) {
634 if(section_start < pos)
635 result += std::string_view(section_start, pos - section_start);
636
637 pos += 2;
638 section_start = pos;
639 if(pos < sv.data() + sv.length()) {
640 pos += 1;
641 section_start = pos;
642 }
643 } else if(pos + 2 < sv.data() + sv.length() && uint8_t(*pos) == 0xEF && uint8_t(*(pos + 1)) == 0xBF && uint8_t(*(pos + 2)) == 0xBD && is_qmark_color(*(pos + 3))) {
644 if(section_start < pos)
645 result += std::string_view(section_start, pos - section_start);
646
647 section_start = pos += 3;
648
649 if(pos < sv.data() + sv.length()) {
650 pos += 1;
651 section_start = pos;
652 }
653 } else if(pos + 1 < sv.data() + sv.length() && *pos == '?' && is_qmark_color(*(pos + 1))) {
654 if(section_start < pos)
655 result += std::string_view(section_start, pos - section_start);
656
657 pos += 1;
658 section_start = pos;
659
660 if(pos < sv.data() + sv.length()) {
661 pos += 1;
662 section_start = pos;
663 }
664 } else if(*pos == '$') {
665 if(section_start < pos)
666 result += std::string_view(section_start, pos - section_start);
667
668 const char* vend = pos + 1;
669 for(; vend != sv.data() + sv.length() && *vend != '$'; ++vend)
670 ;
671
672 pos = vend + 1;
673 section_start = pos;
674 } else if(pos + 1 < sv.data() + sv.length() && *pos == '\\' && *(pos + 1) == 'n') {
675 result += std::string_view(section_start, pos - section_start);
676 section_start = pos += 2;
677 } else {
678 ++pos;
679 }
680 }
681
682 if(section_start < sv.data() + sv.length())
683 result += std::string_view(section_start, (sv.data() + sv.length()) - section_start);
684
685 return result;
686}
687std::string produce_simple_string(sys::state const& state, std::string_view txt) {
688 auto v = state.lookup_key(txt);
689 if(v)
690 return produce_simple_string(state, v);
691 else
692 return std::string(txt);
693}
694
695dcon::text_key find_or_add_key(sys::state& state, std::string_view key, bool as_unicode) {
696 if(as_unicode)
697 return state.add_key_utf8(key);
698 else
699 return state.add_key_win1252(key);
700}
701
702std::string prettify_currency(float num) {
703 constexpr static double mag[] = {
704 1.0,
705 1'000.0,
706 1'000'000.0,
707 1'000'000'000.0,
708 1'000'000'000'000.0,
709 1'000'000'000'000'000.0,
710 1'000'000'000'000'000'000.0
711 };
712 constexpr static char const* sufx_two[] = {
713 "%.2f £",
714 "%.2fK £",
715 "%.2fM £",
716 "%.2fB £",
717 "%.2fT £",
718 "%.2fP £",
719 "%.2fZ £"
720 };
721 constexpr static char const* sufx_one[] = {
722 "%.1f £",
723 "%.1fK £",
724 "%.1fM £",
725 "%.1fB £",
726 "%.1fT £",
727 "%.1fP £",
728 "%.1fZ £"
729 };
730 constexpr static char const* sufx_zero[] = {
731 "%.0f £",
732 "%.0fK £",
733 "%.0fM £",
734 "%.0fB £",
735 "%.0fT £",
736 "%.0fP £",
737 "%.0fZ £"
738 };
739 char buffer[200] = { 0 };
740 double dval = double(num);
741 if(std::abs(dval) <= 1.f) {
742 snprintf(buffer, sizeof(buffer), sufx_two[0], float(dval));
743 return std::string(buffer);
744 }
745 for(size_t i = std::extent_v<decltype(mag)>; i-- > 0;) {
746 if(std::abs(dval) >= mag[i]) {
747 auto reduced = dval / mag[i];
748 if(std::abs(reduced) < 10.0) {
749 snprintf(buffer, sizeof(buffer), sufx_two[i], float(reduced));
750 } else if(std::abs(reduced) < 100.0) {
751 snprintf(buffer, sizeof(buffer), sufx_one[i], float(reduced));
752 } else {
753 snprintf(buffer, sizeof(buffer), sufx_zero[i], float(reduced));
754 }
755 return std::string(buffer);
756 }
757 }
758 return std::string("#inf");
759}
760
761
762std::string prettify(int64_t num) {
763 if(num == 0)
764 return std::string("0");
765
766 constexpr static double mag[] = {
767 1.0,
768 1'000.0,
769 1'000'000.0,
770 1'000'000'000.0,
771 1'000'000'000'000.0,
772 1'000'000'000'000'000.0,
773 1'000'000'000'000'000'000.0
774 };
775 constexpr static char const* sufx_two[] = {
776 "%.0f",
777 "%.2fK",
778 "%.2fM",
779 "%.2fB",
780 "%.2fT",
781 "%.2fP",
782 "%.2fZ"
783 };
784 constexpr static char const* sufx_one[] = {
785 "%.0f",
786 "%.1fK",
787 "%.1fM",
788 "%.1fB",
789 "%.1fT",
790 "%.1fP",
791 "%.1fZ"
792 };
793 constexpr static char const* sufx_zero[] = {
794 "%.0f",
795 "%.0fK",
796 "%.0fM",
797 "%.0fB",
798 "%.0fT",
799 "%.0fP",
800 "%.0fZ"
801 };
802
803 char buffer[200] = { 0 };
804 double dval = double(num);
805 for(size_t i = std::extent_v<decltype(mag)>; i-- > 0;) {
806 if(std::abs(dval) >= mag[i]) {
807 auto reduced = dval / mag[i];
808 if(std::abs(reduced) < 10.0) {
809 snprintf(buffer, sizeof(buffer), sufx_two[i], float(reduced));
810 } else if(std::abs(reduced) < 100.0) {
811 snprintf(buffer, sizeof(buffer), sufx_one[i], float(reduced));
812 } else {
813 snprintf(buffer, sizeof(buffer), sufx_zero[i], float(reduced));
814 }
815 return std::string(buffer);
816 }
817 }
818 return std::string("#inf");
819}
820
821std::string get_short_state_name(sys::state& state, dcon::state_instance_id state_id) {
822 auto fat_id = dcon::fatten(state.world, state_id);
823 for(auto st : fat_id.get_definition().get_abstract_state_membership()) {
824 if(auto osm = st.get_province().get_state_membership().id; osm && fat_id.id != osm) {
825 if(!state.key_is_localized(fat_id.get_definition().get_name())) {
826 return get_name_as_string(state, fat_id.get_capital());
827 } else {
828 return get_name_as_string(state, fat_id.get_definition());
829 }
830 }
831 }
832 if(!state.key_is_localized(fat_id.get_definition().get_name()))
833 return get_name_as_string(state, fat_id.get_capital());
834 return get_name_as_string(state, fat_id.get_definition());
835}
836
837std::string get_dynamic_state_name(sys::state& state, dcon::state_instance_id state_id) {
838 auto fat_id = dcon::fatten(state.world, state_id);
839 for(auto st : fat_id.get_definition().get_abstract_state_membership()) {
840 if(auto osm = st.get_province().get_state_membership().id; osm && fat_id.id != osm) {
841 auto adj_id = text::get_adjective(state, fat_id.get_nation_from_state_ownership());
842 if(!state.key_is_localized(fat_id.get_definition().get_name())) {
843 if(!state.key_is_localized(adj_id)) {
844 return get_name_as_string(state, fat_id.get_capital());
845 }
849 return resolve_string_substitution(state, "compose_dynamic_adj_state", sub);
850 } else if(!state.key_is_localized(adj_id)) {
851 return get_name_as_string(state, fat_id.get_definition());
852 }
854 add_to_substitution_map(sub, text::variable_type::state, fat_id.get_definition());
856 return resolve_string_substitution(state, "compose_dynamic_adj_state", sub);
857 }
858 }
859 if(!state.key_is_localized(fat_id.get_definition().get_name()))
860 return get_name_as_string(state, fat_id.get_capital());
861 return get_name_as_string(state, fat_id.get_definition());
862}
863
864std::string get_province_state_name(sys::state& state, dcon::province_id prov_id) {
865 auto fat_id = dcon::fatten(state.world, prov_id);
866 auto state_instance_id = fat_id.get_state_membership().id;
867 if(state_instance_id) {
868 return get_dynamic_state_name(state, state_instance_id);
869 } else {
870 auto sdef = fat_id.get_abstract_state_membership_as_province().get_state();
871 if(!state.key_is_localized(sdef.get_name())) {
872 auto lprovs = sdef.get_abstract_state_membership();
873 if(lprovs.begin() != lprovs.end())
874 return get_name_as_string(state, (*lprovs.begin()).get_province());
875 }
876 return get_name_as_string(state, sdef);
877 }
878}
879
880dcon::text_key get_name(sys::state& state, dcon::nation_id id) {
881 auto ident = state.world.nation_get_identity_from_identity_holder(id);
882 auto gov_id = state.world.nation_get_government_type(id);
883 if(gov_id) {
884 auto gname = state.world.national_identity_get_government_name(ident, gov_id);
885 if(state.key_is_localized(gname))
886 return gname;
887 }
888 return state.world.national_identity_get_name(ident);
889}
890dcon::text_key get_adjective(sys::state& state, dcon::nation_id id) {
891 auto ident = state.world.nation_get_identity_from_identity_holder(id);
892 //government specific adjective
893 if(auto k = state.world.national_identity_get_government_adjective(ident, state.world.nation_get_government_type(id)); state.key_is_localized(k)) {
894 return k;
895 }
896 return state.world.national_identity_get_adjective(ident);
897}
898
899dcon::text_key get_ruler_title(sys::state& state, dcon::nation_id n) {
900 auto ident = state.world.nation_get_identity_from_identity_holder(n);
901 auto gov = state.world.nation_get_government_type(n);
902 //government specific adjective
903 if(auto k = state.world.national_identity_get_government_ruler_name(ident, gov); state.key_is_localized(k)) {
904 return k;
905 }
906 return state.world.government_type_get_ruler_name(gov);
907}
908
910 switch(category) {
912 return text::produce_simple_string(state, "rail_focus");
914 return text::produce_simple_string(state, "immigration_focus");
916 return text::produce_simple_string(state, "diplomatic_focus");
918 return text::produce_simple_string(state, "promotion_focus");
920 return text::produce_simple_string(state, "production_focus");
922 return text::produce_simple_string(state, "party_loyalty_focus");
924 return text::produce_simple_string(state, "policy_focus");
926 return text::produce_simple_string(state, "tier_1_focus");
928 return text::produce_simple_string(state, "tier_2_focus");
930 return text::produce_simple_string(state, "tier_3_focus");
932 return text::produce_simple_string(state, "tier_4_focus");
934 return text::produce_simple_string(state, "tier_5_focus");
936 return text::produce_simple_string(state, "tier_6_focus");
938 return text::produce_simple_string(state, "tier_7_focus");
940 return text::produce_simple_string(state, "tier_8_focus");
942 return text::produce_simple_string(state, "building_focus");
944 return text::produce_simple_string(state, "population_focus");
946 return text::produce_simple_string(state, "heavy_industry_focus");
948 return text::produce_simple_string(state, "consumer_goods_focus");
950 return text::produce_simple_string(state, "military_goods_focus");
952 return text::produce_simple_string(state, "immigration_colonization_focus");
953 default:
954 return text::produce_simple_string(state, "category");
955 }
956}
957
961 return text::produce_simple_string(state, "rel_neutral");
963 return text::produce_simple_string(state, "rel_opposed");
965 return text::produce_simple_string(state, "rel_hostile");
967 return text::produce_simple_string(state, "rel_cordial");
969 return text::produce_simple_string(state, "rel_friendly");
971 return text::produce_simple_string(state, "rel_sphere_of_influence");
972 default:
973 return text::produce_simple_string(state, "rel_neutral");
974 }
975}
976
977std::string format_percentage(float num, size_t digits) {
978 return format_float(num * 100.f, digits) + '%';
979}
980
981std::string format_float(float num, size_t digits) {
982 char buffer[200] = {0};
983 switch(digits) {
984 default:
985 // fallthrough
986 case 3:
987 if(num == 0.f) {
988 return std::string("0.000");
989 } else if(num > 0.f && num < 0.001f) {
990 return std::string(">0.000");
991 } else if(num > -0.001f && num < 0.f) {
992 return std::string("<0.000");
993 }
994 snprintf(buffer, sizeof(buffer), "%.3f", num);
995 break;
996 case 2:
997 if(num == 0.f) {
998 return std::string("0.00");
999 } else if(num > 0.f && num < 0.01f) {
1000 return std::string(">0.00");
1001 } else if(num > -0.01f && num < 0.f) {
1002 return std::string("<0.00");
1003 }
1004 snprintf(buffer, sizeof(buffer), "%.2f", num);
1005 break;
1006 case 1:
1007 if(num == 0.f) {
1008 return std::string("0.0");
1009 } else if(num > 0.f && num < 0.1f) {
1010 return std::string(">0.0");
1011 } else if(num > -0.1f && num < 0.f) {
1012 return std::string("<0.0");
1013 }
1014 snprintf(buffer, sizeof(buffer), "%.1f", num);
1015 break;
1016 case 0:
1017 if(num == 0.f) {
1018 return std::string("0");
1019 } else if(num > 0.f && num < 1.f) {
1020 return std::string(">0");
1021 } else if(num > -1.f && num < 0.f) {
1022 return std::string("<0");
1023 }
1024 return std::to_string(int64_t(num));
1025 }
1026 return std::string(buffer);
1027}
1028
1029std::string format_money(float num) {
1030 return prettify_currency(num); // Currency is postfixed, NOT prefixed
1031}
1032
1033std::string format_wholenum(int32_t num) {
1034 std::string result;
1035
1036 if(num == 0) {
1037 result = "0";
1038 return result;
1039 }
1040
1041 auto abs_value = num >= 0 ? num : -num;
1042
1043 int32_t pcount = 0;
1044 while(abs_value > 0) {
1045 if(pcount == 3) {
1046 result.push_back(',');
1047 }
1048 auto last = abs_value % 10;
1049 abs_value /= 10;
1050
1051 result.push_back(char('0' + last));
1052
1053 ++pcount;
1054 }
1055
1056 if(num < 0)
1057 result.push_back('-');
1058
1059 std::reverse(result.begin(), result.end());
1060
1061 return result;
1062}
1063
1064std::string format_ratio(int32_t left, int32_t right) {
1065 return std::to_string(left) + '/' + std::to_string(right);
1066}
1067
1069 mp.insert_or_assign(uint32_t(key), value);
1070}
1071
1072dcon::text_key localize_month(sys::state const& state, uint16_t month) {
1073 static const std::string_view month_names[12] = {"january", "february", "march", "april", "may_month_name", "june", "july", "august",
1074 "september", "october", "november", "december"};
1075
1076 if(month == 0 || month > 12) {
1077 return state.lookup_key("january");
1078 }
1079 return state.lookup_key(month_names[month - 1]);
1080}
1081
1083 sys::year_month_day ymd = date.to_ymd(state.start_date);
1088 return resolve_string_substitution(state, "date_string_ymd", sub);
1089}
1090
1091text_chunk const* layout::get_chunk_from_position(int32_t x, int32_t y) const {
1092 for(auto& chunk : contents) {
1093 if(int32_t(chunk.x) <= x && x <= int32_t(chunk.x) + chunk.width && chunk.y <= y && y <= chunk.y + chunk.height) {
1094 return &chunk;
1095 }
1096 }
1097 return nullptr;
1098}
1099
1101 dest.contents.clear();
1102 dest.number_of_lines = 0;
1103 return endless_layout(dest, params, state.world.locale_get_native_rtl(state.font_collection.get_current_locale()) ? layout_base::rtl_status::rtl : layout_base::rtl_status::ltr);
1104}
1105
1106namespace impl {
1107
1108void lb_finish_line(layout_base& dest, layout_box& box, int32_t line_height) {
1109 bool rtl = dest.native_rtl == layout_base::rtl_status::rtl;
1110 if(dest.fixed_parameters.align == alignment::center) {
1111 if(rtl) {
1112 auto gap = (box.x_position - float(dest.fixed_parameters.left)) / 2.0f;
1113 for(size_t i = box.line_start; i < dest.base_layout.contents.size(); ++i) {
1114 dest.base_layout.contents[i].x -= gap;
1115 }
1116 } else {
1117 auto gap = (float(dest.fixed_parameters.right) - box.x_position) / 2.0f;
1118 for(size_t i = box.line_start; i < dest.base_layout.contents.size(); ++i) {
1119 dest.base_layout.contents[i].x += gap;
1120 }
1121 }
1122 } else if(dest.fixed_parameters.align == alignment::right && !rtl) {
1123 auto gap = float(dest.fixed_parameters.right) - box.x_position;
1124 for(size_t i = box.line_start; i < dest.base_layout.contents.size(); ++i) {
1125 dest.base_layout.contents[i].x += gap;
1126 }
1127 } else if(dest.fixed_parameters.align == alignment::left && rtl) {
1128 auto gap = box.x_position - float(dest.fixed_parameters.left);
1129 for(size_t i = box.line_start; i < dest.base_layout.contents.size(); ++i) {
1130 dest.base_layout.contents[i].x -= gap;
1131 }
1132 }
1133
1134 if(rtl) {
1135 box.x_position = float(dest.fixed_parameters.right - box.x_offset);
1136 } else {
1137 box.x_position = float(box.x_offset + dest.fixed_parameters.left);
1138 }
1139
1140 box.y_position += line_height;
1141 dest.base_layout.number_of_lines += 1;
1142 box.line_start = dest.base_layout.contents.size();
1143}
1144
1145} // namespace impl
1146
1148 auto text_height = int32_t(std::ceil(state.font_collection.line_height(state, dest.fixed_parameters.font_id)));
1149 auto line_height = text_height + dest.fixed_parameters.leading;
1150 impl::lb_finish_line(dest, box, line_height);
1151}
1153 auto text_height = int32_t(std::ceil(state.font_collection.line_height(state, dest.fixed_parameters.font_id)));
1154 auto line_height = text_height + dest.fixed_parameters.leading;
1155 dest.base_layout.number_of_lines += 1;
1156 dest.y_cursor += line_height;
1157}
1159 auto text_height = int32_t(std::ceil(state.font_collection.line_height(state, dest.fixed_parameters.font_id)));
1160 auto line_height = text_height + dest.fixed_parameters.leading;
1161 dest.base_layout.number_of_lines += 1;
1162 dest.y_cursor += line_height;
1163}
1164
1166 auto v_amount = state.font_collection.get_font(state, text::font_index_from_font_id(state, dest.fixed_parameters.font_id)).internal_ascender * text::size_from_font_id(dest.fixed_parameters.font_id) / 64.0f;
1167
1168 if(dest.native_rtl == layout_base::rtl_status::rtl) {
1169 box.x_position -= v_amount * 1.5f;
1170 dest.base_layout.contents.push_back(text_chunk{ text::stored_glyphs{}, box.x_position, ico, int16_t(box.y_position), int16_t(v_amount * 1.5f), int16_t(v_amount), text::text_color::white });
1171 } else {
1172 dest.base_layout.contents.push_back(text_chunk{ text::stored_glyphs{}, box.x_position, ico, int16_t(box.y_position), int16_t(v_amount * 1.5f), int16_t(v_amount), text::text_color::white });
1173 box.x_position += v_amount * 1.5f;
1174 }
1175}
1177 auto v_amount = state.font_collection.get_font(state, text::font_index_from_font_id(state, dest.fixed_parameters.font_id)).internal_ascender * text::size_from_font_id(dest.fixed_parameters.font_id) / 64.0f;
1178
1179 if(dest.native_rtl == layout_base::rtl_status::rtl) {
1180 box.x_position -= v_amount;
1181 dest.base_layout.contents.push_back(text_chunk{ text::stored_glyphs{}, box.x_position, ico, int16_t(box.y_position), int16_t(v_amount), int16_t(v_amount), text::text_color::white });
1182 } else {
1183 dest.base_layout.contents.push_back(text_chunk{ text::stored_glyphs{}, box.x_position, ico, int16_t(box.y_position), int16_t(v_amount), int16_t(v_amount), text::text_color::white });
1184 box.x_position += v_amount;
1185 }
1186}
1187
1189 auto v_amount = state.font_collection.get_font(state, text::font_index_from_font_id(state, dest.fixed_parameters.font_id)).internal_ascender * text::size_from_font_id(dest.fixed_parameters.font_id) / 64.0f;
1190
1191 if(dest.native_rtl == layout_base::rtl_status::rtl) {
1192 box.x_position -= v_amount;
1193 dest.base_layout.contents.push_back(text_chunk{ text::stored_glyphs{}, box.x_position, ico, int16_t(box.y_position), int16_t(v_amount), int16_t(v_amount), text::text_color::white });
1194 } else {
1195 dest.base_layout.contents.push_back(text_chunk{ text::stored_glyphs{}, box.x_position, ico, int16_t(box.y_position), int16_t(v_amount), int16_t(v_amount), text::text_color::white });
1196 box.x_position += v_amount;
1197 }
1198}
1199
1200
1202 if(in == alignment::left && state.world.locale_get_native_rtl(state.font_collection.get_current_locale())) {
1203 return alignment::right;
1204 } else if(in == alignment::right && state.world.locale_get_native_rtl(state.font_collection.get_current_locale())) {
1205 return alignment::left;
1206 }
1207 return in;
1208}
1210 if(in == ui::alignment::left && state.world.locale_get_native_rtl(state.font_collection.get_current_locale())) {
1211 return ui::alignment::right;
1212 } else if(in == ui::alignment::right && state.world.locale_get_native_rtl(state.font_collection.get_current_locale())) {
1213 return ui::alignment::left;
1214 }
1215 return in;
1216}
1218 switch(in) {
1220 return text::alignment::left;
1226 return text::alignment::left;
1227 default:
1228 return text::alignment::left;
1229 }
1230
1231}
1232
1234 if(text.size() == 0)
1235 return;
1236
1237 auto& font = state.font_collection.get_font(state, text::font_index_from_font_id(state, dest.fixed_parameters.font_id));
1238 auto text_height = int32_t(std::ceil(state.font_collection.line_height(state, dest.fixed_parameters.font_id)));
1239 auto line_height = text_height + dest.fixed_parameters.leading;
1240 auto tmp_color = color;
1241 if(std::holds_alternative<dcon::nation_id>(source)
1242 || std::holds_alternative<dcon::province_id>(source)
1243 || std::holds_alternative<dcon::state_instance_id>(source)
1244 || std::holds_alternative<dcon::state_definition_id>(source)) {
1245 if(!dest.fixed_parameters.suppress_hyperlinks) {
1246 if(color != text_color::black) {
1247 tmp_color = text_color::light_blue;
1248 } else {
1249 tmp_color = text_color::dark_blue;
1250 }
1251 } else {
1252 if(color != text_color::black) {
1253 tmp_color = text_color::yellow;
1254 } else {
1255 tmp_color = text_color::orange;
1256 }
1257 }
1258 }
1259
1260 bool first_in_line = true;
1261 auto font_size = text::size_from_font_id(dest.fixed_parameters.font_id);
1262
1263 std::vector<uint16_t> temp_text;
1264 {
1265 auto start = text.data();
1266 auto end = start + text.length();
1267 int32_t base_index = 0;
1268
1269 while(start + base_index < end) {
1270 auto c = codepoint_from_utf8(start + base_index, end);
1271
1272 if(!requires_surrogate_pair(c)) {
1273 temp_text.push_back(char16_t(c));
1274 } else {
1275 auto p = make_surrogate_pair(c);
1276 temp_text.push_back(char16_t(p.high));
1277 temp_text.push_back(char16_t(p.low));
1278 }
1279
1280 base_index += int32_t(size_from_utf8(start + base_index, end));
1281 }
1282 }
1283
1284 UErrorCode errorCode = U_ZERO_ERROR;
1285 UBreakIterator* lb_it = ubrk_openBinaryRules(state.font_collection.compiled_ubrk_rules.data(), int32_t(state.font_collection.compiled_ubrk_rules.size()), (UChar const*)temp_text.data(), int32_t(temp_text.size()), &errorCode);
1286
1287 if(!lb_it || !U_SUCCESS(errorCode)) {
1288 std::abort(); // couldn't create iterator
1289 }
1290
1291 ubrk_first(lb_it);
1292
1293 text::stored_glyphs all_glyphs(state, text::font_index_from_font_id(state, dest.fixed_parameters.font_id), std::span<uint16_t>(temp_text.data(), temp_text.size()), text::stored_glyphs::no_bidi{});
1294
1295 if(dest.native_rtl == layout_base::rtl_status::rtl) {
1296 int32_t glyph_position = 0;
1297 int32_t glyph_start_position = 0;
1298 int32_t cluster_position = 0;
1299 int32_t cluster_start_position = 0;
1300
1301 while(cluster_start_position < int32_t(temp_text.size())) {
1302 if(dest.fixed_parameters.single_line && box.x_position <= dest.fixed_parameters.left)
1303 break;
1304
1305 auto next_cluster_position = ubrk_next(lb_it);
1306 int32_t next_glyph_position = 0;
1307
1308 if(next_cluster_position == UBRK_DONE) {
1309 next_glyph_position = int32_t(all_glyphs.glyph_info.size());
1310 next_cluster_position = int32_t(temp_text.size());
1311 } else {
1312 for(next_glyph_position = glyph_position; next_glyph_position < int32_t(all_glyphs.glyph_info.size()); ++next_glyph_position) {
1313 if(all_glyphs.glyph_info[next_glyph_position].cluster >= uint32_t(next_cluster_position)) {
1314 break;
1315 }
1316 }
1317 }
1318
1319 float extent = state.font_collection.text_extent(state, all_glyphs, glyph_start_position, next_glyph_position - glyph_start_position, dest.fixed_parameters.font_id);
1320
1321 if(!dest.fixed_parameters.single_line && first_in_line && int32_t(dest.fixed_parameters.right - box.x_offset) == box.x_position && box.x_position - extent <= dest.fixed_parameters.left) {
1322 // too long, but no line breaking opportunities earlier in the line
1323
1324 box.x_position -= extent;
1325 box.y_size = std::max(box.y_size, box.y_position + line_height);
1326 box.x_size = std::max(box.x_size, int32_t(dest.fixed_parameters.right - box.x_position));
1327
1328 dest.base_layout.contents.push_back(text_chunk{
1329 text::stored_glyphs(state, text::font_index_from_font_id(state, dest.fixed_parameters.font_id), std::span<uint16_t>(temp_text.data() + cluster_start_position, next_cluster_position - cluster_start_position)),
1330 box.x_position, (!dest.fixed_parameters.suppress_hyperlinks) ? source : std::monostate{}, int16_t(box.y_position), int16_t(extent), int16_t(text_height), tmp_color });
1331
1332 if(box.x_position - extent <= dest.fixed_parameters.left)
1333 impl::lb_finish_line(dest, box, line_height);
1334
1335 glyph_start_position = next_glyph_position;
1336 glyph_position = next_glyph_position;
1337 cluster_position = next_cluster_position;
1338 cluster_start_position = next_cluster_position;
1339 } else if(!dest.fixed_parameters.single_line && box.x_position - extent <= dest.fixed_parameters.left) {
1340 extent = state.font_collection.text_extent(state, all_glyphs, glyph_start_position, glyph_position - glyph_start_position, dest.fixed_parameters.font_id);
1341
1342 box.x_position -= extent;
1343 box.y_size = std::max(box.y_size, box.y_position + line_height);
1344 box.x_size = std::max(box.x_size, int32_t(dest.fixed_parameters.right - box.x_position));
1345 dest.base_layout.contents.push_back(
1346 text_chunk{ text::stored_glyphs(state, text::font_index_from_font_id(state, dest.fixed_parameters.font_id), std::span<uint16_t>(temp_text.data() + cluster_start_position, cluster_position - cluster_start_position)),
1347 box.x_position, (!dest.fixed_parameters.suppress_hyperlinks) ? source : std::monostate{}, int16_t(box.y_position), int16_t(extent), int16_t(text_height), tmp_color });
1348
1349 impl::lb_finish_line(dest, box, line_height);
1350
1351 glyph_start_position = glyph_position;
1352 cluster_start_position = cluster_position;
1353
1354 ubrk_previous(lb_it);
1355 first_in_line = true;
1356 } else if(dest.fixed_parameters.single_line && box.x_position - extent <= dest.fixed_parameters.left) {
1357 auto glyphid = FT_Get_Char_Index(font.font_face, 0x2026);
1358
1359 bool ellipsis_valid = true;
1360 auto width_of_ellipsis = font.base_glyph_width(glyphid) * font_size / 64.f;
1361
1362 if(width_of_ellipsis <= 0 || glyphid == 0) {
1363 ellipsis_valid = false;
1364 width_of_ellipsis = font.base_glyph_width(FT_Get_Char_Index(font.font_face, '.')) * 3.0f * font_size / 64.f;
1365 }
1366 if(state.user_settings.use_classic_fonts) {
1367 ellipsis_valid = false;
1368 width_of_ellipsis = 3.0f * font_size / 6.f;
1369 }
1370
1371 int32_t m = glyph_start_position;
1372 while(m < next_glyph_position) {
1373 if(box.x_position - state.font_collection.text_extent(state, all_glyphs, glyph_start_position, uint32_t(m), dest.fixed_parameters.font_id) - width_of_ellipsis < dest.fixed_parameters.left)
1374 break;
1375 ++m;
1376 }
1377 if(m >= next_glyph_position) m = next_glyph_position - 1;
1378
1379 auto cluster_end = all_glyphs.glyph_info[m].cluster;
1380 std::vector<uint16_t> tempv{ temp_text.data() + cluster_start_position, temp_text.data() + cluster_end };
1381
1382 if(ellipsis_valid) {
1383 tempv.push_back(0x2026);
1384 } else {
1385 tempv.push_back('.'); tempv.push_back('.'); tempv.push_back('.');
1386 }
1387
1388 dest.base_layout.contents.push_back(
1389 text_chunk{ text::stored_glyphs(state, text::font_index_from_font_id(state, dest.fixed_parameters.font_id), std::span<uint16_t>(tempv.data(), tempv.size())),
1390 box.x_position, (!dest.fixed_parameters.suppress_hyperlinks) ? source : std::monostate{}, int16_t(box.y_position), int16_t(extent), int16_t(text_height), tmp_color });
1391 box.x_position = dest.fixed_parameters.left;
1392 box.y_size = std::max(box.y_size, box.y_position + line_height);
1393 box.x_size = std::max(box.x_size, int32_t(dest.fixed_parameters.right - box.x_position));
1394 } else if(next_cluster_position >= int32_t(temp_text.size())) {
1395 // no remaining text
1396
1397 box.x_position -= extent;
1398 box.y_size = std::max(box.y_size, box.y_position + line_height);
1399 box.x_size = std::max(box.x_size, int32_t(dest.fixed_parameters.right - box.x_position));
1400
1401 dest.base_layout.contents.push_back(text_chunk{
1402 text::stored_glyphs(state, text::font_index_from_font_id(state, dest.fixed_parameters.font_id), std::span<uint16_t>(temp_text.data() + cluster_start_position, next_cluster_position - cluster_start_position)),
1403 box.x_position, (!dest.fixed_parameters.suppress_hyperlinks) ? source : std::monostate{}, int16_t(box.y_position), int16_t(extent), int16_t(text_height), tmp_color });
1404
1405 if(!dest.fixed_parameters.single_line && box.x_position <= dest.fixed_parameters.left)
1406 impl::lb_finish_line(dest, box, line_height);
1407
1408 glyph_start_position = next_glyph_position;
1409 glyph_position = next_glyph_position;
1410 cluster_position = next_cluster_position;
1411 cluster_start_position = next_cluster_position;
1412 } else {
1413 glyph_position = next_glyph_position;
1414 cluster_position = next_cluster_position;
1415 first_in_line = false;
1416 }
1417 }
1418
1419 } else {
1420 int32_t glyph_position = 0;
1421 int32_t glyph_start_position = 0;
1422 int32_t cluster_position = 0;
1423 int32_t cluster_start_position = 0;
1424
1425 while(cluster_start_position < int32_t(temp_text.size())) {
1426 if(dest.fixed_parameters.single_line && box.x_position >= dest.fixed_parameters.right)
1427 break;
1428
1429 auto next_cluster_position = ubrk_next(lb_it);
1430 int32_t next_glyph_position = 0;
1431
1432 if(next_cluster_position == UBRK_DONE) {
1433 next_glyph_position = int32_t(all_glyphs.glyph_info.size());
1434 next_cluster_position = int32_t(temp_text.size());
1435 } else {
1436 for(next_glyph_position = glyph_position; next_glyph_position < int32_t(all_glyphs.glyph_info.size()); ++next_glyph_position) {
1437 if(all_glyphs.glyph_info[next_glyph_position].cluster >= uint32_t(next_cluster_position)) {
1438 break;
1439 }
1440 }
1441 }
1442
1443 float extent = state.font_collection.text_extent(state, all_glyphs, glyph_start_position, next_glyph_position - glyph_start_position, dest.fixed_parameters.font_id);
1444
1445 if(!dest.fixed_parameters.single_line && first_in_line && int32_t(box.x_offset + dest.fixed_parameters.left) == box.x_position && box.x_position + extent >= dest.fixed_parameters.right) {
1446 // too long, but no line breaking opportunities earlier in the line
1447
1448 dest.base_layout.contents.push_back(text_chunk{ text::stored_glyphs(state, text::font_index_from_font_id(state, dest.fixed_parameters.font_id), std::span<uint16_t>(temp_text.data() + cluster_start_position, next_cluster_position - cluster_start_position)), box.x_position, (!dest.fixed_parameters.suppress_hyperlinks) ? source : std::monostate{}, int16_t(box.y_position), int16_t(extent), int16_t(text_height), tmp_color });
1449
1450 box.x_position += extent;
1451 box.y_size = std::max(box.y_size, box.y_position + line_height);
1452 box.x_size = std::max(box.x_size, int32_t(box.x_position));
1453
1454 if(box.x_position + extent >= dest.fixed_parameters.right)
1455 impl::lb_finish_line(dest, box, line_height);
1456
1457 glyph_start_position = next_glyph_position;
1458 glyph_position = next_glyph_position;
1459 cluster_position = next_cluster_position;
1460 cluster_start_position = next_cluster_position;
1461 } else if(!dest.fixed_parameters.single_line && box.x_position + extent >= dest.fixed_parameters.right) {
1462
1463 extent = state.font_collection.text_extent(state, all_glyphs, glyph_start_position, glyph_position - glyph_start_position, dest.fixed_parameters.font_id);
1464 dest.base_layout.contents.push_back(
1465 text_chunk{ text::stored_glyphs(state, text::font_index_from_font_id(state, dest.fixed_parameters.font_id), std::span<uint16_t>(temp_text.data() + cluster_start_position, cluster_position - cluster_start_position)),
1466 box.x_position, (!dest.fixed_parameters.suppress_hyperlinks) ? source : std::monostate{}, int16_t(box.y_position), int16_t(extent), int16_t(text_height), tmp_color });
1467 box.x_position += extent;
1468 box.y_size = std::max(box.y_size, box.y_position + line_height);
1469 box.x_size = std::max(box.x_size, int32_t(box.x_position));
1470
1471 impl::lb_finish_line(dest, box, line_height);
1472
1473 glyph_start_position = glyph_position;
1474 cluster_start_position = cluster_position;
1475
1476 ubrk_previous(lb_it);
1477 first_in_line = true;
1478 } else if(dest.fixed_parameters.single_line && box.x_position + extent >= dest.fixed_parameters.right) {
1479 auto glyphid = FT_Get_Char_Index(font.font_face, 0x2026);
1480
1481 bool ellipsis_valid = true;
1482 auto width_of_ellipsis = font.base_glyph_width(glyphid) * font_size / 64.f;
1483
1484 if(width_of_ellipsis <= 0 || glyphid == 0) {
1485 ellipsis_valid = false;
1486 width_of_ellipsis = font.base_glyph_width(FT_Get_Char_Index(font.font_face, '.')) * 3.0f * font_size / 64.f;
1487 }
1488 if(state.user_settings.use_classic_fonts) {
1489 ellipsis_valid = false;
1490 width_of_ellipsis = 3.0f * font_size / 6.f;
1491 }
1492
1493 int32_t m = glyph_start_position;
1494 while(m < next_glyph_position) {
1495 if(box.x_position + state.font_collection.text_extent(state, all_glyphs, glyph_start_position, uint32_t(m), dest.fixed_parameters.font_id) + width_of_ellipsis > dest.fixed_parameters.right)
1496 break;
1497 ++m;
1498 }
1499 if(m >= next_glyph_position) m = next_glyph_position - 1;
1500
1501 auto cluster_end = all_glyphs.glyph_info[m].cluster;
1502 std::vector<uint16_t> tempv{temp_text.data() + cluster_start_position, temp_text.data() + cluster_end };
1503
1504 if(ellipsis_valid) {
1505 tempv.push_back(0x2026);
1506 } else {
1507 tempv.push_back('.'); tempv.push_back('.'); tempv.push_back('.');
1508 }
1509
1510 dest.base_layout.contents.push_back(
1511 text_chunk{ text::stored_glyphs(state, text::font_index_from_font_id(state, dest.fixed_parameters.font_id), std::span<uint16_t>(tempv.data(), tempv.size())),
1512 box.x_position, (!dest.fixed_parameters.suppress_hyperlinks) ? source : std::monostate{}, int16_t(box.y_position), int16_t(extent), int16_t(text_height), tmp_color });
1513 box.x_position = dest.fixed_parameters.right;
1514 box.y_size = std::max(box.y_size, box.y_position + line_height);
1515 box.x_size = std::max(box.x_size, int32_t(box.x_position));
1516 } else if(next_cluster_position >= int32_t(temp_text.size())) {
1517 // no remaining text
1518
1519 dest.base_layout.contents.push_back(text_chunk{ text::stored_glyphs(state, text::font_index_from_font_id(state, dest.fixed_parameters.font_id), std::span<uint16_t>(temp_text.data() + cluster_start_position, next_cluster_position - cluster_start_position)), box.x_position, (!dest.fixed_parameters.suppress_hyperlinks) ? source : std::monostate{}, int16_t(box.y_position), int16_t(extent), int16_t(text_height), tmp_color });
1520
1521 box.x_position += extent;
1522 box.y_size = std::max(box.y_size, box.y_position + line_height);
1523 box.x_size = std::max(box.x_size, int32_t(box.x_position));
1524
1525 if(!dest.fixed_parameters.single_line && box.x_position >= dest.fixed_parameters.right)
1526 impl::lb_finish_line(dest, box, line_height);
1527
1528 glyph_start_position = next_glyph_position;
1529 glyph_position = next_glyph_position;
1530 cluster_position = next_cluster_position;
1531 cluster_start_position = next_cluster_position;
1532 } else {
1533 glyph_position = next_glyph_position;
1534 cluster_position = next_cluster_position;
1535 first_in_line = false;
1536 }
1537 }
1538 }
1539
1540 ubrk_close(lb_it);
1541}
1542
1543namespace impl {
1544
1546 if(std::holds_alternative<std::string_view>(sub)) {
1547 return std::string(std::get<std::string_view>(sub));
1548 } else if(std::holds_alternative<dcon::text_key>(sub)) {
1549 auto tkey = std::get<dcon::text_key>(sub);
1550 if(auto it = state.locale_key_to_text_sequence.find(tkey); it != state.locale_key_to_text_sequence.end()) {
1551 return std::string(state.locale_string_view(it->second));
1552 } else {
1553 return std::string(state.to_string_view(tkey));
1554 }
1555 } else if(std::holds_alternative<dcon::nation_id>(sub)) {
1556 dcon::nation_id nid = std::get<dcon::nation_id>(sub);
1558 } else if(std::holds_alternative<dcon::state_instance_id>(sub)) {
1559 dcon::state_instance_id sid = std::get<dcon::state_instance_id>(sub);
1560 return get_dynamic_state_name(state, sid);
1561 } else if(std::holds_alternative<dcon::province_id>(sub)) {
1562 auto pid = std::get<dcon::province_id>(sub);
1563 return text::produce_simple_string(state, state.world.province_get_name(pid));
1564 } else if(std::holds_alternative<dcon::national_identity_id>(sub)) {
1565 auto t = std::get<dcon::national_identity_id>(sub);
1566 auto h = state.world.national_identity_get_nation_from_identity_holder(t);
1567 return text::produce_simple_string(state, h ? text::get_name(state, h) : state.world.national_identity_get_name(t));
1568 } else if(std::holds_alternative<int64_t>(sub)) {
1569 return std::to_string(std::get<int64_t>(sub));
1570 } else if(std::holds_alternative<fp_one_place>(sub)) {
1571 return text::format_float(std::get<fp_one_place>(sub).value, 1);
1572 } else if(std::holds_alternative<fp_two_places>(sub)) {
1573 return text::format_float(std::get<fp_two_places>(sub).value, 2);
1574 } else if(std::holds_alternative<fp_three_places>(sub)) {
1575 return text::format_float(std::get<fp_three_places>(sub).value, 3);
1576 } else if(std::holds_alternative<fp_four_places>(sub)) {
1577 return text::format_float(std::get<fp_four_places>(sub).value, 4);
1578 } else if(std::holds_alternative<sys::date>(sub)) {
1579 return date_to_string(state, std::get<sys::date>(sub));
1580 } else if(std::holds_alternative<fp_currency>(sub)) {
1581 return text::format_money(std::get<fp_currency>(sub).value);
1582 } else if(std::holds_alternative<pretty_integer>(sub)) {
1583 return prettify(std::get<pretty_integer>(sub).value);
1584 } else if(std::holds_alternative<fp_percentage>(sub)) {
1585 return text::format_percentage(std::get<fp_percentage>(sub).value, 0);
1586 } else if(std::holds_alternative<fp_percentage_one_place>(sub)) {
1587 return text::format_percentage(std::get<fp_percentage_one_place>(sub).value, 1);
1588 } else if(std::holds_alternative<int_percentage>(sub)) {
1589 return std::to_string(std::get<int_percentage>(sub).value) + "%";
1590 } else if(std::holds_alternative<int_wholenum>(sub)) {
1591 return text::format_wholenum(std::get<int_wholenum>(sub).value);
1592 } else if(std::holds_alternative<dcon::state_definition_id>(sub)) {
1593 return produce_simple_string(state, state.world.state_definition_get_name(std::get<dcon::state_definition_id>(sub)));
1594 } else {
1595 return std::string("?");
1596 }
1597}
1598
1599} // namespace impl
1600
1602 if(sv.length() == 0)
1603 return;
1604
1605 auto current_color = dest.fixed_parameters.color;
1606
1607 auto text_height = int32_t(std::ceil(state.font_collection.line_height(state, dest.fixed_parameters.font_id)));
1608 auto line_height = text_height + dest.fixed_parameters.leading;
1609
1610 char const* seq_start = sv.data();
1611 char const* seq_end = sv.data() + sv.size();
1612 char const* section_start = seq_start;
1613
1614 auto add_text_range = [&](std::string_view sv) {
1615 if(sv.length() > 0) {
1616 add_to_layout_box(state, dest, box, sv, current_color, std::monostate{});
1617 }
1618 };
1619
1620 for(char const* pos = seq_start; pos < seq_end;) {
1621 bool colour_esc = false;
1622 if(pos + 1 < seq_end && uint8_t(*pos) == 0xC2 && uint8_t(*(pos + 1)) == 0xA7) {
1623 if(section_start < pos)
1624 add_text_range(std::string_view(section_start, pos - section_start));
1625
1626 pos += 2;
1627 section_start = pos;
1628 if(pos < seq_end) {
1629 auto newcolor = char_to_color(*pos);
1630 if(newcolor == text_color::reset)
1631 current_color = dest.fixed_parameters.color;
1632 else
1633 current_color = newcolor;
1634
1635 pos += 1;
1636 section_start = pos;
1637 }
1638 } else if(pos + 4 < seq_end && uint8_t(*pos) == 0xEF && uint8_t(*(pos + 1)) == 0xBF && uint8_t(*(pos + 2)) == 0xBD && is_qmark_color(*(pos + 3))) {
1639 if(section_start < pos)
1640 add_text_range(std::string_view(section_start, pos - section_start));
1641
1642 section_start = pos += 3;
1643
1644 if(pos < seq_end) {
1645 auto newcolor = char_to_color(*pos);
1646 if(newcolor == text_color::reset)
1647 current_color = dest.fixed_parameters.color;
1648 else
1649 current_color = newcolor;
1650
1651 pos += 1;
1652 section_start = pos;
1653 }
1654 } else if(*pos == '@' && pos + 3 < seq_end) {
1655 if(*(pos + 1) == '(' && *(pos + 3) == ')') {
1656 if(*(pos + 2) == 'A') {
1657 if(section_start < pos)
1658 add_text_range(std::string_view(section_start, pos - section_start));
1660 pos += 4;
1661 section_start = pos;
1662 } else if(*(pos + 2) == 'N') {
1663 if(section_start < pos)
1664 add_text_range(std::string_view(section_start, pos - section_start));
1666 pos += 4;
1667 section_start = pos;
1668 } else if(*(pos + 2) == 'T') {
1669 if(section_start < pos)
1670 add_text_range(std::string_view(section_start, pos - section_start));
1672 pos += 4;
1673 section_start = pos;
1674 } else if(*(pos + 2) == 'F') {
1675 if(section_start < pos)
1676 add_text_range(std::string_view(section_start, pos - section_start));
1678 pos += 4;
1679 section_start = pos;
1680 } else {
1681 ++pos;
1682 }
1683 } else if(*(pos + 1) == '*') {
1684 uint8_t tens = char(*(pos + 2)) - '0';
1685 uint8_t ones = char(*(pos + 3)) - '0';
1686 uint8_t unit_index = tens * 10 + ones;
1687 pos += 4;
1688 dcon::unit_type_id given_unit_type_id { unit_index };
1689 auto icon = state.military_definitions.unit_base_definitions[given_unit_type_id].icon;
1690 add_to_layout_box(state, dest, box, embedded_unit_icon{ given_unit_type_id });
1691 section_start = pos;
1692 } else {
1693 auto tag_int = nations::tag_to_int(char(toupper(*(pos + 1))), char(toupper(*(pos + 2))), char(toupper(*(pos + 3))));
1694 bool matched = false;
1695 for(auto nid : state.world.in_national_identity) {
1696 if(nid.get_identifying_int() == tag_int) {
1697 matched = true;
1698
1699 if(section_start < pos)
1700 add_text_range(std::string_view(section_start, pos - section_start));
1701
1702 add_to_layout_box(state, dest, box, embedded_flag{ nid.id });
1703 pos += 4;
1704
1705 section_start = pos;
1706 break;
1707 }
1708 }
1709 if(!matched)
1710 ++pos;
1711 }
1712 } else if(pos + 1 < seq_end && *pos == '?' && is_qmark_color(*(pos + 1))) {
1713 if(section_start < pos)
1714 add_text_range(std::string_view(section_start, pos - section_start));
1715
1716 pos += 1;
1717 section_start = pos;
1718 // This colour escape sequence must be followed by something, otherwise
1719 // we should probably discard the last colour command
1720 if(pos < seq_end) {
1721
1722 auto newcolor = char_to_color(*pos);
1723 if(newcolor == text_color::reset)
1724 current_color = dest.fixed_parameters.color;
1725 else
1726 current_color = newcolor;
1727
1728 pos += 1;
1729 section_start = pos;
1730 }
1731 } else if(*pos == '$') {
1732 if(section_start < pos)
1733 add_text_range(std::string_view(section_start, pos - section_start));
1734
1735 const char* vend = pos + 1;
1736 for(; vend != seq_end && *vend != '$'; ++vend)
1737 ;
1738 if(vend > pos + 1) {
1739 auto var_type = variable_type_from_name(std::string_view(pos + 1, vend - pos - 1));
1741 if(auto it = mp.find(uint32_t(var_type)); it != mp.end()) {
1742 auto txt = impl::lb_resolve_substitution(state, it->second, mp);
1743 add_to_layout_box(state, dest, box, std::string_view(txt), current_color, it->second);
1744 } else {
1745 add_to_layout_box(state, dest, box, std::string_view("???"), current_color, std::monostate{});
1746 }
1747 } else {
1748 add_to_layout_box(state, dest, box, std::string_view(pos, (vend - pos) + 1), current_color, std::monostate{});
1749 }
1750 }
1751 pos = vend + 1;
1752 section_start = pos;
1753 } else if(pos + 1 < seq_end && *pos == '\\' && *(pos + 1) == 'n') {
1754 if(section_start < pos)
1755 add_text_range(std::string_view(section_start, pos - section_start));
1756
1758 section_start = pos += 2;
1759 } else {
1760 ++pos;
1761 }
1762 }
1763
1764 if(section_start < seq_end)
1765 add_text_range(std::string_view(section_start, seq_end - section_start));
1766}
1767
1768void add_to_layout_box(sys::state& state, layout_base& dest, layout_box& box, dcon::text_key source_text, substitution_map const& mp) {
1769 if(!source_text)
1770 return;
1771
1772 std::string_view sv;
1773 if(auto it = state.locale_key_to_text_sequence.find(source_text); it != state.locale_key_to_text_sequence.end()) {
1774 sv = state.locale_string_view(it->second);
1775 } else {
1776 sv = state.to_string_view(source_text);
1777 }
1779}
1780
1783 add_to_layout_box(state, dest, box, std::string_view(txt), color, val);
1784}
1785void add_to_layout_box(sys::state& state, layout_base& dest, layout_box& box, std::string const& val, text_color color) {
1786 add_to_layout_box(state, dest, box, std::string_view(val), color, std::monostate{});
1787}
1789 auto& font = state.font_collection.get_font(state, text::font_index_from_font_id(state, dest.fixed_parameters.font_id));
1790 auto glyphid = FT_Get_Char_Index(font.font_face, ' ');
1791 float amount = font.base_glyph_width(glyphid) * text::size_from_font_id(dest.fixed_parameters.font_id) / 64.f;;
1792
1793 if(dest.native_rtl == layout_base::rtl_status::rtl)
1794 box.x_position -= amount;
1795 else
1796 box.x_position += amount;
1797}
1798
1800 if(dest.native_rtl == layout_base::rtl_status::ltr)
1801 return layout_box{dest.base_layout.contents.size(), dest.base_layout.contents.size(), indent, 0, 0,
1802 float(indent + dest.fixed_parameters.left), 0, dest.fixed_parameters.color};
1803 else
1804 return layout_box{ dest.base_layout.contents.size(), dest.base_layout.contents.size(), indent, 0, 0,
1805 float(dest.fixed_parameters.right - indent), 0, dest.fixed_parameters.color };
1806}
1808 impl::lb_finish_line(dest, box, 0);
1809 if(dest.native_rtl == layout_base::rtl_status::ltr) {
1810 if(box.y_size + dest.y_cursor >= dest.fixed_parameters.bottom) { // make new column
1811 dest.current_column_x = dest.used_width + dest.column_width;
1812 for(auto i = box.first_chunk; i < dest.base_layout.contents.size(); ++i) {
1813 dest.base_layout.contents[i].y += dest.fixed_parameters.top;
1814 dest.base_layout.contents[i].x += float(dest.current_column_x);
1815 dest.used_width = std::max(dest.used_width, int32_t(dest.base_layout.contents[i].x + dest.base_layout.contents[i].width));
1816 }
1817 dest.y_cursor = box.y_size + dest.fixed_parameters.top;
1818 } else { // append to current column
1819 for(auto i = box.first_chunk; i < dest.base_layout.contents.size(); ++i) {
1820 dest.base_layout.contents[i].y += int16_t(dest.y_cursor);
1821 dest.base_layout.contents[i].x += float(dest.current_column_x);
1822 dest.used_width = std::max(dest.used_width, int32_t(dest.base_layout.contents[i].x + dest.base_layout.contents[i].width));
1823 }
1824 dest.y_cursor += box.y_size;
1825 }
1826 dest.used_height = std::max(dest.used_height, dest.y_cursor);
1827 } else {
1828 if(box.y_size + dest.y_cursor >= dest.fixed_parameters.bottom) { // make new column
1829 dest.current_column_x = dest.used_width - dest.column_width;
1830 for(auto i = box.first_chunk; i < dest.base_layout.contents.size(); ++i) {
1831 dest.base_layout.contents[i].y += dest.fixed_parameters.top;
1832 dest.base_layout.contents[i].x += float(dest.current_column_x) - float(dest.fixed_parameters.right - dest.fixed_parameters.left);
1833 dest.used_width = std::min(dest.used_width, int32_t(dest.base_layout.contents[i].x));
1834 }
1835 dest.y_cursor = box.y_size + dest.fixed_parameters.top;
1836 } else { // append to current column
1837 for(auto i = box.first_chunk; i < dest.base_layout.contents.size(); ++i) {
1838 dest.base_layout.contents[i].y += int16_t(dest.y_cursor);
1839 dest.base_layout.contents[i].x += float(dest.current_column_x) - float(dest.fixed_parameters.right - dest.fixed_parameters.left);
1840 dest.used_width = std::min(dest.used_width, int32_t(dest.base_layout.contents[i].x));
1841 }
1842 dest.y_cursor += box.y_size;
1843 }
1844 dest.used_height = std::max(dest.used_height, dest.y_cursor);
1845 }
1846}
1848 impl::lb_finish_line(dest, box, 0);
1849 for(auto i = box.first_chunk; i < dest.base_layout.contents.size(); ++i) {
1850 dest.base_layout.contents[i].y += int16_t(dest.y_cursor);
1851 }
1852
1853 dest.y_cursor += box.y_size;
1854}
1856 impl::lb_finish_line(dest, box, 0);
1857}
1858
1860 dest.internal_close_box(box);
1861}
1862
1864 close_layout_box(*this, box);
1865}
1867 close_layout_box(*this, box);
1868}
1870 close_layout_box(*this, b);
1871}
1872
1874 dest.contents.clear();
1875 dest.number_of_lines = 0;
1876 return columnar_layout(dest, params, state.world.locale_get_native_rtl(state.font_collection.get_current_locale()) ? layout_base::rtl_status::rtl : layout_base::rtl_status::ltr, 0, 0, params.top, column_width);
1877}
1878
1879// Reduces code repeat
1881 if(auto k = state.lookup_key(key); k) {
1882 add_to_layout_box(state, dest, box, k, sub);
1883 } else {
1884 add_to_layout_box(state, dest, box, key);
1885 }
1886}
1887
1892 if(auto k = state.lookup_key(key); k) {
1893 add_to_layout_box(state, dest, box, k, sub);
1894 } else {
1895 add_to_layout_box(state, dest, box, key);
1896 }
1897}
1898
1899void add_line(sys::state& state, layout_base& dest, dcon::text_key txt, int32_t indent) {
1900 auto box = text::open_layout_box(dest, indent);
1901 add_to_layout_box(state, dest, box, txt);
1903}
1904void add_line(sys::state& state, layout_base& dest, dcon::text_key txt, variable_type subkey, substitution value, int32_t indent) {
1905 auto box = text::open_layout_box(dest, indent);
1908 add_to_layout_box(state, dest, box, txt, sub);
1910}
1912 variable_type subkey_b, substitution value_b, int32_t indent) {
1913
1914 auto box = text::open_layout_box(dest, indent);
1917 text::add_to_substitution_map(sub, subkey_b, value_b);
1918 add_to_layout_box(state, dest, box, txt, sub);
1920}
1922 variable_type subkey_b, substitution value_b, variable_type subkey_c, substitution value_c, int32_t indent) {
1923
1924 auto box = text::open_layout_box(dest, indent);
1927 text::add_to_substitution_map(sub, subkey_b, value_b);
1928 text::add_to_substitution_map(sub, subkey_c, value_c);
1929 add_to_layout_box(state, dest, box, txt, sub);
1931}
1933 variable_type subkey_b, substitution value_b, variable_type subkey_c, substitution value_c, variable_type subkey_d,
1934 substitution value_d, int32_t indent) {
1935
1936 auto box = text::open_layout_box(dest, indent);
1939 text::add_to_substitution_map(sub, subkey_b, value_b);
1940 text::add_to_substitution_map(sub, subkey_c, value_c);
1941 text::add_to_substitution_map(sub, subkey_d, value_d);
1942 add_to_layout_box(state, dest, box, txt, sub);
1944}
1945
1946void add_line(sys::state& state, layout_base& dest, std::string_view key, int32_t indent) {
1947 auto box = text::open_layout_box(dest, indent);
1948 if(auto k = state.lookup_key(key); k) {
1949 add_to_layout_box(state, dest, box, k);
1950 } else {
1951 add_to_layout_box(state, dest, box, key);
1952 }
1954}
1955void add_line_with_condition(sys::state& state, layout_base& dest, std::string_view key, bool condition_met, int32_t indent) {
1956 auto box = text::open_layout_box(dest, indent);
1957
1958 if(condition_met) {
1960 } else {
1962 }
1963
1965
1966 if(auto k = state.lookup_key(key); k) {
1967 add_to_layout_box(state, dest, box, k);
1968 } else {
1969 add_to_layout_box(state, dest, box, key);
1970 }
1972}
1973void add_line_with_condition(sys::state& state, layout_base& dest, std::string_view key, bool condition_met, variable_type subkey, substitution value, int32_t indent) {
1974 auto box = text::open_layout_box(dest, indent);
1975
1976 if(condition_met) {
1978 } else {
1980 }
1981
1983
1984 if(auto k = state.lookup_key(key); k) {
1987 add_to_layout_box(state, dest, box, k, m);
1988 } else {
1989 add_to_layout_box(state, dest, box, key);
1990 }
1992}
1993void add_line_with_condition(sys::state& state, layout_base& dest, std::string_view key, bool condition_met, variable_type subkey, substitution value, variable_type subkeyb, substitution valueb, int32_t indent) {
1994 auto box = text::open_layout_box(dest, indent);
1995
1996 if(condition_met) {
1998 } else {
2000 }
2001
2003
2004 if(auto k = state.lookup_key(key); k) {
2007 add_to_substitution_map(m, subkeyb, valueb);
2008 add_to_layout_box(state, dest, box, k, m);
2009 } else {
2010 add_to_layout_box(state, dest, box, key);
2011 }
2013}
2014void add_line_with_condition(sys::state& state, layout_base& dest, std::string_view key, bool condition_met, variable_type subkey, substitution value, variable_type subkeyb, substitution valueb, variable_type subkeyc, substitution valuec, int32_t indent) {
2015 auto box = text::open_layout_box(dest, indent);
2016
2017 if(condition_met) {
2019 } else {
2021 }
2022
2024
2025 if(auto k = state.lookup_key(key); k) {
2028 add_to_substitution_map(m, subkeyb, valueb);
2029 add_to_substitution_map(m, subkeyc, valuec);
2030 add_to_layout_box(state, dest, box, k, m);
2031 } else {
2032 add_to_layout_box(state, dest, box, key);
2033 }
2035}
2037 int32_t indent) {
2038
2039 auto box = text::open_layout_box(dest, indent);
2040 if(auto k = state.lookup_key(key); k) {
2043
2044 add_to_layout_box(state, dest, box, k, sub);
2045 } else {
2046 add_to_layout_box(state, dest, box, key);
2047 }
2049}
2051 variable_type subkey_b, substitution value_b, int32_t indent) {
2052 auto box = text::open_layout_box(dest, indent);
2053 if(auto k = state.lookup_key(key); k) {
2056 text::add_to_substitution_map(sub, subkey_b, value_b);
2057
2058 add_to_layout_box(state, dest, box, k, sub);
2059 } else {
2060 add_to_layout_box(state, dest, box, key);
2061 }
2063}
2065 variable_type subkey_b, substitution value_b, variable_type subkey_c, substitution value_c, int32_t indent) {
2066 auto box = text::open_layout_box(dest, indent);
2067 if(auto k = state.lookup_key(key); k) {
2070 text::add_to_substitution_map(sub, subkey_b, value_b);
2071 text::add_to_substitution_map(sub, subkey_c, value_c);
2072
2073 add_to_layout_box(state, dest, box, k, sub);
2074 } else {
2075 add_to_layout_box(state, dest, box, key);
2076 }
2078}
2080 variable_type subkey_b, substitution value_b, variable_type subkey_c, substitution value_c, variable_type subkey_d,
2081 substitution value_d, int32_t indent) {
2082 auto box = text::open_layout_box(dest, indent);
2083 if(auto k = state.lookup_key(key); k) {
2086 text::add_to_substitution_map(sub, subkey_b, value_b);
2087 text::add_to_substitution_map(sub, subkey_c, value_c);
2088 text::add_to_substitution_map(sub, subkey_d, value_d);
2089
2090 add_to_layout_box(state, dest, box, k, sub);
2091 } else {
2092 add_to_layout_box(state, dest, box, key);
2093 }
2095}
2096
2097
2100 text::add_to_layout_box(state, dest, box, std::string_view(" "));
2102}
2103
2104void nation_name_and_flag(sys::state& state, dcon::nation_id n, layout_base& dest, int32_t indent) {
2105 auto box = text::open_layout_box(dest, indent);
2106 auto ident = state.world.nation_get_identity_from_identity_holder(n);
2107 add_to_layout_box(state, dest, box, text::embedded_flag{ ident ? ident : state.national_definitions.rebel_id });
2111}
2112
2113std::string resolve_string_substitution(sys::state& state, dcon::text_key source_text, substitution_map const& mp) {
2114 std::string result;
2115
2116 std::string_view sv;
2117 if(auto it = state.locale_key_to_text_sequence.find(source_text); it != state.locale_key_to_text_sequence.end()) {
2118 sv = state.locale_string_view(it->second);
2119 } else {
2120 sv = state.to_string_view(source_text);
2121 }
2122
2123 char const* seq_start = sv.data();
2124 char const* seq_end = sv.data() + sv.size();
2125 char const* section_start = seq_start;
2126
2127 auto add_text_range = [&](std::string_view sv) {
2128 result += sv;
2129 };
2130
2131 for(char const* pos = seq_start; pos < seq_end;) {
2132 bool colour_esc = false;
2133 if(pos + 1 < seq_end && uint8_t(*pos) == 0xC2 && uint8_t(*(pos + 1)) == 0xA7) {
2134 if(section_start < pos)
2135 add_text_range(std::string_view(section_start, pos - section_start));
2136
2137 pos += 2;
2138 section_start = pos;
2139 if(pos < seq_end) {
2140 pos += 1;
2141 section_start = pos;
2142 }
2143 } else if(pos + 2 < seq_end && uint8_t(*pos) == 0xEF && uint8_t(*(pos + 1)) == 0xBF && uint8_t(*(pos + 2)) == 0xBD && is_qmark_color(*(pos + 3))) {
2144 if(section_start < pos)
2145 add_text_range(std::string_view(section_start, pos - section_start));
2146
2147 section_start = pos += 3;
2148
2149 if(pos < seq_end) {
2150 pos += 1;
2151 section_start = pos;
2152 }
2153 } else if(pos + 1 < seq_end && *pos == '?' && is_qmark_color(*(pos + 1))) {
2154 if(section_start < pos)
2155 add_text_range(std::string_view(section_start, pos - section_start));
2156
2157 pos += 1;
2158 section_start = pos;
2159
2160 if(pos < seq_end) {
2161 pos += 1;
2162 section_start = pos;
2163 }
2164 } else if(*pos == '$') {
2165 if(section_start < pos)
2166 add_text_range(std::string_view(section_start, pos - section_start));
2167
2168 const char* vend = pos + 1;
2169 for(; vend != seq_end && *vend != '$'; ++vend)
2170 ;
2171 if(vend > pos + 1) {
2172 auto var_type = variable_type_from_name(std::string_view(pos + 1, vend - pos - 1));
2174 if(auto it = mp.find(uint32_t(var_type)); it != mp.end()) {
2175 result += impl::lb_resolve_substitution(state, it->second, mp);
2176 } else {
2177 result += "???";
2178 }
2179 } else {
2180 result += std::string_view(pos, (vend - pos) + 1);
2181 }
2182 }
2183 pos = vend + 1;
2184 section_start = pos;
2185 } else if(pos + 1 < seq_end && *pos == '\\' && *(pos + 1) == 'n') {
2186 add_text_range(std::string_view(section_start, pos - section_start));
2187 section_start = pos += 2;
2188 } else {
2189 ++pos;
2190 }
2191 }
2192
2193 if(section_start < seq_end)
2194 add_text_range(std::string_view(section_start, seq_end - section_start));
2195
2196 return result;
2197}
2198
2199std::string resolve_string_substitution(sys::state& state, std::string_view key, substitution_map const& mp) {
2200 dcon::text_key source_text;
2201 if(auto k = state.lookup_key(key); k) {
2202 source_text = k;
2203 }
2204
2205 if(!source_text)
2206 return std::string{key};
2207
2208 return resolve_string_substitution(state, source_text, mp);
2209}
2210
2211
2214}
2215void single_line_layout::add_text(sys::state& state, dcon::text_key source_text) {
2216 add_to_layout_box(state, *this, box, source_text);
2217}
2218
2219} // namespace text
FT_Face font_face
Definition: fonts.hpp:109
float base_glyph_width(char32_t ch_in)
Definition: fonts.cpp:471
pop_satisfaction_wrapper_fat fatten(data_container const &c, pop_satisfaction_wrapper_id id) noexcept
Definition: events.cpp:8
constexpr uint8_t level_friendly
Definition: nations.hpp:168
constexpr uint8_t level_opposed
Definition: nations.hpp:165
constexpr uint8_t level_neutral
Definition: nations.hpp:164
constexpr uint8_t level_hostile
Definition: nations.hpp:166
constexpr uint8_t level_in_sphere
Definition: nations.hpp:169
constexpr uint8_t level_mask
Definition: nations.hpp:163
constexpr uint8_t level_cordial
Definition: nations.hpp:167
focus_type
Definition: nations.hpp:40
uint32_t tag_to_int(char first, char second, char third)
Definition: nations.hpp:7
Definition: table.hpp:10
std::string lb_resolve_substitution(sys::state &state, substitution sub, substitution_map const &mp)
Definition: text.cpp:1545
void lb_finish_line(layout_base &dest, layout_box &box, int32_t line_height)
Definition: text.cpp:1108
Definition: bmfont.cpp:118
variable_type
Definition: text.hpp:37
void add_line_break_to_layout_box(sys::state &state, layout_base &dest, layout_box &box)
Definition: text.cpp:1147
void add_to_layout_box(sys::state &state, layout_base &dest, layout_box &box, embedded_flag ico)
Definition: text.cpp:1165
std::string resolve_string_substitution(sys::state &state, dcon::text_key source_text, substitution_map const &mp)
Definition: text.cpp:2113
std::string format_money(float num)
Definition: text.cpp:1029
std::string get_focus_category_name(sys::state const &state, nations::focus_type category)
Definition: text.cpp:909
int32_t size_from_font_id(uint16_t id)
Definition: fonts.cpp:119
embedded_icon
Definition: text.hpp:781
std::string get_name_as_string(sys::state &state, T t)
Definition: text.hpp:954
std::string format_ratio(int32_t left, int32_t right)
Definition: text.cpp:1064
layout_box open_layout_box(layout_base &dest, int32_t indent)
Definition: text.cpp:1799
bool codepoint_is_space(uint32_t c) noexcept
Definition: text.cpp:88
void add_unparsed_text_to_layout_box(sys::state &state, layout_base &dest, layout_box &box, std::string_view sv, substitution_map const &mp)
Definition: text.cpp:1601
bool codepoint_is_line_break(uint32_t c) noexcept
Definition: text.cpp:92
columnar_layout create_columnar_layout(sys::state &state, layout &dest, layout_parameters const &params, int32_t column_width)
Definition: text.cpp:1873
std::string date_to_string(sys::state &state, sys::date date)
Definition: text.cpp:1082
alignment
Definition: text.hpp:36
void localised_single_sub_box(sys::state &state, layout_base &dest, layout_box &box, std::string_view key, variable_type subkey, substitution value)
Definition: text.cpp:1888
void localised_format_box(sys::state &state, layout_base &dest, layout_box &box, std::string_view key, text::substitution_map const &sub)
Definition: text.cpp:1880
std::string prettify(int64_t num)
Definition: text.cpp:762
endless_layout create_endless_layout(sys::state &state, layout &dest, layout_parameters const &params)
Definition: text.cpp:1100
std::string format_float(float num, size_t digits)
Definition: text.cpp:981
dcon::text_key get_ruler_title(sys::state &state, dcon::nation_id n)
Definition: text.cpp:899
void add_line(sys::state &state, layout_base &dest, dcon::text_key txt, int32_t indent)
Definition: text.cpp:1899
std::string get_short_state_name(sys::state &state, dcon::state_instance_id state_id)
Definition: text.cpp:821
uint32_t codepoint_from_utf8(char const *start, char const *end)
Definition: text.cpp:66
void add_line_with_condition(sys::state &state, layout_base &dest, std::string_view key, bool condition_met, int32_t indent)
Definition: text.cpp:1955
void add_line_break_to_layout(sys::state &state, columnar_layout &dest)
Definition: text.cpp:1152
text::alignment to_text_alignment(ui::alignment in)
Definition: text.cpp:1217
text_color char_to_color(char in)
Definition: text.cpp:18
std::string lowercase_str(std::string_view sv)
Definition: text.cpp:57
bool is_fixed_token_ci(std::string_view v, char const (&t)[N])
Definition: text.cpp:125
char16_t win1250toUTF16(char in)
Definition: text.cpp:587
void add_to_substitution_map(substitution_map &mp, variable_type key, substitution value)
Definition: text.cpp:1068
surrogate_pair make_surrogate_pair(uint32_t val) noexcept
Definition: fonts.hpp:55
void add_divider_to_layout_box(sys::state &state, layout_base &dest, layout_box &box)
Definition: text.cpp:2098
dcon::text_key get_adjective(sys::state &state, dcon::nation_id id)
Definition: text.cpp:890
std::string get_dynamic_state_name(sys::state &state, dcon::state_instance_id state_id)
Definition: text.cpp:837
std::string format_wholenum(int32_t num)
Definition: text.cpp:1033
size_t size_from_utf8(char const *start, char const *)
Definition: text.cpp:82
ankerl::unordered_dense::map< uint32_t, substitution > substitution_map
Definition: text.hpp:794
bool requires_surrogate_pair(uint32_t codepoint)
Definition: fonts.hpp:46
std::string produce_simple_string(sys::state const &state, dcon::text_key id)
Definition: text.cpp:617
text_color
Definition: text.hpp:18
dcon::text_key get_name(sys::state &state, dcon::nation_id id)
Definition: text.cpp:880
font_selection font_index_from_font_id(sys::state &state, uint16_t id)
Definition: fonts.cpp:130
std::string format_percentage(float num, size_t digits)
Definition: text.cpp:977
void consume_csv_file(sys::state &state, char const *file_content, uint32_t file_size, int32_t target_column, bool as_unicode)
Definition: text.cpp:96
text::alignment localized_alignment(sys::state &state, text::alignment in)
Definition: text.cpp:1201
std::string get_influence_level_name(sys::state const &state, uint8_t v)
Definition: text.cpp:958
variable_type variable_type_from_name(std::string_view v)
Definition: text.cpp:137
dcon::text_key localize_month(sys::state const &state, uint16_t month)
Definition: text.cpp:1072
void add_space_to_layout_box(sys::state &state, layout_base &dest, layout_box &box)
Definition: text.cpp:1788
void nation_name_and_flag(sys::state &state, dcon::nation_id n, layout_base &dest, int32_t indent)
Definition: text.cpp:2104
std::string get_province_state_name(sys::state &state, dcon::province_id prov_id)
Definition: text.cpp:864
dcon::text_key find_or_add_key(sys::state &state, std::string_view key, bool as_unicode)
Definition: text.cpp:695
uint32_t font_size(std::string_view txt)
Definition: fonts.cpp:35
std::string prettify_currency(float num)
Definition: text.cpp:702
void close_layout_box(columnar_layout &dest, layout_box &box)
Definition: text.cpp:1807
bool is_qmark_color(char in)
Definition: text.cpp:53
std::variant< std::string_view, dcon::text_key, dcon::province_id, dcon::state_instance_id, dcon::nation_id, dcon::national_identity_id, int64_t, fp_one_place, sys::date, std::monostate, fp_two_places, fp_three_places, fp_four_places, fp_currency, pretty_integer, fp_percentage, fp_percentage_one_place, int_percentage, int_wholenum, dcon::state_definition_id, embedded_icon, embedded_flag, embedded_unit_icon > substitution
Definition: text.hpp:793
uint uint32_t
uchar uint8_t
@ ident
#define snprintf
void internal_close_box(layout_box &box) final
Definition: text.cpp:1863
void internal_close_box(layout_box &box) final
Definition: text.cpp:1866
size_t first_chunk
Definition: text.hpp:824
size_t line_start
Definition: text.hpp:825
int32_t y_position
Definition: text.hpp:831
int32_t y_size
Definition: text.hpp:828
int32_t x_offset
Definition: text.hpp:826
int32_t x_size
Definition: text.hpp:827
float x_position
Definition: text.hpp:830
text_chunk const * get_chunk_from_position(int32_t x, int32_t y) const
Definition: text.cpp:1091
std::vector< text_chunk > contents
Definition: text.hpp:818
void internal_close_box(layout_box &box) final
Definition: text.cpp:1869
void add_text(sys::state &state, std::string_view v)
Definition: text.cpp:2212
std::vector< stored_glyph > glyph_info
Definition: fonts.hpp:82
#define CT_STRING_ENUM(X)
Definition: text.cpp:135