diff --git a/android/app/build.gradle b/android/app/build.gradle
index ad7cfecc98..b92bc5c2b9 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -115,9 +115,6 @@ android {
// but continue the build even when errors are found:
abortOnError false
}
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_7
- }
// https://developer.android.com/about/versions/marshmallow/android-6.0-changes#behavior-apache-http-client
useLibrary 'org.apache.http.legacy'
@@ -125,7 +122,8 @@ android {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
- //testImplementation 'junit:junit:4.12'
- //androidTestImplementation 'com.android.support.test:runner:1.0.2'
- //androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support:support-annotations:28.0.0'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
diff --git a/android/app/src/androidTest/AndroidManifest.xml b/android/app/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000000..9ff0d4b8d8
--- /dev/null
+++ b/android/app/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/android/app/src/androidTest/java/org/coolreader/crengine/FindFcLangCodeTest.java b/android/app/src/androidTest/java/org/coolreader/crengine/FindFcLangCodeTest.java
new file mode 100644
index 0000000000..90c4ecc443
--- /dev/null
+++ b/android/app/src/androidTest/java/org/coolreader/crengine/FindFcLangCodeTest.java
@@ -0,0 +1,126 @@
+package org.coolreader.crengine;
+
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class FindFcLangCodeTest {
+ @Test
+ public void testFindCompatibleFcLangCode()
+ {
+ // English codes variants
+ String langCode = Engine.findCompatibleFcLangCode("English");
+ assertEquals("en", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("english");
+ assertEquals("en", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("eng");
+ assertEquals("en", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("Eng");
+ assertEquals("en", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("en-US");
+ assertEquals("en", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("eng-US");
+ assertEquals("en", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("en-USA");
+ assertEquals("en", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("eng-USA");
+ assertEquals("en", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("en-GB");
+ assertEquals("en", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("eng-GB");
+ assertEquals("en", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("en-GBR");
+ assertEquals("en", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("eng-GBR");
+ assertEquals("en", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("en_gb");
+ assertEquals("en", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("en");
+ assertEquals("en", langCode);
+
+ // Russian codes variants
+ langCode = Engine.findCompatibleFcLangCode("Russian");
+ assertEquals("ru", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("russian");
+ assertEquals("ru", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("Rus");
+ assertEquals("ru", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("rus");
+ assertEquals("ru", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("ru-RU");
+ assertEquals("ru", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("rus-RU");
+ assertEquals("ru", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("ru-RUS");
+ assertEquals("ru", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("rus-RUS");
+ assertEquals("ru", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("ru_ru");
+ assertEquals("ru", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("ru");
+ assertEquals("ru", langCode);
+
+ // Chinese variants
+ langCode = Engine.findCompatibleFcLangCode("zh-CN");
+ assertEquals("zh_cn", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("zho-CN");
+ assertEquals("zh_cn", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("zh-CHN");
+ assertEquals("zh_cn", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("zho-CHN");
+ assertEquals("zh_cn", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("zh-HK");
+ assertEquals("zh_hk", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("zho-HK");
+ assertEquals("zh_hk", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("zh-HKG");
+ assertEquals("zh_hk", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("zho-HKG");
+ assertEquals("zh_hk", langCode);
+
+ langCode = Engine.findCompatibleFcLangCode("zh_hk");
+ assertEquals("zh_hk", langCode);
+
+ // invalid variant
+ langCode = Engine.findCompatibleFcLangCode("abcd");
+ assertNull(langCode);
+ }
+}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index b6bec4a94c..111144ce2a 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -13,8 +13,6 @@
android:anyDensity="true"
/>
-
-
diff --git a/android/gradle.properties b/android/gradle.properties
index aac7c9b461..14a6522047 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -15,3 +15,7 @@ org.gradle.jvmargs=-Xmx1536m
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
+
+# When set to all, summary or none, Gradle will use different warning type display.
+# See Command-line logging options for details.
+org.gradle.warning.mode=all
diff --git a/android/jni/cr3engine.cpp b/android/jni/cr3engine.cpp
index 8141e4c028..3791c86f8c 100644
--- a/android/jni/cr3engine.cpp
+++ b/android/jni/cr3engine.cpp
@@ -24,6 +24,8 @@
#include <../../crengine/include/fb2def.h>
+#include "fc-lang-cat.h"
+
#define XS_IMPLEMENT_SCHEME 1
#include <../../crengine/include/fb2def.h>
#include
@@ -657,6 +659,33 @@ JNIEXPORT jboolean JNICALL Java_org_coolreader_crengine_Engine_setCacheDirectory
return res ? JNI_TRUE : JNI_FALSE;
}
+/*
+ * Class: org_coolreader_crengine_Engine
+ * Method: haveFcLangCodeInternal
+ * Signature: (Ljava/lang/String;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_coolreader_crengine_Engine_haveFcLangCodeInternal
+ (JNIEnv *env, jclass cls, jstring langCode)
+{
+ jboolean res = JNI_FALSE;
+ const char* langCode_ptr = env->GetStringUTFChars(langCode, 0);
+ if (langCode_ptr) {
+ struct fc_lang_catalog* lang_ptr = fc_lang_cat;
+ for (int i = 0; i < fc_lang_cat_sz; i++)
+ {
+ if (strcmp(lang_ptr->lang_code, langCode_ptr) == 0)
+ {
+ res = JNI_TRUE;
+ break;
+ }
+ lang_ptr++;
+ }
+ env->ReleaseStringUTFChars(langCode, langCode_ptr);
+ }
+ return res;
+}
+
+
/*
* Class: org_coolreader_crengine_Engine
* Method: checkFontLanguageCompatibilityInternal
diff --git a/android/jni/org_coolreader_crengine_Engine.h b/android/jni/org_coolreader_crengine_Engine.h
index 44d1c1dd9c..c294706fda 100644
--- a/android/jni/org_coolreader_crengine_Engine.h
+++ b/android/jni/org_coolreader_crengine_Engine.h
@@ -107,6 +107,14 @@ JNIEXPORT void JNICALL Java_org_coolreader_crengine_Engine_drawBookCoverInternal
JNIEXPORT void JNICALL Java_org_coolreader_crengine_Engine_suspendLongOperationInternal
(JNIEnv *, jclass);
+/*
+ * Class: org_coolreader_crengine_Engine
+ * Method: haveFcLangCodeInternal
+ * Signature: (Ljava/lang/String;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_coolreader_crengine_Engine_haveFcLangCodeInternal
+ (JNIEnv *, jclass, jstring);
+
/*
* Class: org_coolreader_crengine_Engine
* Method: checkFontLanguageCompatibilityInternal
diff --git a/android/res/values-ru/strings.xml b/android/res/values-ru/strings.xml
index bb803b70a0..0518b8b9a9 100644
--- a/android/res/values-ru/strings.xml
+++ b/android/res/values-ru/strings.xml
@@ -591,7 +591,7 @@
Контрастная черная
Ошибка открытия документа \"%s\"
Каталог с данными приложения \"%s\" был удален другой программой!\nВозможно какой-то оптимизатор дискового пространства?\nК сожалению, все настройки утеряны.
- "Шрифт \"%s\" не совместим с языком \"%s\". Вместо него будет задействован дополнительный шрифт."
+ "Предупреждение: шрифт \"%s\" не совместим с языком \"%s\". Вместо него будет задействован дополнительный шрифт."
Набор символов различных писменностей от Fontconfig:
https://www.fontconfig.org/
diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml
index e730216da4..8c0ff1b489 100644
--- a/android/res/values/strings.xml
+++ b/android/res/values/strings.xml
@@ -617,7 +617,7 @@
"20 pages (track)"
Error while opening document \"%s\"
Application data directory \"%s\" has been removed by other app.\nPossibly it was some kind of disc space optimizer.\nUnfortunately all the settings were lost.
- "Font \"%s\" isn't compatible with language \"%s\". Instead will be used fallback font."
+ "Notice: font \"%s\" isn't compatible with language \"%s\". Instead will be used fallback font."
"Languages character set database by Fontconfig:
https://www.fontconfig.org/ "
diff --git a/android/src/org/coolreader/crengine/Engine.java b/android/src/org/coolreader/crengine/Engine.java
index 5f029c08e4..1cf94806d8 100644
--- a/android/src/org/coolreader/crengine/Engine.java
+++ b/android/src/org/coolreader/crengine/Engine.java
@@ -15,6 +15,7 @@
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
+import java.util.MissingResourceException;
import java.util.zip.ZipEntry;
import org.coolreader.R;
@@ -593,9 +594,24 @@ public void initAgain() {
private native static void suspendLongOperationInternal(); // cancel current long operation in engine thread (swapping to cache file) -- call it from GUI thread
+ /**
+ * 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.
+ */
+ private native static boolean haveFcLangCodeInternal(String langCode);
+
+ /**
+ * Check the font for compatibility with the specified language.
+ * @param fontFace font face to check.
+ * @param langCode language code in embedded FontConfig language orthography catalog.
+ * @return true if font compatible with language, false - otherwise.
+ */
private native static boolean checkFontLanguageCompatibilityInternal(String fontFace, String langCode);
-
- public static void suspendLongOperation() {
+
+ public static void suspendLongOperation() {
suspendLongOperationInternal();
}
@@ -604,6 +620,137 @@ public synchronized static boolean checkFontLanguageCompatibility(String fontFac
return checkFontLanguageCompatibilityInternal(fontFace, langCode);
}
+ /**
+ * 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.
+ */
+ public static String findCompatibleFcLangCode(String language)
+ {
+ String langCode = null;
+
+ String lang_part;
+ String country_part;
+ String testLang;
+
+ // Split language and country codes
+ int pos = language.indexOf('-');
+ if (-1 == pos)
+ pos = language.indexOf('_');
+ if (pos > 0) {
+ lang_part = language.substring(0, pos);
+ if (pos < language.length() - 1)
+ country_part = language.substring(pos + 1);
+ else
+ country_part = "";
+ } else {
+ lang_part = language;
+ country_part = "";
+ }
+ lang_part = lang_part.toLowerCase();
+ country_part = country_part.toLowerCase();
+
+ 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 (haveFcLangCodeInternal(testLang))
+ langCode = testLang;
+ else {
+ // Check if lang_part is the three-letter abbreviation: ISO 639-2 or ISO 639-3
+ // and if country_part code is the three-letter country code: ISO 3366-1 alpha 3
+ // Then convert them to two-letter code and test
+ String lang_2l = null;
+ String country_2l = null;
+ int found = 0;
+ for (Locale loc : Locale.getAvailableLocales()) {
+ try {
+ if (lang_part.equals(loc.getISO3Language())) {
+ lang_2l = loc.getLanguage();
+ found |= 1;
+ }
+ } catch (MissingResourceException e) {
+ // three-letter language abbreviation is not available for this locale
+ // just ignore this exception
+ }
+ if (country_part.length() > 0) {
+ try {
+ if (country_part.equals(loc.getISO3Country().toLowerCase())) {
+ country_2l = loc.getCountry().toLowerCase();
+ found |= 2;
+ }
+ } catch (MissingResourceException e) {
+ // the three-letter country abbreviation is not available for this locale
+ // just ignore this exception
+ }
+ if (3 == found)
+ break;
+ } else if (1 == found)
+ break;
+ }
+ if (country_part.length() > 0) {
+ // 2. test lang_2l + country_part
+ if (null != lang_2l) {
+ testLang = lang_2l + "_" + country_part;
+ if (haveFcLangCodeInternal(testLang))
+ langCode = testLang;
+ }
+ if (null == langCode && null != country_2l) {
+ // 3. test lang_part + country_2l
+ testLang = lang_part + "_" + country_2l;
+ if (haveFcLangCodeInternal(testLang))
+ langCode = testLang;
+ }
+ if (null == langCode && null != country_2l && null != lang_2l) {
+ // 4. test lang_2l + country_2l
+ testLang = lang_2l + "_" + country_2l;
+ if (haveFcLangCodeInternal(testLang))
+ langCode = testLang;
+ }
+ }
+ if (null == langCode)
+ {
+ if (null != lang_2l) {
+ // 5. test lang_2l
+ // if two-letter country code not found or county code omitted
+ // but found two-letter language code
+ testLang = lang_2l;
+ if (haveFcLangCodeInternal(testLang))
+ langCode = testLang;
+ } else {
+ // 6. test lang_part
+ testLang = lang_part;
+ if (haveFcLangCodeInternal(testLang))
+ langCode = testLang;
+ }
+ }
+ }
+ if (null == langCode) {
+ Locale locale = null;
+ // 7. Try to find by full language name
+ for (Locale loc : Locale.getAvailableLocales()) {
+ if (language.equalsIgnoreCase(loc.getDisplayLanguage(Locale.ENGLISH))) {
+ locale = loc;
+ break;
+ }
+ }
+ if (null != locale) {
+ testLang = locale.getISO3Language();
+ if (haveFcLangCodeInternal(testLang))
+ langCode = testLang;
+ else {
+ testLang = locale.getLanguage(); // two-letter code
+ if (haveFcLangCodeInternal(testLang))
+ langCode = testLang;
+ }
+ }
+ }
+ return langCode;
+ }
+
/**
* Checks whether specified directlry or file is symbolic link.
* (thread-safe)
diff --git a/android/src/org/coolreader/crengine/ReaderView.java b/android/src/org/coolreader/crengine/ReaderView.java
index c2222c33f4..d9dad09431 100644
--- a/android/src/org/coolreader/crengine/ReaderView.java
+++ b/android/src/org/coolreader/crengine/ReaderView.java
@@ -2579,9 +2579,12 @@ private void applySettings(Properties props)
FileInfo fileInfo = mBookInfo.getFileInfo();
final String bookLanguage = fileInfo.getLanguage();
final String fontFace = props.getProperty(PROP_FONT_FACE);
- if (null != bookLanguage && bookLanguage.length() > 0) {
- boolean res = Engine.checkFontLanguageCompatibility(fontFace, bookLanguage);
- log.d("Checking font \"" + fontFace + "\" for compatibility with language \"" + bookLanguage + "\": res=" + res);
+ String fcLangCode = null;
+ if (null != bookLanguage && bookLanguage.length() > 0)
+ fcLangCode = Engine.findCompatibleFcLangCode(bookLanguage);
+ if (null != fcLangCode && fcLangCode.length() > 0) {
+ boolean res = Engine.checkFontLanguageCompatibility(fontFace, fcLangCode);
+ log.d("Checking font \"" + fontFace + "\" for compatibility with language \"" + bookLanguage + "\" fcLangCode=" + fcLangCode + ": res=" + res);
if (!res) {
BackgroundThread.instance().executeGUI(new Runnable() {
@Override
@@ -2591,7 +2594,8 @@ public void run() {
});
}
} else {
- log.d("Can't get book's language to check font compatibility! bookInfo=" + fileInfo);
+ if (null != bookLanguage)
+ log.d("Can't find compatible language code in embedded FontConfig catalog: language=\"" + bookLanguage + "\" bookInfo=" + fileInfo);
}
}
doc.applySettings(props);