/* * Copyright 2010-2016 OpenXcom Developers. * * This file is part of OpenXcom. * * OpenXcom is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenXcom is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenXcom. If not, see . */ #include "Unicode.h" #include #include #include #include "Logger.h" #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX #endif #define WIN32_LEAN_AND_MEAN #include #include #else #include #include #include #include #endif namespace OpenXcom { namespace Unicode { std::locale utf8; /** * Store a UTF-8 locale to use when dealing with character conversions. * Windows doesn't have a UTF-8 locale so we just use its APIs directly. */ void getUtf8Locale() { std::string loc; #ifndef _WIN32 // Find any UTF-8 locale FILE *fp = popen("locale -a", "r"); if (fp != NULL) { char buf[50]; while (fgets(buf, sizeof(buf), fp) != NULL) { if (strstr(buf, ".utf8") != NULL || strstr(buf, ".UTF-8") != NULL) { // Trim newline size_t end = strlen(buf) - 1; if (buf[end] == '\n') buf[end] = '\0'; loc = buf; break; } } pclose(fp); } #endif // Try a UTF-8 locale (or default if none was found) try { utf8 = std::locale(loc.c_str()); } catch (const std::runtime_error &) { // Well we're stuck with the C locale, hope for the best } Log(LOG_INFO) << "Detected locale: " << utf8.name(); } /** * Takes a Unicode 32-bit string and converts it * to a 8-bit string encoded in UTF-8. * Used for rendering text. * @note Adapted from https://stackoverflow.com/a/148766/2683561 * @param src UTF-8 string. * @return Unicode string. */ UString convUtf8ToUtf32(const std::string &src) { if (src.empty()) return UString(); UString out; out.reserve(src.size()); UCode codepoint = 0; for (std::string::const_iterator i = src.begin(); i != src.end();) { unsigned char ch = static_cast(*i); if (ch <= 0x7f) codepoint = ch; else if (ch <= 0xbf) codepoint = (codepoint << 6) | (ch & 0x3f); else if (ch <= 0xdf) codepoint = ch & 0x1f; else if (ch <= 0xef) codepoint = ch & 0x0f; else codepoint = ch & 0x07; ++i; if (i == src.end() || ((*i & 0xc0) != 0x80 && codepoint <= 0x10ffff)) { out.append(1, codepoint); } } return out; } /** * Takes a Unicode 32-bit string and converts it * to a 8-bit string encoded in UTF-8. * Used for rendering text. * @note Adapted from https://stackoverflow.com/a/148766/2683561 * @param src Unicode string. * @return UTF-8 string. */ std::string convUtf32ToUtf8(const UString &src) { if (src.empty()) return std::string(); std::string out; out.reserve(src.size()); for (UString::const_iterator i = src.begin(); i != src.end(); ++i) { UCode codepoint = *i; if (codepoint <= 0x7f) { out.append(1, static_cast(codepoint)); } else if (codepoint <= 0x7ff) { out.append(1, static_cast(0xc0 | ((codepoint >> 6) & 0x1f))); out.append(1, static_cast(0x80 | (codepoint & 0x3f))); } else if (codepoint <= 0xffff) { out.append(1, static_cast(0xe0 | ((codepoint >> 12) & 0x0f))); out.append(1, static_cast(0x80 | ((codepoint >> 6) & 0x3f))); out.append(1, static_cast(0x80 | (codepoint & 0x3f))); } else { out.append(1, static_cast(0xf0 | ((codepoint >> 18) & 0x07))); out.append(1, static_cast(0x80 | ((codepoint >> 12) & 0x3f))); out.append(1, static_cast(0x80 | ((codepoint >> 6) & 0x3f))); out.append(1, static_cast(0x80 | (codepoint & 0x3f))); } } return out; } /** * Takes a wide-character string and converts it to a * multibyte 8-bit string in a given encoding. * Used for Win32 APIs. * @param src Wide-character string. * @param cp Codepage of the destination string. * @return Multibyte string. */ std::string convWcToMb(const std::wstring &src, unsigned int cp) { if (src.empty()) return std::string(); #ifdef _WIN32 int size = WideCharToMultiByte(cp, 0, &src[0], (int)src.size(), NULL, 0, NULL, NULL); std::string str(size, 0); WideCharToMultiByte(cp, 0, &src[0], (int)src.size(), &str[0], size, NULL, NULL); return str; #else (void)cp; assert(sizeof(wchar_t) == sizeof(UCode)); const UString *ustr = reinterpret_cast(&src); return convUtf32ToUtf8(*ustr); #endif } /** * Takes a multibyte 8-bit string in a given encoding * and converts it to a wide-character string. * Used for Win32 APIs. * @param src Multibyte string. * @param cp Codepage of the source string. * @return Wide-character string. */ std::wstring convMbToWc(const std::string &src, unsigned int cp) { if (src.empty()) return std::wstring(); #ifdef _WIN32 int size = MultiByteToWideChar(cp, 0, &src[0], (int)src.size(), NULL, 0); std::wstring wstr(size, 0); MultiByteToWideChar(cp, 0, &src[0], (int)src.size(), &wstr[0], size); return wstr; #else (void)cp; assert(sizeof(wchar_t) == sizeof(UCode)); UString ustr = convUtf8ToUtf32(src); const std::wstring *wstr = reinterpret_cast(&ustr); return *wstr; #endif } /** * Takes a filesystem path and converts it to a UTF-8 string. * On Windows the C paths use the local ANSI codepage. * Used for SDL APIs. * @param src Filesystem path. * @return UTF-8 string. */ std::string convPathToUtf8(const std::string &src) { #ifdef _WIN32 return convWcToMb(convMbToWc(src, CP_ACP), CP_UTF8); #else return src; #endif } /** * Takes a UTF-8 string and converts it to a filesystem path. * On Windows the C paths use the local ANSI codepage. * Used for SDL APIs. * @param src UTF-8 string. * @return Filesystem path. */ std::string convUtf8ToPath(const std::string &src) { #ifdef _WIN32 return convWcToMb(convMbToWc(src, CP_UTF8), CP_ACP); #else return src; #endif } /** * Compares two UTF-8 strings using natural human ordering. * @param a String A. * @param b String B. * @return String A comes before String B. */ bool naturalCompare(const std::string &a, const std::string &b) { #ifdef _WIN32 typedef int (WINAPI *WinStrCmp)(PCWSTR, PCWSTR); WinStrCmp pWinStrCmp = (WinStrCmp)GetProcAddress(GetModuleHandleA("shlwapi.dll"), "StrCmpLogicalW"); if (pWinStrCmp) { std::wstring wa = convMbToWc(a, CP_UTF8); std::wstring wb = convMbToWc(b, CP_UTF8); return (pWinStrCmp(wa.c_str(), wb.c_str()) < 0); } else #endif { // fallback to lexical sort return caseCompare(a, b); } } /** * Compares two UTF-8 strings ignoring case. * @param a String A. * @param b String B. * @return String A comes before String B. */ bool caseCompare(const std::string &a, const std::string &b) { #ifdef _WIN32 std::wstring wa = convMbToWc(a, CP_UTF8); std::wstring wb = convMbToWc(b, CP_UTF8); return (StrCmpIW(wa.c_str(), wb.c_str()) < 0); #else return (std::use_facet< std::collate >(utf8).compare(&a[0], &a[0] + a.size(), &b[0], &b[0] + b.size()) < 0); #endif } /** * Searches for a substring in another string ignoring case. * @param haystack String to search. * @param needle String to find. * @return True if the needle is in the haystack. */ bool caseFind(const std::string &haystack, const std::string &needle) { #ifdef _WIN32 std::wstring wa = convMbToWc(haystack, CP_UTF8); std::wstring wb = convMbToWc(needle, CP_UTF8); return (StrStrIW(wa.c_str(), wb.c_str()) != NULL); #else std::wstring wa = convMbToWc(haystack, 0); std::wstring wb = convMbToWc(needle, 0); std::use_facet< std::ctype >(utf8).toupper(&wa[0], &wa[0] + wa.size()); std::use_facet< std::ctype >(utf8).toupper(&wb[0], &wb[0] + wb.size()); return (wa.find(wb) != std::wstring::npos); #endif } /** * Uppercases a UTF-8 string, modified in place. * Used for case-insensitive comparisons. * @param s Source string. */ void upperCase(std::string &s) { if (s.empty()) return; #ifdef _WIN32 std::wstring ws = convMbToWc(s, CP_UTF8); CharUpperW(&ws[0]); s = convWcToMb(ws, CP_UTF8); #else std::wstring ws = convMbToWc(s, 0); std::use_facet< std::ctype >(utf8).toupper(&ws[0], &ws[0] + ws.size()); s = convWcToMb(ws, 0); #endif } /** * Lowercases a UTF-8 string, modified in place. * Used for case-insensitive comparisons. * @param s Source string. */ void lowerCase(std::string &s) { if (s.empty()) return; #ifdef _WIN32 std::wstring ws = convMbToWc(s, CP_UTF8); CharLowerW(&ws[0]); s = convWcToMb(ws, CP_UTF8); #else std::wstring ws = convMbToWc(s, 0); std::use_facet< std::ctype >(utf8).tolower(&ws[0], &ws[0] + ws.size()); s = convWcToMb(ws, 0); #endif } /** * Replaces every instance of a substring. * @param str The string to modify. * @param find The substring to find. * @param replace The substring to replace it with. */ void replace(std::string &str, const std::string &find, const std::string &replace) { for (size_t i = str.find(find); i != std::string::npos; i = str.find(find, i + replace.length())) { str.replace(i, find.length(), replace); } } /** * Takes an integer value and formats it as number with separators (spacing the thousands). * @param value The value. * @param currency Currency symbol. * @return The formatted string. */ std::string formatNumber(int64_t value, const std::string ¤cy) { const std::string thousands_sep = "\xC2\xA0"; // TOK_NBSP bool negative = (value < 0); std::ostringstream ss; ss << (negative ? -value : value); std::string s = ss.str(); size_t spacer = s.size() - 3; while (spacer > 0 && spacer < s.size()) { s.insert(spacer, thousands_sep); spacer -= 3; } if (!currency.empty()) { s.insert(0, currency); } if (negative) { s.insert(0, "-"); } return s; } /** * Takes an integer value and formats it as currency, * spacing the thousands and adding a $ sign to the front. * @param funds The funding value. * @return The formatted string. */ std::string formatFunding(int64_t funds) { return formatNumber(funds, "$"); } /** * Takes an integer value and formats it as percentage, * adding a % sign. * @param value The percentage value. * @return The formatted string. */ std::string formatPercentage(int value) { std::ostringstream ss; ss << value << "%"; return ss.str(); } } }