/*
Copyright © 2015-2021 Amebis
This file is part of ZRCola.
ZRCola 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.
ZRCola 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 ZRCola. If not, see .
*/
#include "pch.h"
#define FONT_MATCH_WIDTH 512 // must be a multiple of 8
#define FONT_MATCH_HEIGHT 512
#define FONT_MATCH_THRESHOLD 8e-2
using namespace std;
using namespace stdex;
using namespace winstd;
///
/// (destination character rank, (source character rank, source character)) data holder
///
class com_translation
{
public:
int rank_src; ///< Source sequence rank
int rank_dst; ///< Destination character rank
string norm; ///< Normalization footprint
inline com_translation() :
rank_src(0),
rank_dst(0)
{
}
inline com_translation(int _rank_src, int _rank_dst) :
rank_src(_rank_src),
rank_dst(_rank_dst)
{
}
inline com_translation(int _rank_src, int _rank_dst, const char *_norm) :
rank_src(_rank_src),
rank_dst(_rank_dst),
norm (_norm )
{
}
inline com_translation(int _rank_src, int _rank_dst, string &&_norm) :
rank_src( _rank_src ),
rank_dst( _rank_dst ),
norm (std::move(_norm ))
{
}
inline com_translation(const com_translation &other) :
rank_src(other.rank_src),
rank_dst(other.rank_dst),
norm (other.norm )
{
}
inline com_translation(com_translation &&other) noexcept :
rank_src( other.rank_src ),
rank_dst( other.rank_dst ),
norm (std::move(other.norm ))
{
}
inline com_translation& operator=(const com_translation &other)
{
if (this != std::addressof(other)) {
rank_src = other.rank_src;
rank_dst = other.rank_dst;
norm = other.norm ;
}
return *this;
}
inline com_translation& operator=(com_translation &&other) noexcept
{
if (this != std::addressof(other)) {
rank_src = other.rank_src ;
rank_dst = other.rank_dst ;
norm = std::move(other.norm );
}
return *this;
}
inline bool operator==(_In_ const com_translation& other) const
{
return
rank_src == other.rank_src &&
rank_dst == other.rank_dst &&
norm == other.norm;
}
inline bool operator!=(_In_ const com_translation &other) const
{
return !operator==(other);
}
inline bool operator<(_In_ const com_translation& other) const
{
if (rank_src < other.rank_src) return true;
else if (rank_src > other.rank_src) return false;
else if (rank_dst < other.rank_dst) return true;
else if (rank_dst > other.rank_dst) return false;
else if (norm < other.norm ) return true;
else return false;
}
inline bool operator<=(_In_ const com_translation &other) const
{
return !operator>(other);
}
inline bool operator>(_In_ const com_translation &other) const
{
return other.operator<(*this);
}
inline bool operator>=(_In_ const com_translation &other) const
{
return !operator<(other);
}
};
typedef map > translation_db;
typedef map normperm_db;
static set translate_inv(_In_ const translation_db &db_trans, _In_ const normperm_db &db_np, _In_z_ const wchar_t *str, _Inout_ set &path);
static inline set permutate_and_translate_inv(_In_ const translation_db &db_trans, _In_ const normperm_db &db_np, _In_z_ const wchar_t *str, _In_z_ const char *norm, _Inout_ set &path);
static set translate_inv(_In_ const translation_db &db_trans, _In_ const normperm_db &db_np, _In_z_ const wchar_t *str, _Inout_ set &path)
{
set res;
if (!*str) {
// Empty string results in empty inverse translation.
res.insert(ZRCola::DBSource::charseq(0, L""));
return res;
}
// Prepare inverse translate of the remainder string (without the first character).
auto res_rem = translate_inv(db_trans, db_np, str + 1, path);
if (res_rem.empty())
return res;
// See if first character is inverse translatable.
translation_db::key_type chr(1, *str);
auto const hit_trans = db_trans.find(chr);
if (hit_trans != db_trans.end()) {
// Current character is inverse translatable.
// Add the current character to the path before recursing.
auto hit_path = path.insert(chr);
if (!hit_path.second) {
// Path already contains this character: Cycle detected!
return res;
}
// Iterate all possible character inverse translations and combine them with the remainder string inverse translations.
for (auto d = hit_trans->second.cbegin(), d_end = hit_trans->second.cend(); d != d_end; ++d) {
auto res_chr = d->second.norm.empty() ?
translate_inv(db_trans, db_np, d->first.c_str(), path) :
permutate_and_translate_inv(db_trans, db_np, d->first.c_str(), d->second.norm.c_str(), path);
if (!res_chr.empty()) {
for (auto r_chr = res_chr.cbegin(), r_chr_end = res_chr.cend(); r_chr != r_chr_end; ++r_chr) {
for (auto r_rem = res_rem.cbegin(), r_rem_end = res_rem.cend(); r_rem != r_rem_end; ++r_rem)
res.insert(ZRCola::DBSource::charseq(d->second.rank_src + r_chr->rank + r_rem->rank, r_chr->str + r_rem->str));
}
} else {
// Cycle detected. Do not continue inverse translation.
for (auto r_rem = res_rem.cbegin(), r_end = res_rem.cend(); r_rem != r_end; ++r_rem)
res.insert(ZRCola::DBSource::charseq(r_rem->rank, chr + r_rem->str));
}
}
// Remove the current character from the path.
path.erase(hit_path.first);
} else {
// First character is non-inverse translatable. Combine it with the remainder(s).
for (auto r_rem = res_rem.cbegin(), r_end = res_rem.cend(); r_rem != r_end; ++r_rem)
res.insert(ZRCola::DBSource::charseq(r_rem->rank, chr + r_rem->str));
}
return res;
}
static inline set permutate_and_translate_inv(_In_ const translation_db &db_trans, _In_ const normperm_db &db_np, _In_z_ const wchar_t *str, _In_z_ const char *norm, _Inout_ set &path)
{
// Primary permutation inverse translate.
auto res = translate_inv(db_trans, db_np, str, path);
// Secondary permutation(s).
auto const hit_np = db_np.find(norm);
if (hit_np != db_np.end()) {
for (auto perm = hit_np->second.cbegin(), perm_end = hit_np->second.cend(); perm != perm_end; ++perm) {
// Prepare permutated string.
translation_db::mapped_type::key_type str_perm;
for (auto idx = perm->cbegin(), idx_end = perm->cend(); idx != idx_end; ++idx)
str_perm += str[*idx];
// Secondary permutation inverse translate.
auto res_perm = translate_inv(db_trans, db_np, str_perm.c_str(), path);
for (auto r = res_perm.cbegin(), r_end = res_perm.cend(); r != r_end; ++r)
res.insert(ZRCola::DBSource::charseq(r->rank + 1, r->str));
}
}
return res;
}
static bool contains_pua(_In_ const wstring &str)
{
for (auto p = str.c_str(), p_end = str.c_str() + str.size(); p < p_end; p++)
if (L'\ue000' <= *p && *p <= L'\uf8ff')
return true;
return false;
}
static void replace_all(_Inout_ wstring &str, _In_ const wstring &from, _In_ const wstring &to)
{
size_t start_pos = 0;
while ((start_pos = str.find(from, start_pos)) != wstring::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length();
}
}
static double compare_bitmaps(
_In_count_c_(FONT_MATCH_WIDTH * FONT_MATCH_HEIGHT / 8) const unsigned char *bits_orig,
_In_count_c_(FONT_MATCH_WIDTH * FONT_MATCH_HEIGHT / 8) const unsigned char *bits)
{
#define B2(n) n, n + 1, n + 1, n + 2
#define B4(n) B2(n), B2(n + 1), B2(n + 1), B2(n + 2)
#define B6(n) B4(n), B4(n + 1), B4(n + 1), B4(n + 2)
static const unsigned char number_of_bits[256] = { B6(0), B6(1), B6(1), B6(2) };
#undef B2
#undef B4
#undef B6
// Set divisors to 1 to prevent divide-by-zero.
size_t b_orig = 1, b = 1, x = 0;
for (size_t i = 0; i < FONT_MATCH_WIDTH * FONT_MATCH_HEIGHT / 8; ++i) {
b_orig += number_of_bits[bits_orig[i]];
b += number_of_bits[bits [i]];
x += number_of_bits[bits_orig[i] ^ bits[i]];
}
return (double)x/b_orig * (double)x/b;
}
static string make_unicode(_In_ const wstring &str)
{
string out;
for (size_t i = 0, n = str.length(); i < n; i++)
out += string_printf(i ? "+%04X" : "%04X", str[i]);
return out;
}
///
/// Main function
///
int _tmain(int argc, _TCHAR *argv[])
{
wxApp::CheckBuildOptions(WX_BUILD_OPTIONS_SIGNATURE, "program");
// Initialize wxWidgets.
wxInitializer initializer;
if (!initializer) {
_ftprintf(stderr, wxT("Failed to initialize the wxWidgets library, aborting.\n"));
return -1;
}
// Initialize configuration.
wxConfigBase *cfgPrev = wxConfigBase::Set(new wxConfig(wxT(PRODUCT_CFG_APPLICATION), wxT(PRODUCT_CFG_VENDOR)));
if (cfgPrev) wxDELETE(cfgPrev);
// Initialize locale.
wxLocale locale;
if (wxInitializeLocale(locale))
wxVERIFY(locale.AddCatalog(wxT("ZRColaCompile")));
// Parse command line.
static const wxCmdLineEntryDesc cmdLineDesc[] =
{
{ wxCMD_LINE_SWITCH, "h" , "help" , _("Show this help message"), wxCMD_LINE_VAL_NONE , wxCMD_LINE_OPTION_HELP },
{ wxCMD_LINE_PARAM , NULL, NULL , _("" ), wxCMD_LINE_VAL_STRING, wxCMD_LINE_OPTION_MANDATORY },
{ wxCMD_LINE_PARAM , NULL, NULL , _("