| e8555c89 Andy McCall |
1 |
#include "MsxChar2Unicode.hh" |
| 2 |
|
| 3 |
#include "File.hh" |
| 4 |
#include "FileContext.hh" |
| 5 |
#include "FileException.hh" |
| 6 |
#include "MSXException.hh" |
| 7 |
|
| 8 |
#include "StringOp.hh" |
| 9 |
#include "one_of.hh" |
| 10 |
#include "ranges.hh" |
| 11 |
#include "strCat.hh" |
| 12 |
#include "utf8_unchecked.hh" |
| 13 |
|
| 14 |
#include <algorithm> |
| 15 |
#include <bit> |
| 16 |
|
| 17 |
namespace openmsx { |
| 18 |
|
| 19 |
MsxChar2Unicode::MsxChar2Unicode(std::string_view mappingName) |
| 20 |
{ |
| 21 |
std::ranges::fill(msx2unicode, uint32_t(-1)); |
| 22 |
|
| 23 |
std::string filename; |
| 24 |
try { |
| 25 |
filename = systemFileContext().resolve( |
| 26 |
tmpStrCat("unicodemaps/character_set_mappings/", mappingName)); |
| 27 |
} catch (FileException& e) { |
| 28 |
throw MSXException("Couldn't find MSX character mapping file that was specified in unicodemap: ", mappingName, " (", e.getMessage(), ")"); |
| 29 |
} |
| 30 |
try { |
| 31 |
auto buf = File(filename).mmap<const char>(); |
| 32 |
parseVid(std::string_view(buf.data(), buf.size())); |
| 33 |
} catch (FileException&) { |
| 34 |
throw MSXException("Couldn't load MSX character mapping file that was specified in unicodemap: ", filename); |
| 35 |
} catch (MSXException& e) { |
| 36 |
throw MSXException(e.getMessage(), " in ", filename); |
| 37 |
} |
| 38 |
} |
| 39 |
|
| 40 |
|
| 41 |
|
| 42 |
|
| 43 |
|
| 44 |
static constexpr std::string_view getLine(std::string_view& text) |
| 45 |
{ |
| 46 |
if (auto pos = text.find_first_of('\n'); pos != std::string_view::npos) { |
| 47 |
|
| 48 |
auto pos2 = ((pos != 0) && (text[pos - 1] == '\r')) ? pos - 1 : pos; |
| 49 |
auto result = text.substr(0, pos2); |
| 50 |
text.remove_prefix(pos + 1); |
| 51 |
return result; |
| 52 |
} |
| 53 |
return std::exchange(text, {}); |
| 54 |
} |
| 55 |
|
| 56 |
|
| 57 |
|
| 58 |
|
| 59 |
|
| 60 |
[[nodiscard]] static constexpr std::string_view stripComments(std::string_view line) |
| 61 |
{ |
| 62 |
if (auto pos = line.find_first_of('#'); pos != std::string_view::npos) { |
| 63 |
line = line.substr(0, pos); |
| 64 |
} |
| 65 |
return line; |
| 66 |
} |
| 67 |
|
| 68 |
|
| 69 |
|
| 70 |
|
| 71 |
[[nodiscard]] static constexpr bool isSep(char c) |
| 72 |
{ |
| 73 |
return c == one_of(' ', '\t', '\r'); |
| 74 |
} |
| 75 |
|
| 76 |
|
| 77 |
|
| 78 |
|
| 79 |
|
| 80 |
|
| 81 |
static constexpr std::string_view getToken(std::string_view& line) |
| 82 |
{ |
| 83 |
size_t s = line.size(); |
| 84 |
size_t i = 0; |
| 85 |
while ((i < s) && !isSep(line[i])) { |
| 86 |
++i; |
| 87 |
} |
| 88 |
auto result = line.substr(0, i); |
| 89 |
while ((i < s) && isSep(line[i])) { |
| 90 |
++i; |
| 91 |
} |
| 92 |
line.remove_prefix(i); |
| 93 |
return result; |
| 94 |
} |
| 95 |
|
| 96 |
void MsxChar2Unicode::parseVid(std::string_view file) |
| 97 |
{ |
| 98 |
|
| 99 |
|
| 100 |
|
| 101 |
|
| 102 |
|
| 103 |
|
| 104 |
|
| 105 |
|
| 106 |
|
| 107 |
unicode2msx.reserve(256); |
| 108 |
|
| 109 |
while (!file.empty()) { |
| 110 |
auto origLine = getLine(file); |
| 111 |
auto line = stripComments(origLine); |
| 112 |
|
| 113 |
auto msxTok = getToken(line); |
| 114 |
if (msxTok.empty()) continue; |
| 115 |
auto msx = StringOp::stringTo<uint8_t>(msxTok); |
| 116 |
if (!msx) { |
| 117 |
throw MSXException("Invalid msx character value, expected an " |
| 118 |
"integer in range 0x00..0xff, but got: ", msxTok); |
| 119 |
} |
| 120 |
|
| 121 |
auto unicodeTok = getToken(line); |
| 122 |
if ((unicodeTok.size() >= 5) && (unicodeTok[0] == '<') && |
| 123 |
(unicodeTok[3] == '>') && (unicodeTok[4] == '+')) { |
| 124 |
|
| 125 |
|
| 126 |
|
| 127 |
|
| 128 |
|
| 129 |
|
| 130 |
|
| 131 |
unicodeTok.remove_prefix(5); |
| 132 |
} |
| 133 |
auto unicode = StringOp::stringTo<uint32_t>(unicodeTok); |
| 134 |
if (!unicode || *unicode > 0x10ffff) { |
| 135 |
throw MSXException("Invalid unicode character value, expected an " |
| 136 |
"integer in range 0..0x10ffff, but got: ", unicodeTok); |
| 137 |
} |
| 138 |
|
| 139 |
if (!line.empty()) { |
| 140 |
throw MSXException("Syntax error, expected \"<msx-char> <unicode>\", " |
| 141 |
"but got: ", origLine); |
| 142 |
} |
| 143 |
|
| 144 |
|
| 145 |
|
| 146 |
if (msx2unicode[*msx] == uint32_t(-1)) { |
| 147 |
msx2unicode[*msx] = *unicode; |
| 148 |
} |
| 149 |
|
| 150 |
unicode2msx.emplace_back(*unicode, *msx); |
| 151 |
} |
| 152 |
|
| 153 |
|
| 154 |
|
| 155 |
|
| 156 |
std::ranges::stable_sort(unicode2msx, {}, &Entry::unicode); |
| 157 |
auto u = std::ranges::unique(unicode2msx, {}, &Entry::unicode); |
| 158 |
unicode2msx.erase(u.begin(), u.end()); |
| 159 |
} |
| 160 |
|
| 161 |
std::string MsxChar2Unicode::msxToUtf8( |
| 162 |
std::span<const uint8_t> msx, const std::function<uint32_t(uint8_t)>& fallback) const |
| 163 |
{ |
| 164 |
std::string utf8; |
| 165 |
utf8.reserve(msx.size()); |
| 166 |
auto out = std::back_inserter(utf8); |
| 167 |
for (auto m : msx) { |
| 168 |
auto u = msx2unicode[m]; |
| 169 |
auto u2 = (u != uint32_t(-1)) ? u : fallback(m); |
| 170 |
out = utf8::unchecked::append(u2, out); |
| 171 |
} |
| 172 |
return utf8; |
| 173 |
} |
| 174 |
|
| 175 |
std::vector<uint8_t> MsxChar2Unicode::utf8ToMsx( |
| 176 |
std::string_view utf8, const std::function<uint8_t(uint32_t)>& fallback) const |
| 177 |
{ |
| 178 |
std::vector<uint8_t> msx; |
| 179 |
auto it = utf8.begin(), et = utf8.end(); |
| 180 |
while (it != et) { |
| 181 |
auto u = utf8::unchecked::next(it); |
| 182 |
auto m = binary_find(unicode2msx, u, {}, &Entry::unicode); |
| 183 |
msx.push_back(m ? m->msx : fallback(u)); |
| 184 |
} |
| 185 |
return msx; |
| 186 |
} |
| 187 |
|
| 188 |
std::string MsxChar2Unicode::msxToUtf8(std::span<const uint8_t> msx, char fallback) const |
| 189 |
{ |
| 190 |
return msxToUtf8(msx, [&](uint32_t) { return fallback; }); |
| 191 |
} |
| 192 |
|
| 193 |
std::vector<uint8_t> MsxChar2Unicode::utf8ToMsx(std::string_view utf8, char fallback) const |
| 194 |
{ |
| 195 |
return utf8ToMsx(utf8, [&](uint8_t) { return fallback; }); |
| 196 |
} |
| 197 |
|
| 198 |
|
| 199 |
} |