Cache ICU break iterators in SkUnicode_icu

Gate this behind an ifdef that can be disabled in environments where
the ICU ubrk_safeClone API is not available.

Change-Id: Ia1a3f677271622f2b7ae478b4d1ce76c74eed057
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/352876
Reviewed-by: Julia Lavrova <jlavrova@google.com>
Commit-Queue: Jason Simmons <jsimmons@google.com>
diff --git a/gn/skia.gni b/gn/skia.gni
index a6ffb6e..21e8e53 100644
--- a/gn/skia.gni
+++ b/gn/skia.gni
@@ -21,6 +21,7 @@
   skia_enable_fontmgr_win = is_win
   skia_enable_fontmgr_win_gdi = is_win
   skia_enable_gpu = true
+  skia_enable_icu_ubrk_safeclone = false
   skia_enable_pdf = true
   skia_enable_skottie = !(is_win && is_component_build)
   skia_enable_skrive = true
diff --git a/modules/skshaper/BUILD.gn b/modules/skshaper/BUILD.gn
index 57160c1..2063f20 100644
--- a/modules/skshaper/BUILD.gn
+++ b/modules/skshaper/BUILD.gn
@@ -20,6 +20,9 @@
       defines += [ "SK_SHAPER_HARFBUZZ_AVAILABLE" ]
       defines += [ "SK_UNICODE_AVAILABLE" ]
     }
+    if (skia_enable_icu_ubrk_safeclone) {
+      defines += [ "SK_ENABLE_ICU_UBRK_SAFECLONE" ]
+    }
   }
 
   component("skshaper") {
diff --git a/modules/skshaper/src/SkUnicode_icu.cpp b/modules/skshaper/src/SkUnicode_icu.cpp
index 66185b6..99bb55b 100644
--- a/modules/skshaper/src/SkUnicode_icu.cpp
+++ b/modules/skshaper/src/SkUnicode_icu.cpp
@@ -5,7 +5,9 @@
 * found in the LICENSE file.
 */
 #include "include/core/SkString.h"
+#include "include/private/SkMutex.h"
 #include "include/private/SkTFitsIn.h"
+#include "include/private/SkTHash.h"
 #include "include/private/SkTemplates.h"
 #include "modules/skshaper/src/SkUnicode.h"
 #include "src/utils/SkUTF.h"
@@ -32,6 +34,16 @@
     return val < 0 ? 0xFFFD : val;
 }
 
+static UBreakIteratorType convertType(SkUnicode::BreakType type) {
+    switch (type) {
+        case SkUnicode::BreakType::kLines: return UBRK_LINE;
+        case SkUnicode::BreakType::kGraphemes: return UBRK_CHARACTER;
+        case SkUnicode::BreakType::kWords: return UBRK_WORD;
+        default:
+            return UBRK_CHARACTER;
+    }
+}
+
 class SkBidiIterator_icu : public SkBidiIterator {
     SkUnicodeBidi fBidi;
 public:
@@ -155,27 +167,58 @@
         fLastResult = 0;
         return true;
     }
+};
 
