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 "%.1f £",
732 "%.1fK £",
733 "%.1fM £",
734 "%.1fB £",
735 "%.1fT £",
736 "%.1fP £",
737 "%.1fZ £"
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
1201 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;
1202
1203 if(dest.native_rtl == layout_base::rtl_status::rtl) {
1204 box.x_position -= v_amount;
1205 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 });
1206 } else {
1207 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 });
1208 box.x_position += v_amount;
1209 }
1210}
1211
1213 if(in == alignment::left && state.world.locale_get_native_rtl(state.font_collection.get_current_locale())) {
1214 return alignment::right;
1215 } else if(in == alignment::right && state.world.locale_get_native_rtl(state.font_collection.get_current_locale())) {
1216 return alignment::left;
1217 }
1218 return in;
1219}
1221 if(in == ui::alignment::left && state.world.locale_get_native_rtl(state.font_collection.get_current_locale())) {
1222 return ui::alignment::right;
1223 } else if(in == ui::alignment::right && state.world.locale_get_native_rtl(state.font_collection.get_current_locale())) {
1224 return ui::alignment::left;
1225 }
1226 return in;
1227}
1229 switch(in) {
1231 return text::alignment::left;
1237 return text::alignment::left;
1238 default:
1239 return text::alignment::left;
1240 }
1241
1242}
1243
1245 if(text.size() == 0)
1246 return;
1247
1248 auto& font = state.font_collection.get_font(state, text::font_index_from_font_id(state, dest.fixed_parameters.font_id));
1249 auto text_height = int32_t(std::ceil(state.font_collection.line_height(state, dest.fixed_parameters.font_id)));
1250 auto line_height = text_height + dest.fixed_parameters.leading;
1251 auto tmp_color = color;
1252 if(std::holds_alternative<dcon::nation_id>(source)
1253 || std::holds_alternative<dcon::province_id>(source)
1254 || std::holds_alternative<dcon::state_instance_id>(source)
1255 || std::holds_alternative<dcon::state_definition_id>(source)) {
1256 if(!dest.fixed_parameters.suppress_hyperlinks) {
1257 if(color != text_color::black) {
1258 tmp_color = text_color::light_blue;
1259 } else {
1260 tmp_color = text_color::dark_blue;
1261 }
1262 } else {
1263 if(color != text_color::black) {
1264 tmp_color = text_color::yellow;
1265 } else {
1266 tmp_color = text_color::orange;
1267 }
1268 }
1269 }
1270
1271 bool first_in_line = true;
1272 auto font_size = text::size_from_font_id(dest.fixed_parameters.font_id);
1273
1274 std::vector<uint16_t> temp_text;
1275 {
1276 auto start = text.data();
1277 auto end = start + text.length();
1278 int32_t base_index = 0;
1279
1280 while(start + base_index < end) {
1281 auto c = codepoint_from_utf8(start + base_index, end);
1282
1283 if(!requires_surrogate_pair(c)) {
1284 temp_text.push_back(char16_t(c));
1285 } else {
1286 auto p = make_surrogate_pair(c);
1287 temp_text.push_back(char16_t(p.high));
1288 temp_text.push_back(char16_t(p.low));
1289 }
1290
1291 base_index += int32_t(size_from_utf8(start + base_index, end));
1292 }
1293 }
1294
1295 UErrorCode errorCode = U_ZERO_ERROR;
1296 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);
1297
1298 if(!lb_it || !U_SUCCESS(errorCode)) {
1299 std::abort(); // couldn't create iterator
1300 }
1301
1302 ubrk_first(lb_it);
1303
1304 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{});
1305
1306 if(dest.native_rtl == layout_base::rtl_status::rtl) {
1307 int32_t glyph_position = 0;
1308 int32_t glyph_start_position = 0;
1309 int32_t cluster_position = 0;
1310 int32_t cluster_start_position = 0;
1311
1312 while(cluster_start_position < int32_t(temp_text.size())) {
1313 if(dest.fixed_parameters.single_line && box.x_position <= dest.fixed_parameters.left)
1314 break;
1315
1316 auto next_cluster_position = ubrk_next(lb_it);
1317 int32_t next_glyph_position = 0;
1318
1319 if(next_cluster_position == UBRK_DONE) {
1320 next_glyph_position = int32_t(all_glyphs.glyph_info.size());
1321 next_cluster_position = int32_t(temp_text.size());
1322 } else {
1323 for(next_glyph_position = glyph_position; next_glyph_position < int32_t(all_glyphs.glyph_info.size()); ++next_glyph_position) {
1324 if(all_glyphs.glyph_info[next_glyph_position].cluster >= uint32_t(next_cluster_position)) {
1325 break;
1326 }
1327 }
1328 }
1329
1330 float extent = state.font_collection.text_extent(state, all_glyphs, glyph_start_position, next_glyph_position - glyph_start_position, dest.fixed_parameters.font_id);
1331
1332 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) {
1333 // too long, but no line breaking opportunities earlier in the line
1334
1335 box.x_position -= extent;
1336 box.y_size = std::max(box.y_size, box.y_position + line_height);
1337 box.x_size = std::max(box.x_size, int32_t(dest.fixed_parameters.right - box.x_position));
1338
1339 dest.base_layout.contents.push_back(text_chunk{
1340 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)),
1341 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 });
1342
1343 if(box.x_position - extent <= dest.fixed_parameters.left)
1344 impl::lb_finish_line(dest, box, line_height);
1345
1346 glyph_start_position = next_glyph_position;
1347 glyph_position = next_glyph_position;
1348 cluster_position = next_cluster_position;
1349 cluster_start_position = next_cluster_position;
1350 } else if(!dest.fixed_parameters.single_line && box.x_position - extent <= dest.fixed_parameters.left) {
1351 extent = state.font_collection.text_extent(state, all_glyphs, glyph_start_position, glyph_position - glyph_start_position, dest.fixed_parameters.font_id);
1352
1353 box.x_position -= extent;
1354 box.y_size = std::max(box.y_size, box.y_position + line_height);
1355 box.x_size = std::max(box.x_size, int32_t(dest.fixed_parameters.right - box.x_position));
1356 dest.base_layout.contents.push_back(
1357 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)),
1358 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 });
1359
1360 impl::lb_finish_line(dest, box, line_height);
1361
1362 glyph_start_position = glyph_position;
1363 cluster_start_position = cluster_position;
1364
1365 ubrk_previous(lb_it);
1366 first_in_line = true;
1367 } else if(dest.fixed_parameters.single_line && box.x_position - extent <= dest.fixed_parameters.left) {
1368 auto glyphid = FT_Get_Char_Index(font.font_face, 0x2026);
1369
1370 bool ellipsis_valid = true;
1371 auto width_of_ellipsis = font.base_glyph_width(glyphid) * font_size / 64.f;
1372
1373 if(width_of_ellipsis <= 0 || glyphid == 0) {
1374 ellipsis_valid = false;
1375 width_of_ellipsis = font.base_glyph_width(FT_Get_Char_Index(font.font_face, '.')) * 3.0f * font_size / 64.f;
1376 }
1377 if(state.user_settings.use_classic_fonts) {
1378 ellipsis_valid = false;
1379 width_of_ellipsis = 3.0f * font_size / 6.f;
1380 }
1381
1382 int32_t m = glyph_start_position;
1383 while(m < next_glyph_position) {
1384 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)
1385 break;
1386 ++m;
1387 }
1388 if(m >= next_glyph_position) m = next_glyph_position - 1;
1389
1390 auto cluster_end = all_glyphs.glyph_info[m].cluster;
1391 std::vector<uint16_t> tempv{ temp_text.data() + cluster_start_position, temp_text.data() + cluster_end };
1392
1393 if(ellipsis_valid) {
1394 tempv.push_back(0x2026);
1395 } else {
1396 tempv.push_back('.'); tempv.push_back('.'); tempv.push_back('.');
1397 }
1398
1399 dest.base_layout.contents.push_back(
1400 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())),
1401 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 });
1402 box.x_position = dest.fixed_parameters.left;
1403 box.y_size = std::max(box.y_size, box.y_position + line_height);
1404 box.x_size = std::max(box.x_size, int32_t(dest.fixed_parameters.right - box.x_position));
1405 } else if(next_cluster_position >= int32_t(temp_text.size())) {
1406 // no remaining text
1407
1408 box.x_position -= extent;
1409 box.y_size = std::max(box.y_size, box.y_position + line_height);
1410 box.x_size = std::max(box.x_size, int32_t(dest.fixed_parameters.right - box.x_position));
1411
1412 dest.base_layout.contents.push_back(text_chunk{
1413 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)),
1414 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 });
1415
1416 if(!dest.fixed_parameters.single_line && box.x_position <= dest.fixed_parameters.left)
1417 impl::lb_finish_line(dest, box, line_height);
1418
1419 glyph_start_position = next_glyph_position;
1420 glyph_position = next_glyph_position;
1421 cluster_position = next_cluster_position;
1422 cluster_start_position = next_cluster_position;
1423 } else {
1424 glyph_position = next_glyph_position;
1425 cluster_position = next_cluster_position;
1426 first_in_line = false;
1427 }
1428 }
1429
1430 } else {
1431 int32_t glyph_position = 0;
1432 int32_t glyph_start_position = 0;
1433 int32_t cluster_position = 0;
1434 int32_t cluster_start_position = 0;
1435
1436 while(cluster_start_position < int32_t(temp_text.size())) {
1437 if(dest.fixed_parameters.single_line && box.x_position >= dest.fixed_parameters.right)
1438 break;
1439
1440 auto next_cluster_position = ubrk_next(lb_it);
1441 int32_t next_glyph_position = 0;
1442
1443 if(next_cluster_position == UBRK_DONE) {
1444 next_glyph_position = int32_t(all_glyphs.glyph_info.size());
1445 next_cluster_position = int32_t(temp_text.size());
1446 } else {
1447 for(next_glyph_position = glyph_position; next_glyph_position < int32_t(all_glyphs.glyph_info.size()); ++next_glyph_position) {
1448 if(all_glyphs.glyph_info[next_glyph_position].cluster >= uint32_t(next_cluster_position)) {
1449 break;
1450 }
1451 }
1452 }
1453
1454 float extent = state.font_collection.text_extent(state, all_glyphs, glyph_start_position, next_glyph_position - glyph_start_position, dest.fixed_parameters.font_id);
1455
1456 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) {
1457 // too long, but no line breaking opportunities earlier in the line
1458
1459 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 });
1460
1461 box.x_position += extent;
1462 box.y_size = std::max(box.y_size, box.y_position + line_height);
1463 box.x_size = std::max(box.x_size, int32_t(box.x_position));
1464
1465 if(box.x_position + extent >= dest.fixed_parameters.right)
1466 impl::lb_finish_line(dest, box, line_height);
1467
1468 glyph_start_position = next_glyph_position;
1469 glyph_position = next_glyph_position;
1470 cluster_position = next_cluster_position;
1471 cluster_start_position = next_cluster_position;
1472 } else if(!dest.fixed_parameters.single_line && box.x_position + extent >= dest.fixed_parameters.right) {
1473
1474 extent = state.font_collection.text_extent(state, all_glyphs, glyph_start_position, glyph_position - glyph_start_position, dest.fixed_parameters.font_id);
1475 dest.base_layout.contents.push_back(
1476 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)),
1477 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 });
1478 box.x_position += extent;
1479 box.y_size = std::max(box.y_size, box.y_position + line_height);
1480 box.x_size = std::max(box.x_size, int32_t(box.x_position));
1481
1482 impl::lb_finish_line(dest, box, line_height);
1483
1484 glyph_start_position = glyph_position;
1485 cluster_start_position = cluster_position;
1486
1487 ubrk_previous(lb_it);
1488 first_in_line = true;
1489 } else if(dest.fixed_parameters.single_line && box.x_position + extent >= dest.fixed_parameters.right) {
1490 auto glyphid = FT_Get_Char_Index(font.font_face, 0x2026);
1491
1492 bool ellipsis_valid = true;
1493 auto width_of_ellipsis = font.base_glyph_width(glyphid) * font_size / 64.f;
1494
1495 if(width_of_ellipsis <= 0 || glyphid == 0) {
1496 ellipsis_valid = false;
1497 width_of_ellipsis = font.base_glyph_width(FT_Get_Char_Index(font.font_face, '.')) * 3.0f * font_size / 64.f;
1498 }
1499 if(state.user_settings.use_classic_fonts) {
1500 ellipsis_valid = false;
1501 width_of_ellipsis = 3.0f * font_size / 6.f;
1502 }
1503
1504 int32_t m = glyph_start_position;
1505 while(m < next_glyph_position) {
1506 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)
1507 break;
1508 ++m;
1509 }
1510 if(m >= next_glyph_position) m = next_glyph_position - 1;
1511
1512 auto cluster_end = all_glyphs.glyph_info[m].cluster;
1513 std::vector<uint16_t> tempv{temp_text.data() + cluster_start_position, temp_text.data() + cluster_end };
1514
1515 if(ellipsis_valid) {
1516 tempv.push_back(0x2026);
1517 } else {
1518 tempv.push_back('.'); tempv.push_back('.'); tempv.push_back('.');
1519 }
1520
1521 dest.base_layout.contents.push_back(
1522 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())),
1523 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 });
1524 box.x_position = dest.fixed_parameters.right;
1525 box.y_size = std::max(box.y_size, box.y_position + line_height);
1526 box.x_size = std::max(box.x_size, int32_t(box.x_position));
1527 } else if(next_cluster_position >= int32_t(temp_text.size())) {
1528 // no remaining text
1529
1530 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 });
1531
1532 box.x_position += extent;
1533 box.y_size = std::max(box.y_size, box.y_position + line_height);
1534 box.x_size = std::max(box.x_size, int32_t(box.x_position));
1535
1536 if(!dest.fixed_parameters.single_line && box.x_position >= dest.fixed_parameters.right)
1537 impl::lb_finish_line(dest, box, line_height);
1538
1539 glyph_start_position = next_glyph_position;
1540 glyph_position = next_glyph_position;
1541 cluster_position = next_cluster_position;
1542 cluster_start_position = next_cluster_position;
1543 } else {
1544 glyph_position = next_glyph_position;
1545 cluster_position = next_cluster_position;
1546 first_in_line = false;
1547 }
1548 }
1549 }
1550
1551 ubrk_close(lb_it);
1552}
1553
1554namespace impl {
1555
1557 if(std::holds_alternative<std::string_view>(sub)) {
1558 return std::string(std::get<std::string_view>(sub));
1559 } else if(std::holds_alternative<dcon::text_key>(sub)) {
1560 auto tkey = std::get<dcon::text_key>(sub);
1561 if(auto it = state.locale_key_to_text_sequence.find(tkey); it != state.locale_key_to_text_sequence.end()) {
1562 return std::string(state.locale_string_view(it->second));
1563 } else {
1564 return std::string(state.to_string_view(tkey));
1565 }
1566 } else if(std::holds_alternative<dcon::nation_id>(sub)) {
1567 dcon::nation_id nid = std::get<dcon::nation_id>(sub);
1569 } else if(std::holds_alternative<dcon::state_instance_id>(sub)) {
1570 dcon::state_instance_id sid = std::get<dcon::state_instance_id>(sub);
1571 return get_dynamic_state_name(state, sid);
1572 } else if(std::holds_alternative<dcon::province_id>(sub)) {
1573 auto pid = std::get<dcon::province_id>(sub);
1574 return text::produce_simple_string(state, state.world.province_get_name(pid));
1575 } else if(std::holds_alternative<dcon::national_identity_id>(sub)) {
1576 auto t = std::get<dcon::national_identity_id>(sub);
1577 auto h = state.world.national_identity_get_nation_from_identity_holder(t);
1578 return text::produce_simple_string(state, h ? text::get_name(state, h) : state.world.national_identity_get_name(t));
1579 } else if(std::holds_alternative<int64_t>(sub)) {
1580 return std::to_string(std::get<int64_t>(sub));
1581 } else if(std::holds_alternative<fp_one_place>(sub)) {
1582 return text::format_float(std::get<fp_one_place>(sub).value, 1);
1583 } else if(std::holds_alternative<fp_two_places>(sub)) {
1584 return text::format_float(std::get<fp_two_places>(sub).value, 2);
1585 } else if(std::holds_alternative<fp_three_places>(sub)) {
1586 return text::format_float(std::get<fp_three_places>(sub).value, 3);
1587 } else if(std::holds_alternative<fp_four_places>(sub)) {
1588 return text::format_float(std::get<fp_four_places>(sub).value, 4);
1589 } else if(std::holds_alternative<sys::date>(sub)) {
1590 return date_to_string(state, std::get<sys::date>(sub));
1591 } else if(std::holds_alternative<fp_currency>(sub)) {
1592 return text::format_money(std::get<fp_currency>(sub).value);
1593 } else if(std::holds_alternative<pretty_integer>(sub)) {
1594 return prettify(std::get<pretty_integer>(sub).value);
1595 } else if(std::holds_alternative<fp_percentage>(sub)) {
1596 return text::format_percentage(std::get<fp_percentage>(sub).value, 0);
1597 } else if(std::holds_alternative<fp_percentage_one_place>(sub)) {
1598 return text::format_percentage(std::get<fp_percentage_one_place>(sub).value, 1);
1599 } else if(std::holds_alternative<int_percentage>(sub)) {
1600 return std::to_string(std::get<int_percentage>(sub).value) + "%";
1601 } else if(std::holds_alternative<int_wholenum>(sub)) {
1602 return text::format_wholenum(std::get<int_wholenum>(sub).value);
1603 } else if(std::holds_alternative<dcon::state_definition_id>(sub)) {
1604 return produce_simple_string(state, state.world.state_definition_get_name(std::get<dcon::state_definition_id>(sub)));
1605 } else {
1606 return std::string("?");
1607 }
1608}
1609
1610} // namespace impl
1611
1613 if(sv.length() == 0)
1614 return;
1615
1616 auto current_color = dest.fixed_parameters.color;
1617
1618 auto text_height = int32_t(std::ceil(state.font_collection.line_height(state, dest.fixed_parameters.font_id)));
1619 auto line_height = text_height + dest.fixed_parameters.leading;
1620
1621 char const* seq_start = sv.data();
1622 char const* seq_end = sv.data() + sv.size();
1623 char const* section_start = seq_start;
1624
1625 auto add_text_range = [&](std::string_view sv) {
1626 if(sv.length() > 0) {
1627 add_to_layout_box(state, dest, box, sv, current_color, std::monostate{});
1628 }
1629 };
1630
1631 for(char const* pos = seq_start; pos < seq_end;) {
1632 bool colour_esc = false;
1633 if(pos + 1 < seq_end && uint8_t(*pos) == 0xC2 && uint8_t(*(pos + 1)) == 0xA7) {
1634 if(section_start < pos)
1635 add_text_range(std::string_view(section_start, pos - section_start));
1636
1637 pos += 2;
1638 section_start = pos;
1639 if(pos < seq_end) {
1640 auto newcolor = char_to_color(*pos);
1641 if(newcolor == text_color::reset)
1642 current_color = dest.fixed_parameters.color;
1643 else
1644 current_color = newcolor;
1645
1646 pos += 1;
1647 section_start = pos;
1648 }
1649 } 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))) {
1650 if(section_start < pos)
1651 add_text_range(std::string_view(section_start, pos - section_start));
1652
1653 section_start = pos += 3;
1654
1655 if(pos < seq_end) {
1656 auto newcolor = char_to_color(*pos);
1657 if(newcolor == text_color::reset)
1658 current_color = dest.fixed_parameters.color;
1659 else
1660 current_color = newcolor;
1661
1662 pos += 1;
1663 section_start = pos;
1664 }
1665 } else if(*pos == '@' && pos + 3 < seq_end) {
1666 if(*(pos + 1) == '(' && *(pos + 3) == ')') {
1667 if(*(pos + 2) == 'A') {
1668 if(section_start < pos)
1669 add_text_range(std::string_view(section_start, pos - section_start));
1671 pos += 4;
1672 section_start = pos;
1673 } else if(*(pos + 2) == 'N') {
1674 if(section_start < pos)
1675 add_text_range(std::string_view(section_start, pos - section_start));
1677 pos += 4;
1678 section_start = pos;
1679 } else if(*(pos + 2) == 'T') {
1680 if(section_start < pos)
1681 add_text_range(std::string_view(section_start, pos - section_start));
1683 pos += 4;
1684 section_start = pos;
1685 } else if(*(pos + 2) == 'F') {
1686 if(section_start < pos)
1687 add_text_range(std::string_view(section_start, pos - section_start));
1689 pos += 4;
1690 section_start = pos;
1691 } else {
1692 ++pos;
1693 }
1694 } else if(*(pos + 1) == '*') {
1695 if(section_start < pos)
1696 add_text_range(std::string_view(section_start, pos - section_start));
1697
1698 uint8_t tens = char(*(pos + 2)) - '0';
1699 uint8_t ones = char(*(pos + 3)) - '0';
1700 uint8_t unit_index = tens * 10 + ones;
1701 pos += 4;
1702 dcon::unit_type_id given_unit_type_id { unit_index };
1703 add_to_layout_box(state, dest, box, embedded_unit_icon{ given_unit_type_id });
1704 section_start = pos;
1705 } else if(*(pos + 1) == '$') {
1706 if(section_start < pos)
1707 add_text_range(std::string_view(section_start, pos - section_start));
1708
1709 uint8_t tens = char(*(pos + 2)) - '0';
1710 uint8_t ones = char(*(pos + 3)) - '0';
1711 uint8_t commodity_index = tens * 10 + ones;
1712 pos += 4;
1713 dcon::commodity_id cid{ commodity_index };
1715 section_start = pos;
1716 } else {
1717 auto tag_int = nations::tag_to_int(char(toupper(*(pos + 1))), char(toupper(*(pos + 2))), char(toupper(*(pos + 3))));
1718 bool matched = false;
1719 for(auto nid : state.world.in_national_identity) {
1720 if(nid.get_identifying_int() == tag_int) {
1721 matched = true;
1722
1723 if(section_start < pos)
1724 add_text_range(std::string_view(section_start, pos - section_start));
1725
1726 add_to_layout_box(state, dest, box, embedded_flag{ nid.id });
1727 pos += 4;
1728
1729 section_start = pos;
1730 break;
1731 }
1732 }
1733 if(!matched)
1734 ++pos;
1735 }
1736 } else if(pos + 1 < seq_end && *pos == '?' && is_qmark_color(*(pos + 1))) {
1737 if(section_start < pos)
1738 add_text_range(std::string_view(section_start, pos - section_start));
1739
1740 pos += 1;
1741 section_start = pos;
1742 // This colour escape sequence must be followed by something, otherwise
1743 // we should probably discard the last colour command
1744 if(pos < seq_end) {
1745
1746 auto newcolor = char_to_color(*pos);
1747 if(newcolor == text_color::reset)
1748 current_color = dest.fixed_parameters.color;
1749 else
1750 current_color = newcolor;
1751
1752 pos += 1;
1753 section_start = pos;
1754 }
1755 } else if(*pos == '$') {
1756 if(section_start < pos)
1757 add_text_range(std::string_view(section_start, pos - section_start));
1758
1759 const char* vend = pos + 1;
1760 for(; vend != seq_end && *vend != '$'; ++vend)
1761 ;
1762 if(vend > pos + 1) {
1763 auto var_type = variable_type_from_name(std::string_view(pos + 1, vend - pos - 1));
1765 if(auto it = mp.find(uint32_t(var_type)); it != mp.end()) {
1766 auto txt = impl::lb_resolve_substitution(state, it->second, mp);
1767 add_to_layout_box(state, dest, box, std::string_view(txt), current_color, it->second);
1768 } else {
1769 add_to_layout_box(state, dest, box, std::string_view("???"), current_color, std::monostate{});
1770 }
1771 } else {
1772 add_to_layout_box(state, dest, box, std::string_view(pos, (vend - pos) + 1), current_color, std::monostate{});
1773 }
1774 }
1775 pos = vend + 1;
1776 section_start = pos;
1777 } else if(pos + 1 < seq_end && *pos == '\\' && *(pos + 1) == 'n') {
1778 if(section_start < pos)
1779 add_text_range(std::string_view(section_start, pos - section_start));
1780
1782 section_start = pos += 2;
1783 } else {
1784 ++pos;
1785 }
1786 }
1787
1788 if(section_start < seq_end)
1789 add_text_range(std::string_view(section_start, seq_end - section_start));
1790}
1791
1792void add_to_layout_box(sys::state& state, layout_base& dest, layout_box& box, dcon::text_key source_text, substitution_map const& mp) {
1793 if(!source_text)
1794 return;
1795
1796 std::string_view sv;
1797 if(auto it = state.locale_key_to_text_sequence.find(source_text); it != state.locale_key_to_text_sequence.end()) {
1798 sv = state.locale_string_view(it->second);
1799 } else {
1800 sv = state.to_string_view(source_text);
1801 }
1803}
1804
1807 add_to_layout_box(state, dest, box, std::string_view(txt), color, val);
1808}
1809void add_to_layout_box(sys::state& state, layout_base& dest, layout_box& box, std::string const& val, text_color color) {
1810 add_to_layout_box(state, dest, box, std::string_view(val), color, std::monostate{});
1811}
1813 auto& font = state.font_collection.get_font(state, text::font_index_from_font_id(state, dest.fixed_parameters.font_id));
1814 auto glyphid = FT_Get_Char_Index(font.font_face, ' ');
1815 float amount = font.base_glyph_width(glyphid) * text::size_from_font_id(dest.fixed_parameters.font_id) / 64.f;;
1816
1817 if(dest.native_rtl == layout_base::rtl_status::rtl)
1818 box.x_position -= amount;
1819 else
1820 box.x_position += amount;
1821}
1822
1824 if(dest.native_rtl == layout_base::rtl_status::ltr)
1825 return layout_box{dest.base_layout.contents.size(), dest.base_layout.contents.size(), indent, 0, 0,
1826 float(indent + dest.fixed_parameters.left), 0, dest.fixed_parameters.color};
1827 else
1828 return layout_box{ dest.base_layout.contents.size(), dest.base_layout.contents.size(), indent, 0, 0,
1829 float(dest.fixed_parameters.right - indent), 0, dest.fixed_parameters.color };
1830}
1832 impl::lb_finish_line(dest, box, 0);
1833 if(dest.native_rtl == layout_base::rtl_status::ltr) {
1834 if(box.y_size + dest.y_cursor >= dest.fixed_parameters.bottom) { // make new column
1835 dest.current_column_x = dest.used_width + dest.column_width;
1836 for(auto i = box.first_chunk; i < dest.base_layout.contents.size(); ++i) {
1837 dest.base_layout.contents[i].y += dest.fixed_parameters.top;
1838 dest.base_layout.contents[i].x += float(dest.current_column_x);
1839 dest.used_width = std::max(dest.used_width, int32_t(dest.base_layout.contents[i].x + dest.base_layout.contents[i].width));
1840 }
1841 dest.y_cursor = box.y_size + dest.fixed_parameters.top;
1842 } else { // append to current column
1843 for(auto i = box.first_chunk; i < dest.base_layout.contents.size(); ++i) {
1844 dest.base_layout.contents[i].y += int16_t(dest.y_cursor);
1845 dest.base_layout.contents[i].x += float(dest.current_column_x);
1846 dest.used_width = std::max(dest.used_width, int32_t(dest.base_layout.contents[i].x + dest.base_layout.contents[i].width));
1847 }
1848 dest.y_cursor += box.y_size;
1849 }
1850 dest.used_height = std::max(dest.used_height, dest.y_cursor);
1851 } else {
1852 if(box.y_size + dest.y_cursor >= dest.fixed_parameters.bottom) { // make new column
1853 dest.current_column_x = dest.used_width - dest.column_width;
1854 for(auto i = box.first_chunk; i < dest.base_layout.contents.size(); ++i) {
1855 dest.base_layout.contents[i].y += dest.fixed_parameters.top;
1856 dest.base_layout.contents[i].x += float(dest.current_column_x) - float(dest.fixed_parameters.right - dest.fixed_parameters.left);
1857 dest.used_width = std::min(dest.used_width, int32_t(dest.base_layout.contents[i].x));
1858 }
1859 dest.y_cursor = box.y_size + dest.fixed_parameters.top;
1860 } else { // append to current column
1861 for(auto i = box.first_chunk; i < dest.base_layout.contents.size(); ++i) {
1862 dest.base_layout.contents[i].y += int16_t(dest.y_cursor);
1863 dest.base_layout.contents[i].x += float(dest.current_column_x) - float(dest.fixed_parameters.right - dest.fixed_parameters.left);
1864 dest.used_width = std::min(dest.used_width, int32_t(dest.base_layout.contents[i].x));
1865 }
1866 dest.y_cursor += box.y_size;
1867 }
1868 dest.used_height = std::max(dest.used_height, dest.y_cursor);
1869 }
1870}
1872 impl::lb_finish_line(dest, box, 0);
1873 for(auto i = box.first_chunk; i < dest.base_layout.contents.size(); ++i) {
1874 dest.base_layout.contents[i].y += int16_t(dest.y_cursor);
1875 }
1876
1877 dest.y_cursor += box.y_size;
1878}
1880 impl::lb_finish_line(dest, box, 0);
1881}
1882
1884 dest.internal_close_box(box);
1885}
1886
1888 close_layout_box(*this, box);
1889}
1891 close_layout_box(*this, box);
1892}
1894 close_layout_box(*this, b);
1895}
1896
1898 dest.contents.clear();
1899 dest.number_of_lines = 0;
1900 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);
1901}
1902
1903// Reduces code repeat
1905 if(auto k = state.lookup_key(key); k) {
1906 add_to_layout_box(state, dest, box, k, sub);
1907 } else {
1908 add_to_layout_box(state, dest, box, key);
1909 }
1910}
1911
1916 if(auto k = state.lookup_key(key); k) {
1917 add_to_layout_box(state, dest, box, k, sub);
1918 } else {
1919 add_to_layout_box(state, dest, box, key);
1920 }
1921}
1922
1923void add_line(sys::state& state, layout_base& dest, dcon::text_key txt, int32_t indent) {
1924 auto box = text::open_layout_box(dest, indent);
1925 add_to_layout_box(state, dest, box, txt);
1927}
1928void add_line(sys::state& state, layout_base& dest, dcon::text_key txt, variable_type subkey, substitution value, int32_t indent) {
1929 auto box = text::open_layout_box(dest, indent);
1932 add_to_layout_box(state, dest, box, txt, sub);
1934}
1936 variable_type subkey_b, substitution value_b, int32_t indent) {
1937
1938 auto box = text::open_layout_box(dest, indent);
1941 text::add_to_substitution_map(sub, subkey_b, value_b);
1942 add_to_layout_box(state, dest, box, txt, sub);
1944}
1946 variable_type subkey_b, substitution value_b, variable_type subkey_c, substitution value_c, int32_t indent) {
1947
1948 auto box = text::open_layout_box(dest, indent);
1951 text::add_to_substitution_map(sub, subkey_b, value_b);
1952 text::add_to_substitution_map(sub, subkey_c, value_c);
1953 add_to_layout_box(state, dest, box, txt, sub);
1955}
1957 variable_type subkey_b, substitution value_b, variable_type subkey_c, substitution value_c, variable_type subkey_d,
1958 substitution value_d, int32_t indent) {
1959
1960 auto box = text::open_layout_box(dest, indent);
1963 text::add_to_substitution_map(sub, subkey_b, value_b);
1964 text::add_to_substitution_map(sub, subkey_c, value_c);
1965 text::add_to_substitution_map(sub, subkey_d, value_d);
1966 add_to_layout_box(state, dest, box, txt, sub);
1968}
1969
1970void add_line(sys::state& state, layout_base& dest, std::string_view key, int32_t indent) {
1971 auto box = text::open_layout_box(dest, indent);
1972 if(auto k = state.lookup_key(key); k) {
1973 add_to_layout_box(state, dest, box, k);
1974 } else {
1975 add_to_layout_box(state, dest, box, key);
1976 }
1978}
1979void add_line_with_condition(sys::state& state, layout_base& dest, std::string_view key, bool condition_met, int32_t indent) {
1980 auto box = text::open_layout_box(dest, indent);
1981
1982 if(condition_met) {
1984 } else {
1986 }
1987
1989
1990 if(auto k = state.lookup_key(key); k) {
1991 add_to_layout_box(state, dest, box, k);
1992 } else {
1993 add_to_layout_box(state, dest, box, key);
1994 }
1996}
1997void 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) {
1998 auto box = text::open_layout_box(dest, indent);
1999
2000 if(condition_met) {
2002 } else {
2004 }
2005
2007
2008 if(auto k = state.lookup_key(key); k) {
2011 add_to_layout_box(state, dest, box, k, m);
2012 } else {
2013 add_to_layout_box(state, dest, box, key);
2014 }
2016}
2017void 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) {
2018 auto box = text::open_layout_box(dest, indent);
2019
2020 if(condition_met) {
2022 } else {
2024 }
2025
2027
2028 if(auto k = state.lookup_key(key); k) {
2031 add_to_substitution_map(m, subkeyb, valueb);
2032 add_to_layout_box(state, dest, box, k, m);
2033 } else {
2034 add_to_layout_box(state, dest, box, key);
2035 }
2037}
2038void 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) {
2039 auto box = text::open_layout_box(dest, indent);
2040
2041 if(condition_met) {
2043 } else {
2045 }
2046
2048
2049 if(auto k = state.lookup_key(key); k) {
2052 add_to_substitution_map(m, subkeyb, valueb);
2053 add_to_substitution_map(m, subkeyc, valuec);
2054 add_to_layout_box(state, dest, box, k, m);
2055 } else {
2056 add_to_layout_box(state, dest, box, key);
2057 }
2059}
2061 int32_t indent) {
2062
2063 auto box = text::open_layout_box(dest, indent);
2064 if(auto k = state.lookup_key(key); k) {
2067
2068 add_to_layout_box(state, dest, box, k, sub);
2069 } else {
2070 add_to_layout_box(state, dest, box, key);
2071 }
2073}
2075 variable_type subkey_b, substitution value_b, int32_t indent) {
2076 auto box = text::open_layout_box(dest, indent);
2077 if(auto k = state.lookup_key(key); k) {
2080 text::add_to_substitution_map(sub, subkey_b, value_b);
2081
2082 add_to_layout_box(state, dest, box, k, sub);
2083 } else {
2084 add_to_layout_box(state, dest, box, key);
2085 }
2087}
2089 variable_type subkey_b, substitution value_b, variable_type subkey_c, substitution value_c, int32_t indent) {
2090 auto box = text::open_layout_box(dest, indent);
2091 if(auto k = state.lookup_key(key); k) {
2094 text::add_to_substitution_map(sub, subkey_b, value_b);
2095 text::add_to_substitution_map(sub, subkey_c, value_c);
2096
2097 add_to_layout_box(state, dest, box, k, sub);
2098 } else {
2099 add_to_layout_box(state, dest, box, key);
2100 }
2102}
2104 variable_type subkey_b, substitution value_b, variable_type subkey_c, substitution value_c, variable_type subkey_d,
2105 substitution value_d, int32_t indent) {
2106 auto box = text::open_layout_box(dest, indent);
2107 if(auto k = state.lookup_key(key); k) {
2110 text::add_to_substitution_map(sub, subkey_b, value_b);
2111 text::add_to_substitution_map(sub, subkey_c, value_c);
2112 text::add_to_substitution_map(sub, subkey_d, value_d);
2113
2114 add_to_layout_box(state, dest, box, k, sub);
2115 } else {
2116 add_to_layout_box(state, dest, box, key);
2117 }
2119}
2120
2121
2124 text::add_to_layout_box(state, dest, box, std::string_view(" "));
2126}
2127
2128void nation_name_and_flag(sys::state& state, dcon::nation_id n, layout_base& dest, int32_t indent) {
2129 auto box = text::open_layout_box(dest, indent);
2130 auto ident = state.world.nation_get_identity_from_identity_holder(n);
2131 add_to_layout_box(state, dest, box, text::embedded_flag{ ident ? ident : state.national_definitions.rebel_id });
2135}
2136
2137std::string resolve_string_substitution(sys::state& state, dcon::text_key source_text, substitution_map const& mp) {
2138 std::string result;
2139
2140 std::string_view sv;
2141 if(auto it = state.locale_key_to_text_sequence.find(source_text); it != state.locale_key_to_text_sequence.end()) {
2142 sv = state.locale_string_view(it->second);
2143 } else {
2144 sv = state.to_string_view(source_text);
2145 }
2146
2147 char const* seq_start = sv.data();
2148 char const* seq_end = sv.data() + sv.size();
2149 char const* section_start = seq_start;
2150
2151 auto add_text_range = [&](std::string_view sv) {
2152 result += sv;
2153 };
2154
2155 for(char const* pos = seq_start; pos < seq_end;) {
2156 bool colour_esc = false;
2157 if(pos + 1 < seq_end && uint8_t(*pos) == 0xC2 && uint8_t(*(pos + 1)) == 0xA7) {
2158 if(section_start < pos)
2159 add_text_range(std::string_view(section_start, pos - section_start));
2160
2161 pos += 2;
2162 section_start = pos;
2163 if(pos < seq_end) {
2164 pos += 1;
2165 section_start = pos;
2166 }
2167 } 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))) {
2168 if(section_start < pos)
2169 add_text_range(std::string_view(section_start, pos - section_start));
2170
2171 section_start = pos += 3;
2172
2173 if(pos < seq_end) {
2174 pos += 1;
2175 section_start = pos;
2176 }
2177 } else if(pos + 1 < seq_end && *pos == '?' && is_qmark_color(*(pos + 1))) {
2178 if(section_start < pos)
2179 add_text_range(std::string_view(section_start, pos - section_start));
2180
2181 pos += 1;
2182 section_start = pos;
2183
2184 if(pos < seq_end) {
2185 pos += 1;
2186 section_start = pos;
2187 }
2188 } else if(*pos == '$') {
2189 if(section_start < pos)
2190 add_text_range(std::string_view(section_start, pos - section_start));
2191
2192 const char* vend = pos + 1;
2193 for(; vend != seq_end && *vend != '$'; ++vend)
2194 ;
2195 if(vend > pos + 1) {
2196 auto var_type = variable_type_from_name(std::string_view(pos + 1, vend - pos - 1));
2198 if(auto it = mp.find(uint32_t(var_type)); it != mp.end()) {
2199 result += impl::lb_resolve_substitution(state, it->second, mp);
2200 } else {
2201 result += "???";
2202 }
2203 } else {
2204 result += std::string_view(pos, (vend - pos) + 1);
2205 }
2206 }
2207 pos = vend + 1;
2208 section_start = pos;
2209 } else if(pos + 1 < seq_end && *pos == '\\' && *(pos + 1) == 'n') {
2210 add_text_range(std::string_view(section_start, pos - section_start));
2211 section_start = pos += 2;
2212 } else {
2213 ++pos;
2214 }
2215 }
2216
2217 if(section_start < seq_end)
2218 add_text_range(std::string_view(section_start, seq_end - section_start));
2219
2220 return result;
2221}
2222
2223std::string resolve_string_substitution(sys::state& state, std::string_view key, substitution_map const& mp) {
2224 dcon::text_key source_text;
2225 if(auto k = state.lookup_key(key); k) {
2226 source_text = k;
2227 }
2228
2229 if(!source_text)
2230 return std::string{key};
2231
2232 return resolve_string_substitution(state, source_text, mp);
2233}
2234
2235
2238}
2239void single_line_layout::add_text(sys::state& state, dcon::text_key source_text) {
2240 add_to_layout_box(state, *this, box, source_text);
2241}
2242
2243} // 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:172
constexpr uint8_t level_opposed
Definition: nations.hpp:169
constexpr uint8_t level_neutral
Definition: nations.hpp:168
constexpr uint8_t level_hostile
Definition: nations.hpp:170
constexpr uint8_t level_in_sphere
Definition: nations.hpp:173
constexpr uint8_t level_mask
Definition: nations.hpp:167
constexpr uint8_t level_cordial
Definition: nations.hpp:171
focus_type
Definition: nations.hpp:44
uint32_t tag_to_int(char first, char second, char third)
Definition: nations.hpp:11
Definition: table.hpp:10
std::string lb_resolve_substitution(sys::state &state, substitution sub, substitution_map const &mp)
Definition: text.cpp:1556
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:2137
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:957
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:1823
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:1612
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:1897
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:1912
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:1904
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:1923
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:1979
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:1228
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:2122
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:797
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:1212
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:1812
void nation_name_and_flag(sys::state &state, dcon::nation_id n, layout_base &dest, int32_t indent)
Definition: text.cpp:2128
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:1831
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, embedded_commodity_icon > substitution
Definition: text.hpp:796
bool is_qmark_color(char in)
Definition: text.cpp:53
uint uint32_t
uchar uint8_t
@ ident
#define snprintf
Holds important data about the game world, state, and other data regarding windowing,...
void internal_close_box(layout_box &box) final
Definition: text.cpp:1887
void internal_close_box(layout_box &box) final
Definition: text.cpp:1890
size_t first_chunk
Definition: text.hpp:827
size_t line_start
Definition: text.hpp:828
int32_t y_position
Definition: text.hpp:834
int32_t y_size
Definition: text.hpp:831
int32_t x_offset
Definition: text.hpp:829
int32_t x_size
Definition: text.hpp:830
float x_position
Definition: text.hpp:833
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:821
void internal_close_box(layout_box &box) final
Definition: text.cpp:1893
void add_text(sys::state &state, std::string_view v)
Definition: text.cpp:2236
std::vector< stored_glyph > glyph_info
Definition: fonts.hpp:82
#define CT_STRING_ENUM(X)
Definition: text.cpp:135