diff --git a/cr3qt/CMakeLists.txt b/cr3qt/CMakeLists.txt index 39c741bc52..be8fb6994a 100644 --- a/cr3qt/CMakeLists.txt +++ b/cr3qt/CMakeLists.txt @@ -96,6 +96,7 @@ ENDIF() INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_BINARY_DIR}/cr3qt ) +INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/crengine/fc-lang ) IF (UNIX) FIND_PROGRAM(GZIP_TOOL diff --git a/cr3qt/src/cr3widget.cpp b/cr3qt/src/cr3widget.cpp index e6434050c1..b26842dccc 100644 --- a/cr3qt/src/cr3widget.cpp +++ b/cr3qt/src/cr3widget.cpp @@ -28,6 +28,9 @@ #include #include #include +#include + +#include "fc-lang-cat.h" /// to hide non-qt implementation, place all crengine-related fields here class CR3View::DocViewData @@ -1083,7 +1086,9 @@ bool CR3View::updateSelection( ldomXPointer p ) void CR3View::checkFontLanguageCompatibility() { lString32 fontFace; + lString32 fileName; _data->_props->getString(PROP_FONT_FACE, fontFace); + _docview->getDocProps()->getString(DOC_PROP_FILE_NAME, fileName); lString8 fontFace_u8 = UnicodeToUtf8(fontFace); lString32 langCode = _docview->getLanguage(); lString8 langCode_u8 = UnicodeToUtf8(langCode); @@ -1092,15 +1097,106 @@ void CR3View::checkFontLanguageCompatibility() return; } if (fontFace_u8.length() > 0) { - bool res = fontMan->checkFontLangCompat(fontFace_u8, langCode_u8); - CRLog::debug("Checking font \"%s\" for compatibility with language \"%s\": %d", fontFace_u8.c_str(), langCode_u8.c_str(), res); - if (!res) - { - QMessageBox::warning(this, tr("Warning"), - tr("Font \"%1\" isn't compatible with language \"%2\". Instead will be used fallback font.").arg(fontFace_u8.c_str()).arg(langCode_u8.c_str()), - QMessageBox::Ok); + lString8 fcLang = findCompatibleFcLangCode(langCode_u8); + if (!fcLang.empty()) { + bool res = fontMan->checkFontLangCompat(fontFace_u8, fcLang); + CRLog::debug("Checking font \"%s\" for compatibility with language \"%s\": %d", fontFace_u8.c_str(), langCode_u8.c_str(), res); + if (!res) + { + QMessageBox::warning(this, tr("Warning"), + tr("Font \"%1\" isn't compatible with language \"%2\". Instead will be used fallback font.").arg(fontFace_u8.c_str()).arg(langCode_u8.c_str()), + QMessageBox::Ok); + } + } else { + CRLog::warn("Can't find compatible language code in embedded FontConfig catalog: language=\"%s\", filename=\"%s\"", langCode_u8.c_str(), LCSTR(fileName)); + } + } +} + +lString8 CR3View::findCompatibleFcLangCode(lString8 language) +{ + lString8 langCode; + + lString8 lang_part; + lString8 country_part; + lString8 testLang; + + language = language.lowercase(); + + // Split language and country codes + int pos = language.pos('-'); + if (-1 == pos) + pos = language.pos('_'); + if (pos > 0) { + lang_part = language.substr(0, pos); + if (pos < language.length() - 1) + country_part = language.substr(pos + 1); + else + country_part = ""; + } else { + lang_part = language; + country_part = ""; + } + lang_part = lang_part.lowercase(); + country_part = country_part.lowercase(); + + if (country_part.length() > 0) + testLang = lang_part + "_" + country_part; + else + testLang = lang_part; + // 1. Check if testLang is already language code accepted by FontConfig languages symbols database + if (haveFcLangCode(testLang)) + langCode = testLang; + else { + // TODO: convert three letter language code to two-letter language code + // TODO: convert three letter country code to two-letter country code + // TODO: test with two letter codes + // See android/src: org.coolreader.crengine.Engine.findCompatibleFcLangCode() + if (langCode.empty()) { + // 2. test lang_part + testLang = lang_part; + if (haveFcLangCode(testLang)) + langCode = testLang; } } + if (langCode.empty()) { + QString match_lang; + // 3. Try to find by full language name + QList allLocales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry); + QList::const_iterator it; + for (it = allLocales.begin(); it != allLocales.end(); ++it) { + const QLocale& loc = *it; + QString full_lang = QLocale::languageToString(loc.language()).toLower(); + if (language.compare(full_lang.toLatin1().data()) == 0) { + match_lang = loc.name(); + pos = match_lang.indexOf('_'); + if (pos > 0) + match_lang = match_lang.mid(0, pos); + } + } + if (!match_lang.isEmpty()) { + testLang = lString8(match_lang.toLatin1().data()); + if (haveFcLangCode(testLang)) + langCode = testLang; + } + } + return langCode; +} + +bool CR3View::haveFcLangCode(lString8 langCode) +{ + bool res = false; + if (!langCode.empty()) { + struct fc_lang_catalog* lang_ptr = fc_lang_cat; + for (int i = 0; i < fc_lang_cat_sz; i++) { + if (langCode.compare(lang_ptr->lang_code) == 0) { + res = true; + break; + } + lang_ptr++; + } + } + return res; } void CR3View::mousePressEvent ( QMouseEvent * event ) diff --git a/cr3qt/src/cr3widget.h b/cr3qt/src/cr3widget.h index 8f8a00a42d..1dc9ad5194 100644 --- a/cr3qt/src/cr3widget.h +++ b/cr3qt/src/cr3widget.h @@ -169,6 +169,24 @@ class CR3View : public QWidget, public LVDocViewCallback bool endSelection( ldomXPointer p ); bool updateSelection( ldomXPointer p ); void checkFontLanguageCompatibility(); + /** + * Finds the corresponding language code in embedded FontConfig language orthography catalog. + * + * @param language language code in free form: ISO 639-1, ISO 639-2 or full name of the language in English. Also allowed concatenation of country code in ISO 3166-1 alpha-2 or ISO 3166-1 alpha-3. + * @return language code in the FontConfig language orthography catalog if it's found, null - otherwise. + * + * If a country code in any form is added to the language, but the record with the country code is not found - it is simply ignored and the search continues without a country code. + */ + lString8 findCompatibleFcLangCode(lString8 language); + /** + * Test if in embedded FontConfig language orthography catalog have record with language code langCode. + * + * @param langCode language code + * @return true if record with langCode found, false - otherwise. + * + * Language code compared as is without any modifications. + */ + bool haveFcLangCode(lString8 langCode); DocViewData * _data; // to hide non-qt implementation LVDocView * _docview; diff --git a/crengine/include/lvstring.h b/crengine/include/lvstring.h index 11e5ef174f..732927059b 100644 --- a/crengine/include/lvstring.h +++ b/crengine/include/lvstring.h @@ -120,6 +120,10 @@ int lStr_cmp(const lChar32 * str1, const lChar16 * str2); /// strcmp for lChar8 int lStr_cmp(const lChar8 * str1, const lChar8 * str2); /// convert string to uppercase +void lStr_uppercase( lChar8 * str, int len ); +/// convert string to lowercase +void lStr_lowercase( lChar8 * str, int len ); +/// convert string to uppercase void lStr_uppercase( lChar32 * str, int len ); /// convert string to lowercase void lStr_lowercase( lChar32 * str, int len ); @@ -409,6 +413,10 @@ class lString8 lString8 & replace(size_type p0, size_type n0, const lString8 & str, size_type offset, size_type count); /// replace fragment with repeated character lString8 & replace(size_type p0, size_type n0, size_type count, value_type ch); + /// make string uppercase + lString8 & uppercase(); + /// make string lowercase + lString8 & lowercase(); /// compare with another string int compare(const lString8& str) const { return lStr_cmp(pchunk->buf8, str.pchunk->buf8); } /// compare part of string with another string @@ -421,6 +429,10 @@ class lString8 int compare(size_type p0, size_type n0, const value_type *s) const; /// compare part of string with C-string fragment int compare(size_type p0, size_type n0, const value_type *s, size_type pos) const; + /// find position of char inside string, -1 if not found + int pos(lChar8 ch) const; + /// find position of char inside string starting from specified position, -1 if not found + int pos(lChar8 ch, int start) const; /// find position of substring inside string, -1 if not found int pos(const lString8 & subStr) const; /// find position of substring inside string, -1 if not found diff --git a/crengine/src/lvstring.cpp b/crengine/src/lvstring.cpp index b87aa668f5..43f1da48dc 100644 --- a/crengine/src/lvstring.cpp +++ b/crengine/src/lvstring.cpp @@ -2735,6 +2735,32 @@ lString8 lString8::substr(size_type pos, size_type n) const return lString8( pchunk->buf8+pos, n ); } +int lString8::pos(lChar8 ch) const +{ + for (int i = 0; i < length(); i++) + { + if (pchunk->buf8[i] == ch) + { + return i; + } + } + return -1; +} + +int lString8::pos(lChar8 ch, int start) const +{ + if (length() - start < 1) + return -1; + for (int i = start; i < length(); i++) + { + if (pchunk->buf8[i] == ch) + { + return i; + } + } + return -1; +} + int lString8::pos(const lString8 & subStr) const { if (subStr.length()>length()) @@ -3330,6 +3356,17 @@ bool lvUnicodeIsAlpha( lChar32 ch ) return false; } +lString8 & lString8::uppercase() +{ + lStr_uppercase( modify(), length() ); + return *this; +} + +lString8 & lString8::lowercase() +{ + lStr_lowercase( modify(), length() ); + return *this; +} lString32 & lString32::uppercase() { @@ -3355,6 +3392,30 @@ lString32 & lString32::fullWidthChars() return *this; } +void lStr_uppercase( lChar8 * str, int len ) +{ + for ( int i=0; i='a' && ch<='z' ) { + str[i] = ch - 0x20; + } else if ( ch>=0xE0 && ch<=0xFF ) { + str[i] = ch - 0x20; + } + } +} + +void lStr_lowercase( lChar8 * str, int len ) +{ + for ( int i=0; i='A' && ch<='Z' ) { + str[i] = ch + 0x20; + } else if ( ch>=0xC0 && ch<=0xDF ) { + str[i] = ch + 0x20; + } + } +} + void lStr_uppercase( lChar32 * str, int len ) { for ( int i=0; i