-    static UBreakIteratorType convertType(SkUnicode::BreakType type) {
-        switch (type) {
-            case SkUnicode::BreakType::kLines: return UBRK_LINE;
-            case SkUnicode::BreakType::kGraphemes: return UBRK_CHARACTER;
-            case SkUnicode::BreakType::kWords: return UBRK_WORD;
-            default:
-              return UBRK_CHARACTER;
-        }
+class SkIcuBreakIteratorCache {
+    SkTHashMap<SkUnicode::BreakType, ICUBreakIterator> fBreakCache;
+    SkMutex fBreakCacheMutex;
+
+ public:
+    static SkIcuBreakIteratorCache& get() {
+        static SkIcuBreakIteratorCache instance;
+        return instance;
     }
 
-    static std::unique_ptr<SkBreakIterator> makeUtf8BreakIterator
-        (const char locale[], SkUnicode::BreakType type) {
+#ifdef SK_ENABLE_ICU_UBRK_SAFECLONE
+
+    ICUBreakIterator makeBreakIterator(SkUnicode::BreakType type) {
         UErrorCode status = U_ZERO_ERROR;
-        ICUBreakIterator iterator(ubrk_open(convertType(type), locale, nullptr, 0, &status));
+        ICUBreakIterator* cachedIterator;
+        {
+            SkAutoMutexExclusive lock(fBreakCacheMutex);
+            cachedIterator = fBreakCache.find(type);
+            if (!cachedIterator) {
+                ICUBreakIterator newIterator(ubrk_open(convertType(type), uloc_getDefault(), nullptr, 0, &status));
+                if (U_FAILURE(status)) {
+                    SkDEBUGF("Break error: %s", u_errorName(status));
+                } else {
+                    cachedIterator = fBreakCache.set(type, std::move(newIterator));
+                }
+            }
+        }
+        ICUBreakIterator iterator;
+        if (cachedIterator) {
+            iterator.reset(ubrk_safeClone(cachedIterator->get(), nullptr, nullptr, &status));
+            if (U_FAILURE(status)) {
+                SkDEBUGF("Break error: %s", u_errorName(status));
+            }
+        }
+        return iterator;
+    }
+
+#else  // SK_ENABLE_ICU_UBRK_SAFECLONE
+
+    ICUBreakIterator makeBreakIterator(SkUnicode::BreakType type) {
+        UErrorCode status = U_ZERO_ERROR;
+        ICUBreakIterator iterator(ubrk_open(convertType(type), uloc_getDefault(), nullptr, 0, &status));
         if (U_FAILURE(status)) {
             SkDEBUGF("Break error: %s", u_errorName(status));
-            return nullptr;
         }
-        return std::unique_ptr<SkBreakIterator>(new SkBreakIterator_icu(std::move(iterator)));
+        return iterator;
     }
+
+#endif  // SK_ENABLE_ICU_UBRK_SAFECLONE
+
 };
 
 class SkScriptIterator_icu : public SkScriptIterator {
@@ -198,18 +241,6 @@
 };
 
 class SkUnicode_icu : public SkUnicode {
-
-    static UBreakIteratorType convertType(BreakType type) {
-        switch (type) {
-            case BreakType::kLines: return UBRK_LINE;
-            case BreakType::kGraphemes: return UBRK_CHARACTER;
-            case BreakType::kWords: return UBRK_WORD;
-            default:
-              SkDEBUGF("Convert error: wrong break type");
-              return UBRK_CHARACTER;
-        }
-    }
-
     static bool extractBidi(const char utf8[],
                             int utf8Units,
                             TextDirection dir,
@@ -271,9 +302,8 @@
 
         UErrorCode status = U_ZERO_ERROR;
 
-        UBreakIteratorType breakType = convertType(BreakType::kWords);
-        ICUBreakIterator iterator(ubrk_open(breakType, uloc_getDefault(), nullptr, 0, &status));
-        if (U_FAILURE(status)) {
+        ICUBreakIterator iterator = SkIcuBreakIteratorCache::get().makeBreakIterator(BreakType::kWords);
+        if (!iterator) {
             SkDEBUGF("Break error: %s", u_errorName(status));
             return false;
         }
@@ -313,9 +343,9 @@
         }
         SkASSERT(text);
 
-        ICUBreakIterator iterator(ubrk_open(convertType(type), uloc_getDefault(), nullptr, 0, &status));
-        if (U_FAILURE(status)) {
-            SkDEBUGF("Break error: %s", u_errorName(status));
+        ICUBreakIterator iterator = SkIcuBreakIteratorCache::get().makeBreakIterator(type);
+        if (!iterator) {
+            return false;
         }
 
         ubrk_setUText(iterator.get(), text.get(), &status);
@@ -408,7 +438,13 @@
     }
     std::unique_ptr<SkBreakIterator> makeBreakIterator(const char locale[],
                                                        BreakType breakType) override {
-        return SkBreakIterator_icu::makeUtf8BreakIterator(locale, breakType);
+        UErrorCode status = U_ZERO_ERROR;
+        ICUBreakIterator iterator(ubrk_open(convertType(breakType), locale, nullptr, 0, &status));
+        if (U_FAILURE(status)) {
+            SkDEBUGF("Break error: %s", u_errorName(status));
+            return nullptr;
+        }
+        return std::unique_ptr<SkBreakIterator>(new SkBreakIterator_icu(std::move(iterator)));
     }
     std::unique_ptr<SkScriptIterator> makeScriptIterator() override {
         return SkScriptIterator_icu::makeScriptIterator();