Move hyphenator mapping to native code.
This CL contains the following changes:
- Replace Hyphenator mapping in Java with minikin::HyphenatorMap.
- Remove locale tracking code from StaticLayout.java
- Stop creating Hyphenator pointer array in StaticLayout.
Bug: 65024629
Bug: 67319341
Test: bit CtsTextTestCases:*
Test: bit FrameworksCoreTests:android.text.
Test: bit FrameworksCoreTests:android.widget.TextViewTest
Change-Id: Ib88c8e816c70959057abe296cc434ddb70bc397a
diff --git a/core/java/android/text/Hyphenator.java b/core/java/android/text/Hyphenator.java
index ddfc00c..4f1488e 100644
--- a/core/java/android/text/Hyphenator.java
+++ b/core/java/android/text/Hyphenator.java
@@ -16,262 +16,15 @@
package android.text;
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.util.HashMap;
-import java.util.Locale;
-
/**
- * Hyphenator is a wrapper class for a native implementation of automatic hyphenation,
+ * Hyphenator just initializes the native implementation of automatic hyphenation,
* in essence finding valid hyphenation opportunities in a word.
*
* @hide
*/
public class Hyphenator {
- private static String TAG = "Hyphenator";
-
- private final static Object sLock = new Object();
-
- @GuardedBy("sLock")
- final static HashMap<Locale, Hyphenator> sMap = new HashMap<Locale, Hyphenator>();
-
- private final long mNativePtr;
- private final HyphenationData mData;
-
- private Hyphenator(long nativePtr, HyphenationData data) {
- mNativePtr = nativePtr;
- mData = data;
- }
-
- public long getNativePtr() {
- return mNativePtr;
- }
-
- public static Hyphenator get(@Nullable Locale locale) {
- synchronized (sLock) {
- Hyphenator result = sMap.get(locale);
- if (result != null) {
- return result;
- }
-
- // If there's a variant, fall back to language+variant only, if available
- final String variant = locale.getVariant();
- if (!variant.isEmpty()) {
- final Locale languageAndVariantOnlyLocale =
- new Locale(locale.getLanguage(), "", variant);
- result = sMap.get(languageAndVariantOnlyLocale);
- if (result != null) {
- return putAlias(locale, result);
- }
- }
-
- // Fall back to language-only, if available
- final Locale languageOnlyLocale = new Locale(locale.getLanguage());
- result = sMap.get(languageOnlyLocale);
- if (result != null) {
- return putAlias(locale, result);
- }
-
- // Fall back to script-only, if available
- final String script = locale.getScript();
- if (!script.equals("")) {
- final Locale scriptOnlyLocale = new Locale.Builder()
- .setLanguage("und")
- .setScript(script)
- .build();
- result = sMap.get(scriptOnlyLocale);
- if (result != null) {
- return putAlias(locale, result);
- }
- }
-
- return putEmptyAlias(locale);
- }
- }
-
- private static class HyphenationData {
- private static final String SYSTEM_HYPHENATOR_LOCATION = "/system/usr/hyphen-data";
-
- public final int mMinPrefix, mMinSuffix;
- public final long mDataAddress;
-
- // Reasonable enough values for cases where we have no hyphenation patterns but may be able
- // to do some automatic hyphenation based on characters. These values would be used very
- // rarely.
- private static final int DEFAULT_MIN_PREFIX = 2;
- private static final int DEFAULT_MIN_SUFFIX = 2;
-
- public static final HyphenationData sEmptyData =
- new HyphenationData(DEFAULT_MIN_PREFIX, DEFAULT_MIN_SUFFIX);
-
- // Create empty HyphenationData.
- private HyphenationData(int minPrefix, int minSuffix) {
- mMinPrefix = minPrefix;
- mMinSuffix = minSuffix;
- mDataAddress = 0;
- }
-
- HyphenationData(String languageTag, int minPrefix, int minSuffix) {
- mMinPrefix = minPrefix;
- mMinSuffix = minSuffix;
-
- final String patternFilename = "hyph-" + languageTag.toLowerCase(Locale.US) + ".hyb";
- final File patternFile = new File(SYSTEM_HYPHENATOR_LOCATION, patternFilename);
- if (!patternFile.canRead()) {
- mDataAddress = 0;
- } else {
- long address;
- try (RandomAccessFile f = new RandomAccessFile(patternFile, "r")) {
- address = Os.mmap(0, f.length(), OsConstants.PROT_READ,
- OsConstants.MAP_SHARED, f.getFD(), 0 /* offset */);
- } catch (IOException | ErrnoException e) {
- Log.e(TAG, "error loading hyphenation " + patternFile, e);
- address = 0;
- }
- mDataAddress = address;
- }
- }
- }
-
- // Do not call this method outside of init method.
- private static Hyphenator putNewHyphenator(Locale loc, HyphenationData data) {
- final Hyphenator hyphenator = new Hyphenator(nBuildHyphenator(
- data.mDataAddress, loc.getLanguage(), data.mMinPrefix, data.mMinSuffix), data);
- sMap.put(loc, hyphenator);
- return hyphenator;
- }
-
- // Do not call this method outside of init method.
- private static void loadData(String langTag, int minPrefix, int maxPrefix) {
- final HyphenationData data = new HyphenationData(langTag, minPrefix, maxPrefix);
- putNewHyphenator(Locale.forLanguageTag(langTag), data);
- }
-
- // Caller must acquire sLock before calling this method.
- // The Hyphenator for the baseLangTag must exists.
- private static Hyphenator addAliasByTag(String langTag, String baseLangTag) {
- return putAlias(Locale.forLanguageTag(langTag),
- sMap.get(Locale.forLanguageTag(baseLangTag)));
- }
-
- // Caller must acquire sLock before calling this method.
- private static Hyphenator putAlias(Locale locale, Hyphenator base) {
- return putNewHyphenator(locale, base.mData);
- }
-
- // Caller must acquire sLock before calling this method.
- private static Hyphenator putEmptyAlias(Locale locale) {
- return putNewHyphenator(locale, HyphenationData.sEmptyData);
- }
-
- // TODO: Confirm that these are the best values. Various sources suggest (1, 1), but
- // that appears too small.
- private static final int INDIC_MIN_PREFIX = 2;
- private static final int INDIC_MIN_SUFFIX = 2;
-
- /**
- * Load hyphenation patterns at initialization time. We want to have patterns
- * for all locales loaded and ready to use so we don't have to do any file IO
- * on the UI thread when drawing text in different locales.
- *
- * @hide
- */
public static void init() {
- synchronized (sLock) {
- sMap.put(null, null);
-
- loadData("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Assamese
- loadData("bg", 2, 2); // Bulgarian
- loadData("bn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Bengali
- loadData("cu", 1, 2); // Church Slavonic
- loadData("cy", 2, 3); // Welsh
- loadData("da", 2, 2); // Danish
- loadData("de-1901", 2, 2); // German 1901 orthography
- loadData("de-1996", 2, 2); // German 1996 orthography
- loadData("de-CH-1901", 2, 2); // Swiss High German 1901 orthography
- loadData("en-GB", 2, 3); // British English
- loadData("en-US", 2, 3); // American English
- loadData("es", 2, 2); // Spanish
- loadData("et", 2, 3); // Estonian
- loadData("eu", 2, 2); // Basque
- loadData("fr", 2, 3); // French
- loadData("ga", 2, 3); // Irish
- loadData("gu", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Gujarati
- loadData("hi", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Hindi
- loadData("hr", 2, 2); // Croatian
- loadData("hu", 2, 2); // Hungarian
- // texhyphen sources say Armenian may be (1, 2); but that it needs confirmation.
- // Going with a more conservative value of (2, 2) for now.
- loadData("hy", 2, 2); // Armenian
- loadData("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Kannada
- loadData("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Malayalam
- loadData("mn-Cyrl", 2, 2); // Mongolian in Cyrillic script
- loadData("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Marathi
- loadData("nb", 2, 2); // Norwegian Bokmål
- loadData("nn", 2, 2); // Norwegian Nynorsk
- loadData("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Oriya
- loadData("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Punjabi
- loadData("pt", 2, 3); // Portuguese
- loadData("sl", 2, 2); // Slovenian
- loadData("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Tamil
- loadData("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Telugu
- loadData("tk", 2, 2); // Turkmen
- loadData("und-Ethi", 1, 1); // Any language in Ethiopic script
-
- // Following two hyphenators do not have pattern files but there is some special logic
- // based on language.
- loadData("ca", 2, 2); // Catalan
- loadData("pl", 2, 2); // Polish
-
- // English locales that fall back to en-US. The data is
- // from CLDR. It's all English locales, minus the locales whose
- // parent is en-001 (from supplementalData.xml, under <parentLocales>).
- // TODO: Figure out how to get this from ICU.
- addAliasByTag("en-AS", "en-US"); // English (American Samoa)
- addAliasByTag("en-GU", "en-US"); // English (Guam)
- addAliasByTag("en-MH", "en-US"); // English (Marshall Islands)
- addAliasByTag("en-MP", "en-US"); // English (Northern Mariana Islands)
- addAliasByTag("en-PR", "en-US"); // English (Puerto Rico)
- addAliasByTag("en-UM", "en-US"); // English (United States Minor Outlying Islands)
- addAliasByTag("en-VI", "en-US"); // English (Virgin Islands)
-
- // All English locales other than those falling back to en-US are mapped to en-GB.
- addAliasByTag("en", "en-GB");
-
- // For German, we're assuming the 1996 (and later) orthography by default.
- addAliasByTag("de", "de-1996");
- // Liechtenstein uses the Swiss hyphenation rules for the 1901 orthography.
- addAliasByTag("de-LI-1901", "de-CH-1901");
-
- // Norwegian is very probably Norwegian Bokmål.
- addAliasByTag("no", "nb");
-
- // Use mn-Cyrl. According to CLDR's likelySubtags.xml, mn is most likely to be mn-Cyrl.
- addAliasByTag("mn", "mn-Cyrl"); // Mongolian
-
- // Fall back to Ethiopic script for languages likely to be written in Ethiopic.
- // Data is from CLDR's likelySubtags.xml.
- // TODO: Convert this to a mechanism using ICU4J's ULocale#addLikelySubtags().
- addAliasByTag("am", "und-Ethi"); // Amharic
- addAliasByTag("byn", "und-Ethi"); // Blin
- addAliasByTag("gez", "und-Ethi"); // Geʻez
- addAliasByTag("ti", "und-Ethi"); // Tigrinya
- addAliasByTag("wal", "und-Ethi"); // Wolaytta
- }
- };
-
- private static native long nBuildHyphenator(long dataAddress,
- @NonNull String langTag, @IntRange(from = 1) int minPrefix,
- @IntRange(from = 1) int minSuffix);
+ nInit();
+ }
+ private static native void nInit();
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 4b6b6ae..5c60188 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -21,21 +21,18 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Paint;
-import android.os.LocaleList;
import android.text.style.LeadingMarginSpan;
import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
import android.text.style.LineHeightSpan;
import android.text.style.MetricAffectingSpan;
import android.text.style.TabStopSpan;
import android.util.Log;
-import android.util.Pair;
import android.util.Pools.SynchronizedPool;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
import java.util.Arrays;
-import java.util.Locale;
/**
* StaticLayout is a Layout for text that will not be edited after it
@@ -101,7 +98,6 @@
b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
- b.mLocales = null;
b.mMeasuredText = MeasuredText.obtain();
return b;
@@ -118,7 +114,6 @@
b.mMeasuredText = null;
b.mLeftIndents = null;
b.mRightIndents = null;
- b.mLocales = null;
b.mLeftPaddings = null;
b.mRightPaddings = null;
nFinishBuilder(b.mNativePtr);
@@ -409,17 +404,6 @@
return this;
}
- @NonNull
- private long[] getHyphenators(@NonNull LocaleList locales) {
- final int length = locales.size();
- final long[] result = new long[length];
- for (int i = 0; i < length; i++) {
- final Locale locale = locales.get(i);
- result[i] = Hyphenator.get(locale).getNativePtr();
- }
- return result;
- }
-
/**
* Measurement and break iteration is done in native code. The protocol for using
* the native code is as follows.
@@ -438,27 +422,12 @@
* After all paragraphs, call finish() to release expensive buffers.
*/
- private Pair<String, long[]> getLocaleAndHyphenatorIfChanged(TextPaint paint) {
- final LocaleList locales = paint.getTextLocales();
- if (!locales.equals(mLocales)) {
- mLocales = locales;
- return new Pair(locales.toLanguageTags(), getHyphenators(locales));
- } else {
- // passing null means keep current locale.
- // TODO: move locale change detection to native.
- return new Pair(null, null);
- }
- }
-
/* package */ void addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
- Pair<String, long[]> locHyph = getLocaleAndHyphenatorIfChanged(paint);
- nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl, locHyph.first,
- locHyph.second);
+ nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl);
}
/* package */ void addReplacementRun(TextPaint paint, int start, int end, float width) {
- Pair<String, long[]> locHyph = getLocaleAndHyphenatorIfChanged(paint);
- nAddReplacementRun(mNativePtr, start, end, width, locHyph.first, locHyph.second);
+ nAddReplacementRun(mNativePtr, paint.getNativeInstance(), start, end, width);
}
/**
@@ -516,8 +485,6 @@
// This will go away and be subsumed by native builder code
private MeasuredText mMeasuredText;
- private LocaleList mLocales;
-
private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
}
@@ -807,9 +774,6 @@
}
}
- // TODO: Move locale tracking code to native.
- b.mLocales = null; // Reset the locale tracking.
-
nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
firstWidth, firstWidthLineCount, restWidth,
variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency,
@@ -1537,15 +1501,16 @@
@Nullable int[] indents, @Nullable int[] leftPaddings, @Nullable int[] rightPaddings,
@IntRange(from = 0) int indentsOffset);
+ // TODO: Make this method CriticalNative once native code defers doing layouts.
private static native void nAddStyleRun(
/* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
- @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl,
- @Nullable String languageTags, @Nullable long[] hyphenators);
+ @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl);
- private static native void nAddReplacementRun(/* non-zero */ long nativePtr,
+ // TODO: Make this method CriticalNative once native code defers doing layouts.
+ private static native void nAddReplacementRun(
+ /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
@IntRange(from = 0) int start, @IntRange(from = 0) int end,
- @FloatRange(from = 0.0f) float width, @Nullable String languageTags,
- @Nullable long[] hyphenators);
+ @FloatRange(from = 0.0f) float width);
// populates LineBreaks and returns the number of breaks found
//
diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp
index da025da..b46f389 100644
--- a/core/jni/android_text_Hyphenator.cpp
+++ b/core/jni/android_text_Hyphenator.cpp
@@ -14,24 +14,155 @@
* limitations under the License.
*/
-#include <cstdint>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <algorithm>
+
#include <core_jni_helpers.h>
#include <minikin/Hyphenator.h>
-#include <nativehelper/ScopedUtfChars.h>
namespace android {
-static jlong nBuildHyphenator(JNIEnv* env, jclass, jlong dataAddress, jstring lang,
- jint minPrefix, jint minSuffix) {
- const uint8_t* bytebuf = reinterpret_cast<const uint8_t*>(dataAddress); // null allowed.
- ScopedUtfChars language(env, lang);
- minikin::Hyphenator* hyphenator = minikin::Hyphenator::loadBinary(
- bytebuf, minPrefix, minSuffix, language.c_str(), language.size());
- return reinterpret_cast<jlong>(hyphenator);
+static std::string buildFileName(const std::string& locale) {
+ constexpr char SYSTEM_HYPHENATOR_PREFIX[] = "/system/usr/hyphen-data/hyph-";
+ constexpr char SYSTEM_HYPHENATOR_SUFFIX[] = ".hyb";
+ std::string lowerLocale;
+ lowerLocale.reserve(locale.size());
+ std::transform(locale.begin(), locale.end(), std::back_inserter(lowerLocale), ::tolower);
+ return SYSTEM_HYPHENATOR_PREFIX + lowerLocale + SYSTEM_HYPHENATOR_SUFFIX;
+}
+
+static const uint8_t* mmapPatternFile(const std::string& locale) {
+ const std::string hyFilePath = buildFileName(locale);
+ const int fd = open(hyFilePath.c_str(), O_RDONLY);
+ if (fd == -1) {
+ return nullptr; // Open failed.
+ }
+
+ struct stat st = {};
+ if (fstat(fd, &st) == -1) { // Unlikely to happen.
+ close(fd);
+ return nullptr;
+ }
+
+ void* ptr = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0 /* offset */);
+ close(fd);
+ if (ptr == MAP_FAILED) {
+ return nullptr;
+ }
+ return reinterpret_cast<const uint8_t*>(ptr);
+}
+
+static void addHyphenatorWithoutPatternFile(const std::string& locale, int minPrefix,
+ int minSuffix) {
+ minikin::addHyphenator(locale, minikin::Hyphenator::loadBinary(
+ nullptr, minPrefix, minSuffix, locale));
+}
+
+static void addHyphenator(const std::string& locale, int minPrefix, int minSuffix) {
+ const uint8_t* ptr = mmapPatternFile(locale);
+ if (ptr == nullptr) {
+ ALOGE("Unable to find pattern file or unable to map it for %s", locale.c_str());
+ return;
+ }
+ minikin::addHyphenator(locale, minikin::Hyphenator::loadBinary(
+ ptr, minPrefix, minSuffix, locale));
+}
+
+static void addHyphenatorAlias(const std::string& from, const std::string& to) {
+ minikin::addHyphenatorAlias(from, to);
+}
+
+static void init() {
+ // TODO: Confirm that these are the best values. Various sources suggest (1, 1), but that
+ // appears too small.
+ constexpr int INDIC_MIN_PREFIX = 2;
+ constexpr int INDIC_MIN_SUFFIX = 2;
+
+ addHyphenator("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Assamese
+ addHyphenator("bg", 2, 2); // Bulgarian
+ addHyphenator("bn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Bengali
+ addHyphenator("cu", 1, 2); // Church Slavonic
+ addHyphenator("cy", 2, 3); // Welsh
+ addHyphenator("da", 2, 2); // Danish
+ addHyphenator("de-1901", 2, 2); // German 1901 orthography
+ addHyphenator("de-1996", 2, 2); // German 1996 orthography
+ addHyphenator("de-CH-1901", 2, 2); // Swiss High German 1901 orthography
+ addHyphenator("en-GB", 2, 3); // British English
+ addHyphenator("en-US", 2, 3); // American English
+ addHyphenator("es", 2, 2); // Spanish
+ addHyphenator("et", 2, 3); // Estonian
+ addHyphenator("eu", 2, 2); // Basque
+ addHyphenator("fr", 2, 3); // French
+ addHyphenator("ga", 2, 3); // Irish
+ addHyphenator("gu", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Gujarati
+ addHyphenator("hi", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Hindi
+ addHyphenator("hr", 2, 2); // Croatian
+ addHyphenator("hu", 2, 2); // Hungarian
+ // texhyphen sources say Armenian may be (1, 2); but that it needs confirmation.
+ // Going with a more conservative value of (2, 2) for now.
+ addHyphenator("hy", 2, 2); // Armenian
+ addHyphenator("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Kannada
+ addHyphenator("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Malayalam
+ addHyphenator("mn-Cyrl", 2, 2); // Mongolian in Cyrillic script
+ addHyphenator("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Marathi
+ addHyphenator("nb", 2, 2); // Norwegian Bokmål
+ addHyphenator("nn", 2, 2); // Norwegian Nynorsk
+ addHyphenator("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Oriya
+ addHyphenator("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Punjabi
+ addHyphenator("pt", 2, 3); // Portuguese
+ addHyphenator("sl", 2, 2); // Slovenian
+ addHyphenator("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Tamil
+ addHyphenator("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Telugu
+ addHyphenator("tk", 2, 2); // Turkmen
+ addHyphenator("und-Ethi", 1, 1); // Any language in Ethiopic script
+
+ // Following two hyphenators do not have pattern files but there is some special logic based on
+ // language.
+ addHyphenatorWithoutPatternFile("ca", 2, 2); // Catalan
+ addHyphenatorWithoutPatternFile("pl", 2, 2); // Polish
+
+ // English locales that fall back to en-US. The data is from CLDR. It's all English locales,
+ // minus the locales whose parent is en-001 (from supplementalData.xml, under <parentLocales>).
+ // TODO: Figure out how to get this from ICU.
+ addHyphenatorAlias("en-AS", "en-US"); // English (American Samoa)
+ addHyphenatorAlias("en-GU", "en-US"); // English (Guam)
+ addHyphenatorAlias("en-MH", "en-US"); // English (Marshall Islands)
+ addHyphenatorAlias("en-MP", "en-US"); // English (Northern Mariana Islands)
+ addHyphenatorAlias("en-PR", "en-US"); // English (Puerto Rico)
+ addHyphenatorAlias("en-UM", "en-US"); // English (United States Minor Outlying Islands)
+ addHyphenatorAlias("en-VI", "en-US"); // English (Virgin Islands)
+
+ // All English locales other than those falling back to en-US are mapped to en-GB.
+ addHyphenatorAlias("en", "en-GB");
+
+ // For German, we're assuming the 1996 (and later) orthography by default.
+ addHyphenatorAlias("de", "de-1996");
+ // Liechtenstein uses the Swiss hyphenation rules for the 1901 orthography.
+ addHyphenatorAlias("de-LI-1901", "de-CH-1901");
+
+ // Norwegian is very probably Norwegian Bokmål.
+ addHyphenatorAlias("no", "nb");
+
+ // Use mn-Cyrl. According to CLDR's likelySubtags.xml, mn is most likely to be mn-Cyrl.
+ addHyphenatorAlias("mn", "mn-Cyrl"); // Mongolian
+
+ // Fall back to Ethiopic script for languages likely to be written in Ethiopic.
+ // Data is from CLDR's likelySubtags.xml.
+ // TODO: Convert this to a mechanism using ICU4J's ULocale#addLikelySubtags().
+ addHyphenatorAlias("am", "und-Ethi"); // Amharic
+ addHyphenatorAlias("byn", "und-Ethi"); // Blin
+ addHyphenatorAlias("gez", "und-Ethi"); // Geʻez
+ addHyphenatorAlias("ti", "und-Ethi"); // Tigrinya
+ addHyphenatorAlias("wal", "und-Ethi"); // Wolaytta
+
}
static const JNINativeMethod gMethods[] = {
- {"nBuildHyphenator", "(JLjava/lang/String;II)J", (void*) nBuildHyphenator},
+ {"nInit", "()V", (void*) init},
};
int register_android_text_Hyphenator(JNIEnv* env) {
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index 1f7277a..04e9dfd 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -195,49 +195,9 @@
b->finish();
}
-class ScopedNullableUtfString {
-public:
- ScopedNullableUtfString(JNIEnv* env, jstring s) : mEnv(env), mStr(s) {
- if (s == nullptr) {
- mUtf8Chars = nullptr;
- } else {
- mUtf8Chars = mEnv->GetStringUTFChars(s, nullptr);
- }
- }
-
- ~ScopedNullableUtfString() {
- if (mUtf8Chars != nullptr) {
- mEnv->ReleaseStringUTFChars(mStr, mUtf8Chars);
- }
- }
-
- const char* get() const {
- return mUtf8Chars;
- }
-
-private:
- JNIEnv* mEnv;
- jstring mStr;
- const char* mUtf8Chars;
-};
-
-static std::vector<minikin::Hyphenator*> makeHyphenators(JNIEnv* env, jlongArray hyphenators) {
- std::vector<minikin::Hyphenator*> out;
- if (hyphenators == nullptr) {
- return out;
- }
- ScopedLongArrayRO longArray(env, hyphenators);
- size_t size = longArray.size();
- out.reserve(size);
- for (size_t i = 0; i < size; i++) {
- out.push_back(reinterpret_cast<minikin::Hyphenator*>(longArray[i]));
- }
- return out;
-}
-
// Basically similar to Paint.getTextRunAdvances but with C++ interface
static void nAddStyleRun(JNIEnv* env, jclass, jlong nativePtr, jlong nativePaint, jint start,
- jint end, jboolean isRtl, jstring langTags, jlongArray hyphenators) {
+ jint end, jboolean isRtl) {
minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
Paint* paint = reinterpret_cast<Paint*>(nativePaint);
const Typeface* typeface = paint->getAndroidTypeface();
@@ -246,16 +206,14 @@
minikin::FontStyle style = MinikinUtils::prepareMinikinPaint(&minikinPaint, paint,
typeface);
- ScopedNullableUtfString langTagsString(env, langTags);
- b->addStyleRun(&minikinPaint, resolvedTypeface->fFontCollection, style, start,
- end, isRtl, langTagsString.get(), makeHyphenators(env, hyphenators));
+ b->addStyleRun(&minikinPaint, resolvedTypeface->fFontCollection, style, start, end, isRtl);
}
-static void nAddReplacementRun(JNIEnv* env, jclass, jlong nativePtr,
- jint start, jint end, jfloat width, jstring langTags, jlongArray hyphenators) {
+static void nAddReplacementRun(JNIEnv* env, jclass, jlong nativePtr, jlong nativePaint,
+ jint start, jint end, jfloat width) {
minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
- ScopedNullableUtfString langTagsString(env, langTags);
- b->addReplacement(start, end, width, langTagsString.get(), makeHyphenators(env, hyphenators));
+ Paint* paint = reinterpret_cast<Paint*>(nativePaint);
+ b->addReplacement(start, end, width, paint->getMinikinLangListId());
}
static const JNINativeMethod gMethods[] = {
@@ -264,8 +222,8 @@
{"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
{"nFinishBuilder", "(J)V", (void*) nFinishBuilder},
{"nSetupParagraph", "(J[CIFIF[IIIIZ[I[I[II)V", (void*) nSetupParagraph},
- {"nAddStyleRun", "(JJIIZLjava/lang/String;[J)V", (void*) nAddStyleRun},
- {"nAddReplacementRun", "(JIIFLjava/lang/String;[J)V", (void*) nAddReplacementRun},
+ {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun},
+ {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun},
{"nComputeLineBreaks", "(JLandroid/text/StaticLayout$LineBreaks;[I[F[F[F[II[F)I",
(void*) nComputeLineBreaks}
};