FontClient plugin cache optimize 87/275287/28
authorEunki, Hong <eunkiki.hong@samsung.com>
Thu, 19 May 2022 13:13:55 +0000 (22:13 +0900)
committerEunki, Hong <eunkiki.hong@samsung.com>
Thu, 2 Jun 2022 08:12:44 +0000 (17:12 +0900)
1. Make find operation of the fontId by fontDescriptionId and
requestedPointSize int O(1) by unordered_map.

2. Cache 128 (or DALI_GLYPH_CACHE_MAX) Glyphs per each glyphIndex.
So we can access glyph informations more faster.
This Glyph cache algorithm working as LRU (Least Recently Used).

TODO : is LRUCacheContainer used only for text-abstraction/plugin?

Change-Id: I9f66435527b73507ffa85c71bc4b594545c5f1eb
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
13 files changed:
automated-tests/src/dali-adaptor-internal/CMakeLists.txt
automated-tests/src/dali-adaptor-internal/utc-Dali-LRUCacheContainer.cpp [new file with mode: 0644]
dali/internal/text/file.list
dali/internal/text/text-abstraction/plugin/bitmap-font-cache-item.cpp
dali/internal/text/text-abstraction/plugin/bitmap-font-cache-item.h
dali/internal/text/text-abstraction/plugin/font-cache-item-interface.h
dali/internal/text/text-abstraction/plugin/font-client-plugin-impl.cpp
dali/internal/text/text-abstraction/plugin/font-client-plugin-impl.h
dali/internal/text/text-abstraction/plugin/font-face-cache-item.cpp
dali/internal/text/text-abstraction/plugin/font-face-cache-item.h
dali/internal/text/text-abstraction/plugin/font-face-glyph-cache-manager.cpp [new file with mode: 0644]
dali/internal/text/text-abstraction/plugin/font-face-glyph-cache-manager.h [new file with mode: 0644]
dali/internal/text/text-abstraction/plugin/lru-cache-container.h [new file with mode: 0644]

index 4fe46cfd2ab4811557842f668738d6a9287feb2f..a99d4d302b4ec94972510d8cad82b8c93be54d2e 100644 (file)
@@ -7,15 +7,16 @@ SET(CAPI_LIB "dali-adaptor-internal")
 
 SET(TC_SOURCES
     utc-Dali-AddOns.cpp
+    utc-Dali-BmpLoader.cpp
     utc-Dali-CommandLineOptions.cpp
     utc-Dali-CompressedTextures.cpp
     utc-Dali-FontClient.cpp
     utc-Dali-GifLoader.cpp
     utc-Dali-IcoLoader.cpp
-    utc-Dali-BmpLoader.cpp
     utc-Dali-ImageOperations.cpp
     utc-Dali-Internal-PixelBuffer.cpp
     utc-Dali-Lifecycle-Controller.cpp
+    utc-Dali-LRUCacheContainer.cpp
     utc-Dali-TiltSensor.cpp
     utc-Dali-WbmpLoader.cpp
 )
diff --git a/automated-tests/src/dali-adaptor-internal/utc-Dali-LRUCacheContainer.cpp b/automated-tests/src/dali-adaptor-internal/utc-Dali-LRUCacheContainer.cpp
new file mode 100644 (file)
index 0000000..08476ea
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <iostream>
+#include <string>
+
+#include <dali-test-suite-utils.h>
+#include <dali/internal/text/text-abstraction/plugin/lru-cache-container.h>
+
+using namespace Dali::TextAbstraction::Internal;
+using TestLRUCacheIntInt    = LRUCacheContainer<int, int>;
+using TestLRUCacheIntString = LRUCacheContainer<int, std::string>;
+
+namespace
+{
+template<typename K, typename E>
+void TestLRUCacheExist(LRUCacheContainer<K, E>& cache, const K& key, bool expectExist, const char* location)
+{
+  auto iter = cache.Find(key);
+  DALI_TEST_EQUALS((iter != cache.End()), expectExist, location);
+}
+
+template<typename K, typename E>
+void TestLRUCachePop(LRUCacheContainer<K, E>& cache, const E& expectElement, const char* location)
+{
+  auto popElement = cache.Pop();
+  DALI_TEST_EQUALS(popElement, expectElement, location);
+}
+} // namespace
+
+void utc_dali_internal_lru_cache_container_startup(void)
+{
+  test_return_value = TET_UNDEF;
+}
+
+void utc_dali_internal_lru_cache_container_cleanup(void)
+{
+  test_return_value = TET_PASS;
+}
+
+int UtcDaliLRUCacheContainerPushPopTest(void)
+{
+  TestLRUCacheIntInt cache(3);
+
+  tet_infoline("Test LRUCache Push and Pop");
+
+  DALI_TEST_EQUALS(cache.IsEmpty(), true, TEST_LOCATION);
+  DALI_TEST_EQUALS(cache.IsFull(), false, TEST_LOCATION);
+
+  cache.Push(1111, 111);
+  DALI_TEST_EQUALS(cache.IsEmpty(), false, TEST_LOCATION);
+
+  cache.Push(2222, 222);
+  cache.Push(3333, 333);
+  DALI_TEST_EQUALS(cache.IsFull(), true, TEST_LOCATION);
+
+  cache.Push(4444, 444);
+  DALI_TEST_EQUALS(cache.IsFull(), true, TEST_LOCATION);
+
+  TestLRUCacheExist(cache, 1111, false, TEST_LOCATION);
+  TestLRUCacheExist(cache, 2222, true, TEST_LOCATION);
+  TestLRUCacheExist(cache, 3333, true, TEST_LOCATION);
+  TestLRUCacheExist(cache, 4444, true, TEST_LOCATION);
+
+  TestLRUCachePop(cache, 222, TEST_LOCATION);
+  DALI_TEST_EQUALS(cache.IsFull(), false, TEST_LOCATION);
+
+  TestLRUCachePop(cache, 333, TEST_LOCATION);
+  DALI_TEST_EQUALS(cache.IsEmpty(), false, TEST_LOCATION);
+  DALI_TEST_EQUALS(cache.IsFull(), false, TEST_LOCATION);
+
+  cache.Push(5555, 555);
+  cache.Push(6666, 666);
+
+  // Replace exist key
+  cache.Push(5555, 777);
+  DALI_TEST_EQUALS(cache.IsFull(), true, TEST_LOCATION);
+
+  // Change element
+  DALI_TEST_EQUALS(cache.Get(5555), 777, TEST_LOCATION);
+  cache.Get(5555) = 888;
+  DALI_TEST_EQUALS(cache.Get(5555), 888, TEST_LOCATION);
+
+  TestLRUCachePop(cache, 444, TEST_LOCATION);
+
+  TestLRUCacheExist(cache, 2222, false, TEST_LOCATION);
+  TestLRUCacheExist(cache, 3333, false, TEST_LOCATION);
+  TestLRUCacheExist(cache, 4444, false, TEST_LOCATION);
+
+  TestLRUCachePop(cache, 666, TEST_LOCATION);
+  TestLRUCachePop(cache, 888, TEST_LOCATION);
+  DALI_TEST_EQUALS(cache.IsEmpty(), true, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliLRUCacheContainerPushPopTest2(void)
+{
+  TestLRUCacheIntString cache(3);
+
+  tet_infoline("Test LRUCache Push and Pop 2");
+
+  DALI_TEST_EQUALS(cache.IsEmpty(), true, TEST_LOCATION);
+  DALI_TEST_EQUALS(cache.IsFull(), false, TEST_LOCATION);
+
+  cache.Push(1111, "111");
+  DALI_TEST_EQUALS(cache.IsEmpty(), false, TEST_LOCATION);
+
+  cache.Push(2222, "222");
+  cache.Push(3333, "333");
+  DALI_TEST_EQUALS(cache.IsFull(), true, TEST_LOCATION);
+
+  cache.Push(4444, "444");
+  DALI_TEST_EQUALS(cache.IsFull(), true, TEST_LOCATION);
+
+  TestLRUCacheExist(cache, 1111, false, TEST_LOCATION);
+  TestLRUCacheExist(cache, 2222, true, TEST_LOCATION);
+  TestLRUCacheExist(cache, 3333, true, TEST_LOCATION);
+  TestLRUCacheExist(cache, 4444, true, TEST_LOCATION);
+
+  TestLRUCachePop(cache, std::string("222"), TEST_LOCATION);
+  DALI_TEST_EQUALS(cache.IsFull(), false, TEST_LOCATION);
+
+  TestLRUCachePop(cache, std::string("333"), TEST_LOCATION);
+  DALI_TEST_EQUALS(cache.IsEmpty(), false, TEST_LOCATION);
+  DALI_TEST_EQUALS(cache.IsFull(), false, TEST_LOCATION);
+
+  cache.Push(5555, "555");
+  cache.Push(6666, "666");
+
+  // Replace exist key
+  cache.Push(5555, "777");
+  DALI_TEST_EQUALS(cache.IsFull(), true, TEST_LOCATION);
+
+  // Change element
+  DALI_TEST_EQUALS(cache.Get(5555), "777", TEST_LOCATION);
+  cache.Get(5555) = "888";
+  DALI_TEST_EQUALS(cache.Get(5555), "888", TEST_LOCATION);
+
+  TestLRUCachePop(cache, std::string("444"), TEST_LOCATION);
+
+  TestLRUCacheExist(cache, 2222, false, TEST_LOCATION);
+  TestLRUCacheExist(cache, 3333, false, TEST_LOCATION);
+  TestLRUCacheExist(cache, 4444, false, TEST_LOCATION);
+
+  TestLRUCachePop(cache, std::string("666"), TEST_LOCATION);
+  TestLRUCachePop(cache, std::string("888"), TEST_LOCATION);
+  DALI_TEST_EQUALS(cache.IsEmpty(), true, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliLRUCacheContainerPopEmptyNegative(void)
+{
+  TestLRUCacheIntInt cache(3);
+
+  tet_infoline("Test LRUCache Pop empty");
+
+  try
+  {
+    cache.Pop();
+    DALI_TEST_CHECK(false); // Should not get here
+  }
+  catch(...)
+  {
+    DALI_TEST_CHECK(true); // Asserted
+  }
+
+  END_TEST;
+}
+
+int UtcDaliLRUCacheContainerGetInvalidNegative(void)
+{
+  TestLRUCacheIntInt cache(3);
+
+  tet_infoline("Test LRUCache Get with invalid key");
+
+  cache.Push(111, 1);
+  cache.Push(222, 2);
+  cache.Push(333, 3);
+  cache.Push(444, 4);
+
+  try
+  {
+    cache.Get(111);
+    DALI_TEST_CHECK(false); // Should not get here
+  }
+  catch(...)
+  {
+    DALI_TEST_CHECK(true); // Asserted
+  }
+
+  END_TEST;
+}
index 7f00ae6684fadb9fab7e13df7349af55433e6e1d..1444e791707c3190bbe85e49af845076ded8f6f4 100644 (file)
@@ -13,4 +13,5 @@ SET( adaptor_text_common_src_files
     ${adaptor_text_dir}/text-abstraction/plugin/font-client-utils.cpp
     ${adaptor_text_dir}/text-abstraction/plugin/font-client-plugin-impl.cpp
     ${adaptor_text_dir}/text-abstraction/plugin/font-face-cache-item.cpp
+    ${adaptor_text_dir}/text-abstraction/plugin/font-face-glyph-cache-manager.cpp
 )
index e7b5d43812e2a0af688e7e03d88e9ef1ed8c777b..6512b959c46b4a504293452ee98fe458995c769f 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -67,14 +67,14 @@ void BitmapFontCacheItem::GetFontMetrics(FontMetrics& metrics, unsigned int dpiV
   metrics.underlineThickness = font.underlineThickness;
 }
 
-bool BitmapFontCacheItem::GetGlyphMetrics(GlyphInfo& glyph, unsigned int dpiVertical, bool horizontal) const
+bool BitmapFontCacheItem::GetGlyphMetrics(GlyphInfo& glyphInfo, unsigned int dpiVertical, bool horizontal) const
 {
   bool success(false);
 
   unsigned int index = 0u;
   for(auto& item : font.glyphs)
   {
-    if(item.utf32 == glyph.index)
+    if(item.utf32 == glyphInfo.index)
     {
       Devel::PixelBuffer& pixelBuffer = const_cast<Devel::PixelBuffer&>(pixelBuffers[index]);
       if(!pixelBuffer)
@@ -82,13 +82,13 @@ bool BitmapFontCacheItem::GetGlyphMetrics(GlyphInfo& glyph, unsigned int dpiVert
         pixelBuffer = LoadImageFromFile(item.url);
       }
 
-      glyph.width       = static_cast<float>(pixelBuffer.GetWidth());
-      glyph.height      = static_cast<float>(pixelBuffer.GetHeight());
-      glyph.xBearing    = 0.f;
-      glyph.yBearing    = glyph.height + item.descender;
-      glyph.advance     = glyph.width;
-      glyph.scaleFactor = 1.f;
-      success           = true;
+      glyphInfo.width       = static_cast<float>(pixelBuffer.GetWidth());
+      glyphInfo.height      = static_cast<float>(pixelBuffer.GetHeight());
+      glyphInfo.xBearing    = 0.f;
+      glyphInfo.yBearing    = glyphInfo.height + item.descender;
+      glyphInfo.advance     = glyphInfo.width;
+      glyphInfo.scaleFactor = 1.f;
+      success               = true;
       break;
     }
     ++index;
index 501f56e045d5e1ae37949d5b9a01cc0fe9d484f3..39e0cfb9ed78e2a8985deae792bed36c5dbaa14b 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_INTERNAL_TEXT_ABSTRACTION_BITMAP_FONT_CACHE_ITEM_H
 
 /*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -41,6 +41,11 @@ struct BitmapFontCacheItem : public FontCacheItemInterface
    */
   BitmapFontCacheItem(const BitmapFont& bitmapFont, FontId fontId);
 
+  /**
+   * Destructor
+   */
+  ~BitmapFontCacheItem() = default;
+
   /**
    * @copydoc FontCacheItemInterface::GetFontMetrics()
    */
@@ -49,7 +54,7 @@ struct BitmapFontCacheItem : public FontCacheItemInterface
   /**
    * @copydoc FontCacheItemInterface::GetGlyphMetrics()
    */
-  bool GetGlyphMetrics(GlyphInfo& glyph, unsigned int dpiVertical, bool horizontal) const override;
+  bool GetGlyphMetrics(GlyphInfo& glyphInfo, unsigned int dpiVertical, bool horizontal) const override;
 
   /**
    * @copydoc FontCacheItemInterface::CreateBitmap()
index 09addd2cbc8d7b8e655ab06aaee0a9aaa1f6026a..1d042d5a2249cf29eda6d59185acbcc53f6e68dc 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_TEST_ABSTRACTION_INTERNAL_FONT_CACHE_ITEM_INTERFACE_H
 
 /*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -40,7 +40,7 @@ struct FontCacheItemInterface
    *
    * @param[in,out] glyph The glyph to fill
    */
-  virtual bool GetGlyphMetrics(GlyphInfo& glyph, unsigned int dpiVertical, bool horizontal) const = 0;
+  virtual bool GetGlyphMetrics(GlyphInfo& glyphInfo, unsigned int dpiVertical, bool horizontal) const = 0;
 
   /**
    * Create a bitmap for the given glyph
index 57ea4fbbe74bec5f76b6f6394f9c65449413ff9c..7790a2e13e83c393b1551eb3ae323c3ab8dcef4d 100644 (file)
@@ -248,12 +248,10 @@ FontClient::Plugin::FontDescriptionCacheItem::FontDescriptionCacheItem(FontDescr
 {
 }
 
-FontClient::Plugin::FontDescriptionSizeCacheItem::FontDescriptionSizeCacheItem(FontDescriptionId validatedFontId,
-                                                                               PointSize26Dot6   requestedPointSize,
-                                                                               FontId            fontId)
-: validatedFontId(validatedFontId),
-  requestedPointSize(requestedPointSize),
-  fontId(fontId)
+FontClient::Plugin::FontDescriptionSizeCacheKey::FontDescriptionSizeCacheKey(FontDescriptionId fontDescriptionId,
+                                                                             PointSize26Dot6   requestedPointSize)
+: fontDescriptionId(fontDescriptionId),
+  requestedPointSize(requestedPointSize)
 {
 }
 
@@ -299,6 +297,9 @@ FontClient::Plugin::~Plugin()
   DestroyCharacterSets(mCharacterSetCache);
   ClearCharacterSetFromFontFaceCache();
 
+  // Clear FontFaceCache here. Due to we sould deallocate FT_Faces before done freetype library
+  mFontFaceCache.clear();
+
 #ifdef ENABLE_VECTOR_BASED_TEXT_RENDERING
   delete mVectorFontCache;
 #endif
@@ -330,6 +331,7 @@ void FontClient::Plugin::ClearCache()
   mCharacterSetCache.Clear();
 
   mFontDescriptionSizeCache.clear();
+  mFontDescriptionSizeCache.rehash(0); // Note : unordered_map.clear() didn't deallocate memory
 
   mEllipsisCache.Clear();
   mPixelBufferCache.clear();
@@ -492,14 +494,14 @@ void FontClient::Plugin::GetDefaultPlatformFontDescription(FontDescription& font
         mFontDescriptionCache.push_back(tempFontDescription);
 
         // Set the index to the vector of paths to font file names.
-        const FontDescriptionId validatedFontId = mFontDescriptionCache.size();
+        const FontDescriptionId fontDescriptionId = mFontDescriptionCache.size();
 
         FONT_LOG_DESCRIPTION(tempFontDescription, "default platform font");
-        DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "  default font validatedFontId : %d\n", validatedFontId);
+        DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "  default font fontDescriptionId : %d\n", fontDescriptionId);
 
         // Cache the index and the matched font's description.
         FontDescriptionCacheItem item(tempFontDescription,
-                                      validatedFontId);
+                                      fontDescriptionId);
 
         mValidatedFontCache.push_back(std::move(item));
       }
@@ -586,9 +588,9 @@ void FontClient::Plugin::GetDescription(FontId           id,
       {
         for(const auto& item : mFontDescriptionSizeCache)
         {
-          if(item.fontId == fontIdCacheItem.id)
+          if(item.second == fontIdCacheItem.index)
           {
-            fontDescription = *(mFontDescriptionCache.begin() + item.validatedFontId - 1u);
+            fontDescription = *(mFontDescriptionCache.begin() + item.first.fontDescriptionId - 1u);
 
             FONT_LOG_DESCRIPTION(fontDescription, "");
             return;
@@ -599,7 +601,7 @@ void FontClient::Plugin::GetDescription(FontId           id,
       case FontDescription::BITMAP_FONT:
       {
         fontDescription.type   = FontDescription::BITMAP_FONT;
-        fontDescription.family = mBitmapFontCache[fontIdCacheItem.id].font.name;
+        fontDescription.family = mBitmapFontCache[fontIdCacheItem.index].font.name;
         break;
       }
       default:
@@ -649,7 +651,7 @@ bool FontClient::Plugin::IsCharacterSupportedByFont(FontId fontId, Character cha
 
 const FontCacheItemInterface* FontClient::Plugin::GetCachedFontItem(FontId id) const
 {
-  const FontId index = id - 1u;
+  const FontCacheIndex index = id - 1u;
   if((id > 0u) && (index < mFontIdCache.Count()))
   {
     const FontIdCacheItem& fontIdCacheItem = mFontIdCache[index];
@@ -657,11 +659,11 @@ const FontCacheItemInterface* FontClient::Plugin::GetCachedFontItem(FontId id) c
     {
       case FontDescription::FACE_FONT:
       {
-        return &mFontFaceCache[fontIdCacheItem.id];
+        return &mFontFaceCache[fontIdCacheItem.index];
       }
       case FontDescription::BITMAP_FONT:
       {
-        return &mBitmapFontCache[fontIdCacheItem.id];
+        return &mBitmapFontCache[fontIdCacheItem.index];
       }
       default:
       {
@@ -715,7 +717,7 @@ FontId FontClient::Plugin::FindFontForCharacter(const FontList&         fontList
         if((fontId > 0) &&
            (fontId - 1u < mFontIdCache.Count()))
         {
-          const FontFaceCacheItem& item = mFontFaceCache[mFontIdCache[fontId - 1u].id];
+          const FontFaceCacheItem& item = mFontFaceCache[mFontIdCache[fontId - 1u].index];
 
           foundColor = item.mHasColorTables;
         }
@@ -905,22 +907,22 @@ FontId FontClient::Plugin::GetFontId(const FontDescription& fontDescription,
   }
 
   // Check if the font's description have been validated before.
-  FontDescriptionId validatedFontId = 0u;
+  FontDescriptionId fontDescriptionId = 0u;
 
   if(!FindValidatedFont(realFontDescription,
-                        validatedFontId))
+                        fontDescriptionId))
   {
     // Use font config to validate the font's description.
     ValidateFont(realFontDescription,
-                 validatedFontId);
+                 fontDescriptionId);
   }
 
-  FontId fontFaceId = 0u;
-  // Check if exists a pair 'validatedFontId, requestedPointSize' in the cache.
-  if(!FindFont(validatedFontId, requestedPointSize, fontFaceId))
+  FontCacheIndex fontCacheIndex = 0u;
+  // Check if exists a pair 'fontDescriptionId, requestedPointSize' in the cache.
+  if(!FindFont(fontDescriptionId, requestedPointSize, fontCacheIndex))
   {
     // Retrieve the font file name path.
-    const FontDescription& description = *(mFontDescriptionCache.begin() + validatedFontId - 1u);
+    const FontDescription& description = *(mFontDescriptionCache.begin() + fontDescriptionId - 1u);
 
     // Retrieve the font id. Do not cache the description as it has been already cached.
     fontId = GetFontId(description.path,
@@ -928,17 +930,15 @@ FontId FontClient::Plugin::GetFontId(const FontDescription& fontDescription,
                        faceIndex,
                        false);
 
-    fontFaceId                               = mFontIdCache[fontId - 1u].id;
-    mFontFaceCache[fontFaceId].mCharacterSet = FcCharSetCopy(mCharacterSetCache[validatedFontId - 1u]);
+    fontCacheIndex                               = mFontIdCache[fontId - 1u].index;
+    mFontFaceCache[fontCacheIndex].mCharacterSet = FcCharSetCopy(mCharacterSetCache[fontDescriptionId - 1u]);
 
-    // Cache the pair 'validatedFontId, requestedPointSize' to improve the following queries.
-    mFontDescriptionSizeCache.push_back(FontDescriptionSizeCacheItem(validatedFontId,
-                                                                     requestedPointSize,
-                                                                     fontFaceId));
+    // Cache the pair 'fontDescriptionId, requestedPointSize' to improve the following queries.
+    mFontDescriptionSizeCache.emplace(FontDescriptionSizeCacheKey(fontDescriptionId, requestedPointSize), fontCacheIndex);
   }
   else
   {
-    fontId = mFontFaceCache[fontFaceId].mFontId + 1u;
+    fontId = mFontFaceCache[fontCacheIndex].mFontId + 1u;
   }
 
   DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "  font id : %d\n", fontId);
@@ -958,8 +958,8 @@ FontId FontClient::Plugin::GetFontId(const BitmapFont& bitmapFont)
   BitmapFontCacheItem bitmapFontCacheItem(bitmapFont, mFontIdCache.Count());
 
   FontIdCacheItem fontIdCacheItem;
-  fontIdCacheItem.type = FontDescription::BITMAP_FONT;
-  fontIdCacheItem.id   = mBitmapFontCache.size();
+  fontIdCacheItem.type  = FontDescription::BITMAP_FONT;
+  fontIdCacheItem.index = mBitmapFontCache.size();
 
   mBitmapFontCache.push_back(std::move(bitmapFontCacheItem));
   mFontIdCache.PushBack(fontIdCacheItem);
@@ -968,7 +968,7 @@ FontId FontClient::Plugin::GetFontId(const BitmapFont& bitmapFont)
 }
 
 void FontClient::Plugin::ValidateFont(const FontDescription& fontDescription,
-                                      FontDescriptionId&     validatedFontId)
+                                      FontDescriptionId&     fontDescriptionId)
 {
   DALI_LOG_TRACE_METHOD(gFontClientLogFilter);
   FONT_LOG_DESCRIPTION(fontDescription, "");
@@ -989,17 +989,17 @@ void FontClient::Plugin::ValidateFont(const FontDescription& fontDescription,
     mFontDescriptionCache.push_back(description);
 
     // Set the index to the vector of paths to font file names.
-    validatedFontId = mFontDescriptionCache.size();
+    fontDescriptionId = mFontDescriptionCache.size();
 
     FONT_LOG_DESCRIPTION(description, "matched");
-    DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "  validatedFontId : %d\n", validatedFontId);
+    DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "  fontDescriptionId : %d\n", fontDescriptionId);
 
     // The reference counter of the character set has already been increased in MatchFontDescriptionToPattern.
     mCharacterSetCache.PushBack(characterSet);
 
     // Cache the index and the matched font's description.
     FontDescriptionCacheItem item(description,
-                                  validatedFontId);
+                                  fontDescriptionId);
 
     mValidatedFontCache.push_back(std::move(item));
 
@@ -1010,7 +1010,7 @@ void FontClient::Plugin::ValidateFont(const FontDescription& fontDescription,
     {
       // Cache the given font's description if it's different than the matched.
       FontDescriptionCacheItem item(fontDescription,
-                                    validatedFontId);
+                                    fontDescriptionId);
 
       mValidatedFontCache.push_back(std::move(item));
     }
@@ -1109,7 +1109,7 @@ bool FontClient::Plugin::GetVectorMetrics(GlyphInfo* array,
     if((fontId > 0u) &&
        (fontId - 1u) < mFontIdCache.Count())
     {
-      FontFaceCacheItem& font = mFontFaceCache[mFontIdCache[fontId - 1u].id];
+      FontFaceCacheItem& font = mFontFaceCache[mFontIdCache[fontId - 1u].index];
 
       if(!font.mVectorFontId)
       {
@@ -1177,8 +1177,8 @@ void FontClient::Plugin::CreateVectorBlob(FontId fontId, GlyphIndex glyphIndex,
   if((fontId > 0u) &&
      (fontId - 1u < mFontIdCache.Count()))
   {
-    const FontId       fontFaceId = mFontIdCache[fontId - 1u].id;
-    FontFaceCacheItem& font       = mFontFaceCache[fontFaceId];
+    const FontCacheIndex fontFaceId = mFontIdCache[fontId - 1u].index;
+    FontFaceCacheItem&   font       = mFontFaceCache[fontFaceId];
 
     if(!font.mVectorFontId)
     {
@@ -1219,7 +1219,7 @@ const GlyphInfo& FontClient::Plugin::GetEllipsisGlyph(PointSize26Dot6 requestedP
                                       false);
 
   // Set the character index to access the glyph inside the font.
-  item.glyph.index = FT_Get_Char_Index(mFontFaceCache[mFontIdCache[item.glyph.fontId - 1u].id].mFreeTypeFace,
+  item.glyph.index = FT_Get_Char_Index(mFontFaceCache[mFontIdCache[item.glyph.fontId - 1u].index].mFreeTypeFace,
                                        ELLIPSIS_CHARACTER);
 
   GetBitmapMetrics(&item.glyph, 1u, true);
@@ -1632,11 +1632,11 @@ FontId FontClient::Plugin::CreateFont(const FontPath& path,
         fontIdCacheItem.type = FontDescription::FACE_FONT;
 
         // Set the index to the FreeType font face cache.
-        fontIdCacheItem.id = mFontFaceCache.size();
-        fontFaceId         = fontIdCacheItem.id + 1u;
+        fontIdCacheItem.index = mFontFaceCache.size();
+        fontFaceId            = fontIdCacheItem.index + 1u;
 
         // Cache the items.
-        mFontFaceCache.push_back(fontFaceCacheItem);
+        mFontFaceCache.emplace_back(std::move(fontFaceCacheItem));
         mFontIdCache.PushBack(fontIdCacheItem);
 
         // Set the font id to be returned.
@@ -1690,11 +1690,11 @@ FontId FontClient::Plugin::CreateFont(const FontPath& path,
         fontIdCacheItem.type = FontDescription::FACE_FONT;
 
         // Set the index to the FreeType font face cache.
-        fontIdCacheItem.id = mFontFaceCache.size();
-        fontFaceId         = fontIdCacheItem.id + 1u;
+        fontIdCacheItem.index = mFontFaceCache.size();
+        fontFaceId            = fontIdCacheItem.index + 1u;
 
         // Cache the items.
-        mFontFaceCache.push_back(fontFaceCacheItem);
+        mFontFaceCache.emplace_back(std::move(fontFaceCacheItem));
         mFontIdCache.PushBack(fontIdCacheItem);
 
         // Set the font id to be returned.
@@ -1752,13 +1752,13 @@ bool FontClient::Plugin::FindFont(const FontPath& path,
 }
 
 bool FontClient::Plugin::FindValidatedFont(const FontDescription& fontDescription,
-                                           FontDescriptionId&     validatedFontId)
+                                           FontDescriptionId&     fontDescriptionId)
 {
   DALI_LOG_TRACE_METHOD(gFontClientLogFilter);
   FONT_LOG_DESCRIPTION(fontDescription, "");
   DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, "  number of validated fonts in the cache : %d\n", mValidatedFontCache.size());
 
-  validatedFontId = 0u;
+  fontDescriptionId = 0u;
 
   for(const auto& item : mValidatedFontCache)
   {
@@ -1768,14 +1768,14 @@ bool FontClient::Plugin::FindValidatedFont(const FontDescription& fontDescriptio
        (fontDescription.weight == item.fontDescription.weight) &&
        (fontDescription.slant == item.fontDescription.slant))
     {
-      validatedFontId = item.index;
+      fontDescriptionId = item.index;
 
-      DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "  validated font found, id : %d\n", validatedFontId);
+      DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "  validated font description found, id : %d\n", fontDescriptionId);
       return true;
     }
   }
 
-  DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "  validated font not found\n");
+  DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "  validated font description not found\n");
   return false;
 }
 
@@ -1809,26 +1809,25 @@ bool FontClient::Plugin::FindFallbackFontList(const FontDescription& fontDescrip
   return false;
 }
 
-bool FontClient::Plugin::FindFont(FontDescriptionId validatedFontId,
+bool FontClient::Plugin::FindFont(FontDescriptionId fontDescriptionId,
                                   PointSize26Dot6   requestedPointSize,
-                                  FontId&           fontId)
+                                  FontCacheIndex&   fontCacheIndex)
 {
   DALI_LOG_TRACE_METHOD(gFontClientLogFilter);
-  DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "    validatedFontId  : %d\n", validatedFontId);
+  DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "   fontDescriptionId : %d\n", fontDescriptionId);
   DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "  requestedPointSize : %d\n", requestedPointSize);
 
-  fontId = 0u;
+  fontCacheIndex = 0u;
+
+  FontDescriptionSizeCacheKey key(fontDescriptionId, requestedPointSize);
 
-  for(const auto& item : mFontDescriptionSizeCache)
+  const auto& iter = mFontDescriptionSizeCache.find(key);
+  if(iter != mFontDescriptionSizeCache.cend())
   {
-    if((validatedFontId == item.validatedFontId) &&
-       (requestedPointSize == item.requestedPointSize))
-    {
-      fontId = item.fontId;
+    fontCacheIndex = iter->second;
 
-      DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "  font found, id : %d\n", fontId);
-      return true;
-    }
+    DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "  font found, index of font cache : %d\n", fontCacheIndex);
+    return true;
   }
 
   DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "  font not found.\n");
@@ -1984,9 +1983,9 @@ void FontClient::Plugin::CacheFontPath(FT_Face ftFace, FontId id, PointSize26Dot
     description.weight = FontWeight::BOLD;
   }
 
-  FontDescriptionId validatedFontId = 0u;
+  FontDescriptionId fontDescriptionId = 0u;
   if(!FindValidatedFont(description,
-                        validatedFontId))
+                        fontDescriptionId))
   {
     FcPattern* pattern = CreateFontFamilyPattern(description); // Creates a new pattern that needs to be destroyed by calling FcPatternDestroy.
 
@@ -2008,19 +2007,17 @@ void FontClient::Plugin::CacheFontPath(FT_Face ftFace, FontId id, PointSize26Dot
     mFontDescriptionCache.push_back(description);
 
     // Set the index to the vector of paths to font file names.
-    validatedFontId = mFontDescriptionCache.size();
+    fontDescriptionId = mFontDescriptionCache.size();
 
     // Increase the reference counter and add the character set to the cache.
     mCharacterSetCache.PushBack(FcCharSetCopy(characterSet));
 
     // Cache the index and the font's description.
     mValidatedFontCache.push_back(std::move(FontDescriptionCacheItem(std::move(description),
-                                                                     validatedFontId)));
+                                                                     fontDescriptionId)));
 
-    // Cache the pair 'validatedFontId, requestedPointSize' to improve the following queries.
-    mFontDescriptionSizeCache.push_back(FontDescriptionSizeCacheItem(validatedFontId,
-                                                                     requestedPointSize,
-                                                                     fontFaceId));
+    // Cache the pair 'fontDescriptionId, requestedPointSize' to improve the following queries.
+    mFontDescriptionSizeCache.emplace(FontDescriptionSizeCacheKey(fontDescriptionId, requestedPointSize), fontFaceId);
   }
 }
 
index e6eb9866956396ff219dd989251fe64c4b59be4b..97bd7545c7657882ecd9258cf0e2f5bf67af4658 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_INTERNAL_TEXT_ABSTRACTION_FONT_CLIENT_PLUGIN_IMPL_H
 
 /*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -36,6 +36,8 @@ class VectorFontCache;
 #endif
 
 // EXTERNAL INCLUDES
+#include <unordered_map>
+
 #include <ft2build.h>
 #include FT_FREETYPE_H
 #include FT_GLYPH_H
@@ -68,10 +70,17 @@ typedef Vector<_FcCharSet*> CharacterSetList;
  */
 struct FontClient::Plugin
 {
+private:
+  /// Redefine FontId name to specifiy the value's usage
+  using FontCacheIndex = FontId;
+
+  /**
+   * @brief Index of FontCache container.
+   */
   struct FontIdCacheItem
   {
-    FontDescription::Type type; ///< The type of font.
-    FontId                id;   ///< Index to the cache of fonts for the specified type.
+    FontDescription::Type type;  ///< The type of font.
+    FontCacheIndex        index; ///< Index to the cache of fonts for the specified type. Face or Bitmap
   };
 
   /**
@@ -101,25 +110,45 @@ struct FontClient::Plugin
   };
 
   /**
-   * @brief Caches the font id of the pair font point size and the index to the vector of font descriptions of validated fonts.
+   * @brief Pair of FontDescriptionId and PointSize. It will be used to find cached validate font.
    */
-  struct FontDescriptionSizeCacheItem
+  struct FontDescriptionSizeCacheKey
   {
-    FontDescriptionSizeCacheItem(FontDescriptionId validatedFontId,
-                                 PointSize26Dot6   requestedPointSize,
-                                 FontId            fontId);
+    FontDescriptionSizeCacheKey(FontDescriptionId fontDescriptionId,
+                                PointSize26Dot6   requestedPointSize);
 
-    FontDescriptionId validatedFontId;    ///< Index to the vector with font descriptions.
+    FontDescriptionId fontDescriptionId;  ///< Index to the vector with font descriptions.
     PointSize26Dot6   requestedPointSize; ///< The font point size.
-    FontId            fontId;             ///< The font identifier.
+
+    bool operator==(FontDescriptionSizeCacheKey const& rhs) const noexcept
+    {
+      return fontDescriptionId == rhs.fontDescriptionId && requestedPointSize == rhs.requestedPointSize;
+    }
+  };
+
+  /**
+   * @brief Custom hash functions for FontDescriptionSizeCacheKey.
+   */
+  struct FontDescriptionSizeCacheKeyHash
+  {
+    std::size_t operator()(FontDescriptionSizeCacheKey const& key) const noexcept
+    {
+      return key.fontDescriptionId ^ key.requestedPointSize;
+    }
   };
 
+  /**
+   * @brief Caches the font id of the pair font point size and the index to the vector of font descriptions of validated fonts.
+   */
+  using FontDescriptionSizeCacheContainer = std::unordered_map<FontDescriptionSizeCacheKey, FontCacheIndex, FontDescriptionSizeCacheKeyHash>;
+
   struct EllipsisItem
   {
     PointSize26Dot6 requestedPointSize;
     GlyphInfo       glyph;
   };
 
+public:
   /**
    * Constructor.
    *
@@ -489,17 +518,17 @@ private:
 
   /**
    * @brief Finds in the cache a pair 'validated font identifier and font point size'.
-   * If there is one it writes the font identifier in the param @p fontId.
+   * If there is one it writes the font identifier in the param @p fontCacheIndex.
    *
    * @param[in] validatedFontId Index to the vector with font descriptions.
    * @param[in] requestedPointSize The font point size.
-   * @param[out] fontId The font identifier.
+   * @param[out] fontCacheIndex The index of font cache identifier.
    *
    * @return @e true if the pair is found.
    */
   bool FindFont(FontDescriptionId validatedFontId,
                 PointSize26Dot6   requestedPointSize,
-                FontId&           fontId);
+                FontCacheIndex&   fontCacheIndex);
 
   /**
    * @brief Finds in the cache a bitmap font with the @p bitmapFont family name.
@@ -571,12 +600,13 @@ private:
 
   std::vector<FallbackCacheItem> mFallbackCache; ///< Cached fallback font lists.
 
-  Vector<FontIdCacheItem>                   mFontIdCache;
-  std::vector<FontFaceCacheItem>            mFontFaceCache;            ///< Caches the FreeType face and font metrics of the triplet 'path to the font file name, font point size and face index'.
-  std::vector<FontDescriptionCacheItem>     mValidatedFontCache;       ///< Caches indices to the vector of font descriptions for a given font.
-  FontList                                  mFontDescriptionCache;     ///< Caches font descriptions for the validated font.
-  CharacterSetList                          mCharacterSetCache;        ///< Caches character set lists for the validated font.
-  std::vector<FontDescriptionSizeCacheItem> mFontDescriptionSizeCache; ///< Caches font identifiers for the pairs of font point size and the index to the vector with font descriptions of the validated fonts.
+  Vector<FontIdCacheItem>               mFontIdCache;          ///< Caches from FontId to FontCacheIndex.
+  std::vector<FontFaceCacheItem>        mFontFaceCache;        ///< Caches the FreeType face and font metrics of the triplet 'path to the font file name, font point size and face index'.
+  std::vector<FontDescriptionCacheItem> mValidatedFontCache;   ///< Caches indices to the vector of font descriptions for a given font.
+  FontList                              mFontDescriptionCache; ///< Caches font descriptions for the validated font.
+  CharacterSetList                      mCharacterSetCache;    ///< Caches character set lists for the validated font.
+
+  FontDescriptionSizeCacheContainer mFontDescriptionSizeCache; ///< Caches font identifiers for the pairs of font point size and the index to the vector with font descriptions of the validated fonts.
 
   VectorFontCache* mVectorFontCache; ///< Separate cache for vector data blobs etc.
 
index 92a1ff200883ed38fd663a26f49712e961430776..df8c735d7b594a828a2066ae3d48ccb910aa50cb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * limitations under the License.
  */
 
+// EXTERNAL HEADERS
 #include <dali/integration-api/debug.h>
+
+// INTERNAL HEADERS
+#include <dali/devel-api/adaptor-framework/environment-variable.h>
 #include <dali/internal/text/text-abstraction/plugin/font-client-utils.h>
 #include <dali/internal/text/text-abstraction/plugin/font-face-cache-item.h>
 
@@ -24,9 +28,34 @@ extern Dali::Integration::Log::Filter* gFontClientLogFilter;
 
 namespace Dali::TextAbstraction::Internal
 {
+namespace
+{
 const float FROM_266        = 1.0f / 64.0f;
 const float POINTS_PER_INCH = 72.f;
 
+/**
+ * @brief Maximum size of glyph cache per each font face.
+ */
+constexpr std::size_t DEFAULT_GLYPH_CACHE_MAX         = 128;
+constexpr std::size_t MINIMUM_SIZE_OF_GLYPH_CACHE_MAX = 2u;
+
+constexpr auto MAX_NUMBER_OF_GLYPH_CACHE_ENV = "DALI_GLYPH_CACHE_MAX";
+
+/**
+ * @brief Get maximum size of glyph cache size from environment.
+ * If not settuped, default as 128.
+ * @note This value fixed when we call it first time.
+ * @return The max size of glyph cache.
+ */
+size_t GetMaxNumberOfGlyphCache()
+{
+  using Dali::EnvironmentVariable::GetEnvironmentVariable;
+  static auto numberString = GetEnvironmentVariable(MAX_NUMBER_OF_GLYPH_CACHE_ENV);
+  static auto number       = numberString ? std::strtoul(numberString, nullptr, 10) : DEFAULT_GLYPH_CACHE_MAX;
+  return (number < MINIMUM_SIZE_OF_GLYPH_CACHE_MAX) ? MINIMUM_SIZE_OF_GLYPH_CACHE_MAX : number;
+}
+} // namespace
+
 FontFaceCacheItem::FontFaceCacheItem(FT_Library&        freeTypeLibrary,
                                      FT_Face            ftFace,
                                      const FontPath&    path,
@@ -35,6 +64,7 @@ FontFaceCacheItem::FontFaceCacheItem(FT_Library&        freeTypeLibrary,
                                      const FontMetrics& metrics)
 : mFreeTypeLibrary(freeTypeLibrary),
   mFreeTypeFace(ftFace),
+  mGlyphCacheManager(new GlyphCacheManager(mFreeTypeFace, GetMaxNumberOfGlyphCache())),
   mPath(path),
   mRequestedPointSize(requestedPointSize),
   mFaceIndex(face),
@@ -62,6 +92,7 @@ FontFaceCacheItem::FontFaceCacheItem(FT_Library&        freeTypeLibrary,
                                      bool               hasColorTables)
 : mFreeTypeLibrary(freeTypeLibrary),
   mFreeTypeFace(ftFace),
+  mGlyphCacheManager(new GlyphCacheManager(mFreeTypeFace, GetMaxNumberOfGlyphCache())),
   mPath(path),
   mRequestedPointSize(requestedPointSize),
   mFaceIndex(face),
@@ -77,6 +108,45 @@ FontFaceCacheItem::FontFaceCacheItem(FT_Library&        freeTypeLibrary,
 {
 }
 
+// Move constructor. font client plugin container may call this.
+// Note that we make nullptr of some reference sensitive values here.
+FontFaceCacheItem::FontFaceCacheItem(FontFaceCacheItem&& rhs)
+: mFreeTypeLibrary(rhs.mFreeTypeLibrary)
+{
+  mFreeTypeFace       = rhs.mFreeTypeFace;
+  mGlyphCacheManager  = rhs.mGlyphCacheManager;
+  mPath               = std::move(rhs.mPath);
+  mRequestedPointSize = rhs.mRequestedPointSize;
+  mFaceIndex          = rhs.mFaceIndex;
+  mMetrics            = rhs.mMetrics;
+  mCharacterSet       = rhs.mCharacterSet;
+  mFixedSizeIndex     = rhs.mFixedSizeIndex;
+  mFixedWidthPixels   = rhs.mFixedWidthPixels;
+  mFixedHeightPixels  = rhs.mFixedWidthPixels;
+  mVectorFontId       = rhs.mVectorFontId;
+  mFontId             = rhs.mFontId;
+  mIsFixedSizeBitmap  = rhs.mIsFixedSizeBitmap;
+  mHasColorTables     = rhs.mHasColorTables;
+
+  rhs.mGlyphCacheManager = nullptr;
+  rhs.mFreeTypeFace      = nullptr;
+}
+
+FontFaceCacheItem::~FontFaceCacheItem()
+{
+  // delete glyph cache manager before free face.
+  if(mGlyphCacheManager)
+  {
+    delete mGlyphCacheManager;
+  }
+
+  // Free face.
+  if(mFreeTypeFace)
+  {
+    FT_Done_Face(mFreeTypeFace);
+  }
+}
+
 void FontFaceCacheItem::GetFontMetrics(FontMetrics& metrics, unsigned int dpiVertical) const
 {
   metrics = mMetrics;
@@ -99,32 +169,36 @@ void FontFaceCacheItem::GetFontMetrics(FontMetrics& metrics, unsigned int dpiVer
   }
 }
 
-bool FontFaceCacheItem::GetGlyphMetrics(GlyphInfo& glyph, unsigned int dpiVertical, bool horizontal) const
+bool FontFaceCacheItem::GetGlyphMetrics(GlyphInfo& glyphInfo, unsigned int dpiVertical, bool horizontal) const
 {
   bool success(true);
 
-  FT_Face ftFace = mFreeTypeFace;
+  GlyphCacheManager::GlyphCacheData glyphData;
+  FT_Error                          error;
 
 #ifdef FREETYPE_BITMAP_SUPPORT
   // Check to see if we should be loading a Fixed Size bitmap?
   if(mIsFixedSizeBitmap)
   {
-    FT_Select_Size(ftFace, mFixedSizeIndex); ///< @todo: needs to be investigated why it's needed to select the size again.
-    int error = FT_Load_Glyph(ftFace, glyph.index, FT_LOAD_COLOR);
+    FT_Select_Size(mFreeTypeFace, mFixedSizeIndex); ///< @todo: needs to be investigated why it's needed to select the size again.
+    mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphInfo.index, FT_LOAD_COLOR, false, glyphData, error);
+
     if(FT_Err_Ok == error)
     {
-      glyph.width    = mFixedWidthPixels;
-      glyph.height   = mFixedHeightPixels;
-      glyph.advance  = mFixedWidthPixels;
-      glyph.xBearing = 0.0f;
+      glyphInfo.width    = mFixedWidthPixels;
+      glyphInfo.height   = mFixedHeightPixels;
+      glyphInfo.advance  = mFixedWidthPixels;
+      glyphInfo.xBearing = 0.0f;
+
+      const auto& metrics = glyphData.mGlyphMetrics;
 
       if(horizontal)
       {
-        glyph.yBearing += static_cast<float>(ftFace->glyph->metrics.horiBearingY) * FROM_266;
+        glyphInfo.yBearing += static_cast<float>(metrics.horiBearingY) * FROM_266;
       }
       else
       {
-        glyph.yBearing += static_cast<float>(ftFace->glyph->metrics.vertBearingY) * FROM_266;
+        glyphInfo.yBearing += static_cast<float>(metrics.vertBearingY) * FROM_266;
       }
 
       // Adjust the metrics if the fixed-size font should be down-scaled
@@ -133,13 +207,13 @@ bool FontFaceCacheItem::GetGlyphMetrics(GlyphInfo& glyph, unsigned int dpiVertic
       if(desiredFixedSize > 0.f)
       {
         const float scaleFactor = desiredFixedSize / mFixedHeightPixels;
-        glyph.width             = round(glyph.width * scaleFactor);
-        glyph.height            = round(glyph.height * scaleFactor);
-        glyph.advance           = round(glyph.advance * scaleFactor);
-        glyph.xBearing          = round(glyph.xBearing * scaleFactor);
-        glyph.yBearing          = round(glyph.yBearing * scaleFactor);
+        glyphInfo.width         = round(glyphInfo.width * scaleFactor);
+        glyphInfo.height        = round(glyphInfo.height * scaleFactor);
+        glyphInfo.advance       = round(glyphInfo.advance * scaleFactor);
+        glyphInfo.xBearing      = round(glyphInfo.xBearing * scaleFactor);
+        glyphInfo.yBearing      = round(glyphInfo.yBearing * scaleFactor);
 
-        glyph.scaleFactor = scaleFactor;
+        glyphInfo.scaleFactor = scaleFactor;
       }
     }
     else
@@ -154,60 +228,59 @@ bool FontFaceCacheItem::GetGlyphMetrics(GlyphInfo& glyph, unsigned int dpiVertic
     // FT_LOAD_DEFAULT causes some issues in the alignment of the glyph inside the bitmap.
     // i.e. with the SNum-3R font.
     // @todo: add an option to use the FT_LOAD_DEFAULT if required?
-    int error = FT_Load_Glyph(ftFace, glyph.index, FT_LOAD_NO_AUTOHINT);
+    mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphInfo.index, FT_LOAD_NO_AUTOHINT, glyphInfo.isBoldRequired, glyphData, error);
 
     // Keep the width of the glyph before doing the software emboldening.
     // It will be used to calculate a scale factor to be applied to the
     // advance as Harfbuzz doesn't apply any SW emboldening to calculate
     // the advance of the glyph.
-    const float width = static_cast<float>(ftFace->glyph->metrics.width) * FROM_266;
 
     if(FT_Err_Ok == error)
     {
-      const bool isEmboldeningRequired = glyph.isBoldRequired && !(ftFace->style_flags & FT_STYLE_FLAG_BOLD);
-      if(isEmboldeningRequired)
-      {
-        // Does the software bold.
-        FT_GlyphSlot_Embolden(ftFace->glyph);
-      }
+      const auto& metrics = glyphData.mGlyphMetrics;
 
-      glyph.width  = static_cast<float>(ftFace->glyph->metrics.width) * FROM_266;
-      glyph.height = static_cast<float>(ftFace->glyph->metrics.height) * FROM_266;
+      glyphInfo.width  = static_cast<float>(metrics.width) * FROM_266;
+      glyphInfo.height = static_cast<float>(metrics.height) * FROM_266;
       if(horizontal)
       {
-        glyph.xBearing += static_cast<float>(ftFace->glyph->metrics.horiBearingX) * FROM_266;
-        glyph.yBearing += static_cast<float>(ftFace->glyph->metrics.horiBearingY) * FROM_266;
+        glyphInfo.xBearing += static_cast<float>(metrics.horiBearingX) * FROM_266;
+        glyphInfo.yBearing += static_cast<float>(metrics.horiBearingY) * FROM_266;
       }
       else
       {
-        glyph.xBearing += static_cast<float>(ftFace->glyph->metrics.vertBearingX) * FROM_266;
-        glyph.yBearing += static_cast<float>(ftFace->glyph->metrics.vertBearingY) * FROM_266;
+        glyphInfo.xBearing += static_cast<float>(metrics.vertBearingX) * FROM_266;
+        glyphInfo.yBearing += static_cast<float>(metrics.vertBearingY) * FROM_266;
       }
 
-      if(isEmboldeningRequired && !Dali::EqualsZero(width))
+      const bool isEmboldeningRequired = glyphInfo.isBoldRequired && !(glyphData.mStyleFlags & FT_STYLE_FLAG_BOLD);
+      if(isEmboldeningRequired)
       {
-        // If the glyph is emboldened by software, the advance is multiplied by a
-        // scale factor to make it slightly bigger.
-        glyph.advance *= (glyph.width / width);
+        // Get dummy glyph data without embolden.
+        GlyphCacheManager::GlyphCacheData dummyData;
+        if(mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphInfo.index, FT_LOAD_NO_AUTOHINT, false, dummyData, error))
+        {
+          // If the glyph is emboldened by software, the advance is multiplied by a
+          // scale factor to make it slightly bigger.
+          const float width = static_cast<float>(dummyData.mGlyphMetrics.width) * FROM_266;
+          if(!EqualsZero(width))
+          {
+            glyphInfo.advance *= (glyphInfo.width / width);
+          }
+        }
       }
 
       // Use the bounding box of the bitmap to correct the metrics.
       // For some fonts i.e the SNum-3R the metrics need to be corrected,
       // otherwise the glyphs 'dance' up and down depending on the
       // font's point size.
-
-      FT_Glyph ftGlyph;
-      error = FT_Get_Glyph(ftFace->glyph, &ftGlyph);
+      FT_Glyph glyph = glyphData.mGlyph;
 
       FT_BBox bbox;
-      FT_Glyph_Get_CBox(ftGlyph, FT_GLYPH_BBOX_GRIDFIT, &bbox);
+      FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_GRIDFIT, &bbox);
 
-      const float descender = glyph.height - glyph.yBearing;
-      glyph.height          = (bbox.yMax - bbox.yMin) * FROM_266;
-      glyph.yBearing        = glyph.height - round(descender);
-
-      // Created FT_Glyph object must be released with FT_Done_Glyph
-      FT_Done_Glyph(ftGlyph);
+      const float descender = glyphInfo.height - glyphInfo.yBearing;
+      glyphInfo.height      = (bbox.yMax - bbox.yMin) * FROM_266;
+      glyphInfo.yBearing    = glyphInfo.height - round(descender);
     }
     else
     {
@@ -229,8 +302,8 @@ bool FontFaceCacheItem::GetGlyphMetrics(GlyphInfo& glyph, unsigned int dpiVertic
 void FontFaceCacheItem::CreateBitmap(
   GlyphIndex glyphIndex, Dali::TextAbstraction::FontClient::GlyphBufferData& data, int outlineWidth, bool isItalicRequired, bool isBoldRequired) const
 {
-  FT_Face  ftFace = mFreeTypeFace;
-  FT_Error error;
+  GlyphCacheManager::GlyphCacheData glyphData;
+  FT_Error                          error;
   // For the software italics.
   bool isShearRequired = false;
 
@@ -238,7 +311,7 @@ void FontFaceCacheItem::CreateBitmap(
   // Check to see if this is fixed size bitmap
   if(mIsFixedSizeBitmap)
   {
-    error = FT_Load_Glyph(ftFace, glyphIndex, FT_LOAD_COLOR);
+    mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphIndex, FT_LOAD_COLOR, isBoldRequired, glyphData, error);
   }
   else
 #endif
@@ -246,111 +319,109 @@ void FontFaceCacheItem::CreateBitmap(
     // FT_LOAD_DEFAULT causes some issues in the alignment of the glyph inside the bitmap.
     // i.e. with the SNum-3R font.
     // @todo: add an option to use the FT_LOAD_DEFAULT if required?
-    error = FT_Load_Glyph(ftFace, glyphIndex, FT_LOAD_NO_AUTOHINT);
+    mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphIndex, FT_LOAD_NO_AUTOHINT, isBoldRequired, glyphData, error);
   }
   if(FT_Err_Ok == error)
   {
-    if(isBoldRequired && !(ftFace->style_flags & FT_STYLE_FLAG_BOLD))
-    {
-      // Does the software bold.
-      FT_GlyphSlot_Embolden(ftFace->glyph);
-    }
-
-    if(isItalicRequired && !(ftFace->style_flags & FT_STYLE_FLAG_ITALIC))
+    if(isItalicRequired && !(glyphData.mStyleFlags & FT_STYLE_FLAG_ITALIC))
     {
       // Will do the software italic.
       isShearRequired = true;
     }
 
-    FT_Glyph glyph;
-    error = FT_Get_Glyph(ftFace->glyph, &glyph);
-
     // Convert to bitmap if necessary
-    if(FT_Err_Ok == error)
+    if(!glyphData.mIsBitmap)
     {
-      if(glyph->format != FT_GLYPH_FORMAT_BITMAP)
-      {
-        int  offsetX = 0, offsetY = 0;
-        bool isOutlineGlyph = (glyph->format == FT_GLYPH_FORMAT_OUTLINE && outlineWidth > 0);
+      FT_Glyph glyph = glyphData.mGlyph;
 
-        // Create a bitmap for the outline
-        if(isOutlineGlyph)
+      DALI_ASSERT_ALWAYS(glyph->format != FT_GLYPH_FORMAT_BITMAP && "Something wrong with cashing. Some bitmap glyph cached failed.");
+
+      int  offsetX = 0, offsetY = 0;
+      bool isOutlineGlyph       = (glyph->format == FT_GLYPH_FORMAT_OUTLINE && outlineWidth > 0);
+      bool isStrokeGlyphSuccess = false;
+
+      // Create a bitmap for the outline
+      if(isOutlineGlyph)
+      {
+        // Retrieve the horizontal and vertical distance from the current pen position to the
+        // left and top border of the glyph bitmap for a normal glyph before applying the outline.
+        if(FT_Err_Ok == error)
         {
-          // Retrieve the horizontal and vertical distance from the current pen position to the
-          // left and top border of the glyph bitmap for a normal glyph before applying the outline.
+          // Copy new glyph, and keep original cached glyph.
+          error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 0);
           if(FT_Err_Ok == error)
           {
-            FT_Glyph normalGlyph;
-            error = FT_Get_Glyph(ftFace->glyph, &normalGlyph);
-
-            error = FT_Glyph_To_Bitmap(&normalGlyph, FT_RENDER_MODE_NORMAL, 0, 1);
-            if(FT_Err_Ok == error)
-            {
-              FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(normalGlyph);
+            FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(glyph);
 
-              offsetX = bitmapGlyph->left;
-              offsetY = bitmapGlyph->top;
-            }
+            offsetX = bitmapGlyph->left;
+            offsetY = bitmapGlyph->top;
 
-            // Created FT_Glyph object must be released with FT_Done_Glyph
-            FT_Done_Glyph(normalGlyph);
+            // Copied FT_Glyph object must be released with FT_Done_Glyph
+            FT_Done_Glyph(glyph);
           }
 
-          // Now apply the outline
+          // Replace as original glyph
+          glyph = glyphData.mGlyph;
+        }
 
-          // Set up a stroker
-          FT_Stroker stroker;
-          error = FT_Stroker_New(mFreeTypeLibrary, &stroker);
+        // Now apply the outline
+
+        // Set up a stroker
+        FT_Stroker stroker;
+        error = FT_Stroker_New(mFreeTypeLibrary, &stroker);
+
+        if(FT_Err_Ok == error)
+        {
+          // Copy glyph pointer for release memory.
+          FT_Stroker_Set(stroker, outlineWidth * 64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
+          error = FT_Glyph_StrokeBorder(&glyph, stroker, 0, 0);
 
           if(FT_Err_Ok == error)
           {
-            FT_Stroker_Set(stroker, outlineWidth * 64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
-            error = FT_Glyph_StrokeBorder(&glyph, stroker, 0, 1);
-
-            if(FT_Err_Ok == error)
-            {
-              FT_Stroker_Done(stroker);
-            }
-            else
-            {
-              DALI_LOG_ERROR("FT_Glyph_StrokeBorder Failed with error: %d\n", error);
-            }
+            FT_Stroker_Done(stroker);
+            isStrokeGlyphSuccess = true;
           }
           else
           {
-            DALI_LOG_ERROR("FT_Stroker_New Failed with error: %d\n", error);
+            DALI_LOG_ERROR("FT_Glyph_StrokeBorder Failed with error: %d\n", error);
           }
         }
-
-        error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1);
-        if(FT_Err_Ok == error)
+        else
         {
-          FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(glyph);
+          DALI_LOG_ERROR("FT_Stroker_New Failed with error: %d\n", error);
+        }
+      }
 
-          if(isOutlineGlyph)
-          {
-            // Calculate the additional horizontal and vertical offsets needed for the position of the outline glyph
-            data.outlineOffsetX = offsetX - bitmapGlyph->left - outlineWidth;
-            data.outlineOffsetY = bitmapGlyph->top - offsetY - outlineWidth;
-          }
+      // Copy new glyph, and keep original cached glyph.
+      // If we already copy new glyph by stroke, just re-use that.
+      error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, isStrokeGlyphSuccess);
+      if(FT_Err_Ok == error)
+      {
+        FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(glyph);
 
-          ConvertBitmap(data, bitmapGlyph->bitmap, isShearRequired);
-        }
-        else
+        if(isOutlineGlyph)
         {
-          DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::CreateBitmap. FT_Get_Glyph Failed with error: %d\n", error);
+          // Calculate the additional horizontal and vertical offsets needed for the position of the outline glyph
+          data.outlineOffsetX = offsetX - bitmapGlyph->left - outlineWidth;
+          data.outlineOffsetY = bitmapGlyph->top - offsetY - outlineWidth;
         }
+
+        ConvertBitmap(data, bitmapGlyph->bitmap, isShearRequired);
+
+        // Copied FT_Glyph object must be released with FT_Done_Glyph
+        FT_Done_Glyph(glyph);
       }
       else
       {
-        ConvertBitmap(data, ftFace->glyph->bitmap, isShearRequired);
+        DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::CreateBitmap. FT_Get_Glyph Failed with error: %d\n", error);
       }
-
-      data.isColorEmoji = mIsFixedSizeBitmap;
-
-      // Created FT_Glyph object must be released with FT_Done_Glyph
-      FT_Done_Glyph(glyph);
     }
+    else
+    {
+      ConvertBitmap(data, *glyphData.mBitmap, isShearRequired);
+    }
+
+    data.isColorEmoji = mIsFixedSizeBitmap;
   }
   else
   {
@@ -366,7 +437,8 @@ bool FontFaceCacheItem::IsColorGlyph(GlyphIndex glyphIndex) const
   // Check to see if this is fixed size bitmap
   if(mHasColorTables)
   {
-    error = FT_Load_Glyph(mFreeTypeFace, glyphIndex, FT_LOAD_COLOR);
+    GlyphCacheManager::GlyphCacheData dummyData;
+    mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphIndex, FT_LOAD_COLOR, false, dummyData, error);
   }
 #endif
   return FT_Err_Ok == error;
index cf75b27787445840cdcff4faaa927f880d596499..e1e0c6e180bde809153c46d71520aaf7d22b30de 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_TEST_ABSTRACTION_INTERNAL_FONT_FACE_CACHE_ITEM_H
 
 /*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,8 +18,8 @@
  */
 
 // INTERNAL INCLUDES
-
 #include <dali/internal/text/text-abstraction/plugin/font-cache-item-interface.h>
+#include <dali/internal/text/text-abstraction/plugin/font-face-glyph-cache-manager.h>
 
 // EXTERNAL INCLUDES
 #include <fontconfig/fontconfig.h>
@@ -57,6 +57,11 @@ struct FontFaceCacheItem : public FontCacheItemInterface
                     float              fixedHeight,
                     bool               hasColorTables);
 
+  FontFaceCacheItem(const FontFaceCacheItem& rhs) = delete; // Do not use copy construct
+  FontFaceCacheItem(FontFaceCacheItem&& rhs);
+
+  ~FontFaceCacheItem();
+
   /**
    * @copydoc FontCacheItemInterface::GetFontMetrics()
    */
@@ -65,7 +70,7 @@ struct FontFaceCacheItem : public FontCacheItemInterface
   /**
    * @copydoc FontCacheItemInterface::GetGlyphMetrics()
    */
-  bool GetGlyphMetrics(GlyphInfo& glyph, unsigned int dpiVertical, bool horizontal) const override;
+  bool GetGlyphMetrics(GlyphInfo& glyphInfo, unsigned int dpiVertical, bool horizontal) const override;
 
   /**
    * @copydoc FontCacheItemInterface::CreateBitmap()
@@ -116,20 +121,21 @@ struct FontFaceCacheItem : public FontCacheItemInterface
     return (0u != (mFreeTypeFace->style_flags & FT_STYLE_FLAG_ITALIC));
   }
 
-  FT_Library&     mFreeTypeLibrary;       ///< A handle to a FreeType library instance.
-  FT_Face         mFreeTypeFace;          ///< The FreeType face.
-  FontPath        mPath;                  ///< The path to the font file name.
-  PointSize26Dot6 mRequestedPointSize;    ///< The font point size.
-  FaceIndex       mFaceIndex;             ///< The face index.
-  FontMetrics     mMetrics;               ///< The font metrics.
-  _FcCharSet*     mCharacterSet;          ///< Pointer with the range of characters.
-  int             mFixedSizeIndex;        ///< Index to the fixed size table for the requested size.
-  float           mFixedWidthPixels;      ///< The height in pixels (fixed size bitmaps only)
-  float           mFixedHeightPixels;     ///< The height in pixels (fixed size bitmaps only)
-  unsigned int    mVectorFontId;          ///< The ID of the equivalent vector-based font
-  FontId          mFontId;                ///< Index to the vector with the cache of font's ids.
-  bool            mIsFixedSizeBitmap : 1; ///< Whether the font has fixed size bitmaps.
-  bool            mHasColorTables : 1;    ///< Whether the font has color tables.
+  FT_Library&        mFreeTypeLibrary;       ///< A handle to a FreeType library instance.
+  FT_Face            mFreeTypeFace;          ///< The FreeType face.
+  GlyphCacheManager* mGlyphCacheManager;     ///< The glyph cache manager. It will cache this face's glyphs.
+  FontPath           mPath;                  ///< The path to the font file name.
+  PointSize26Dot6    mRequestedPointSize;    ///< The font point size.
+  FaceIndex          mFaceIndex;             ///< The face index.
+  FontMetrics        mMetrics;               ///< The font metrics.
+  _FcCharSet*        mCharacterSet;          ///< Pointer with the range of characters.
+  int                mFixedSizeIndex;        ///< Index to the fixed size table for the requested size.
+  float              mFixedWidthPixels;      ///< The height in pixels (fixed size bitmaps only)
+  float              mFixedHeightPixels;     ///< The height in pixels (fixed size bitmaps only)
+  unsigned int       mVectorFontId;          ///< The ID of the equivalent vector-based font
+  FontId             mFontId;                ///< Index to the vector with the cache of font's ids.
+  bool               mIsFixedSizeBitmap : 1; ///< Whether the font has fixed size bitmaps.
+  bool               mHasColorTables : 1;    ///< Whether the font has color tables.
 };
 
 } // namespace Dali::TextAbstraction::Internal
diff --git a/dali/internal/text/text-abstraction/plugin/font-face-glyph-cache-manager.cpp b/dali/internal/text/text-abstraction/plugin/font-face-glyph-cache-manager.cpp
new file mode 100644 (file)
index 0000000..23f0bbd
--- /dev/null
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <dali/integration-api/debug.h>
+#include <dali/internal/text/text-abstraction/plugin/font-client-utils.h>
+#include <dali/internal/text/text-abstraction/plugin/font-face-glyph-cache-manager.h>
+#include FT_BITMAP_H
+
+#if defined(DEBUG_ENABLED)
+extern Dali::Integration::Log::Filter* gFontClientLogFilter;
+#endif
+
+namespace Dali::TextAbstraction::Internal
+{
+namespace
+{
+} // namespace
+
+GlyphCacheManager::GlyphCacheManager(FT_Face ftFace, std::size_t maxNumberOfGlyphCache)
+: mFreeTypeFace(ftFace),
+  mGlyphCacheMaxSize(maxNumberOfGlyphCache),
+  mLRUGlyphCache(mGlyphCacheMaxSize)
+{
+  DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, "FontClient::Plugin::GlyphCacheManager Create with maximum size : %d\n", static_cast<int>(mGlyphCacheMaxSize));
+}
+GlyphCacheManager::~GlyphCacheManager()
+{
+  while(!mLRUGlyphCache.IsEmpty())
+  {
+    auto removedData = mLRUGlyphCache.Pop();
+
+    // Release Glyph data resource
+    removedData.ReleaseGlyphData();
+  }
+  mLRUGlyphCache.Clear();
+}
+
+bool GlyphCacheManager::GetGlyphCacheDataFromIndex(
+  const GlyphIndex& index,
+  const FT_Int32&   flag,
+  const bool&       isBoldRequired,
+  GlyphCacheData&   glyphData,
+  FT_Error&         error)
+{
+  // Append some error value here instead of FT_Err_Ok.
+  error = static_cast<FT_Error>(-1);
+
+  const GlyphCacheKey key  = GlyphCacheKey(index, flag, isBoldRequired);
+  auto                iter = mLRUGlyphCache.Find(key);
+
+  if(iter == mLRUGlyphCache.End())
+  {
+    // If cache size is full, remove oldest glyph.
+    if(mLRUGlyphCache.IsFull())
+    {
+      auto removedData = mLRUGlyphCache.Pop();
+
+      DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, "FontClient::Plugin::GlyphCacheManager::GetGlyphCacheDataFromIndex. Remove oldest cache for glyph : %p\n", removedData.mGlyph);
+
+      // Release Glyph data resource
+      removedData.ReleaseGlyphData();
+    }
+
+    const bool loadSuccess = LoadGlyphDataFromIndex(index, flag, isBoldRequired, glyphData, error);
+    if(loadSuccess)
+    {
+      // Copy and cached data.
+      mLRUGlyphCache.Push(key, glyphData);
+
+      DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, "FontClient::Plugin::GlyphCacheManager::GetGlyphCacheDataFromIndex. Create cache for index : %u flag : %d isBold : %d isBitmap : %d, glyph : %p\n", index, static_cast<int>(flag), isBoldRequired, glyphData.mIsBitmap, glyphData.mGlyph);
+    }
+
+    return loadSuccess;
+  }
+  else
+  {
+    error = FT_Err_Ok;
+
+    // We already notify that we use this glyph. And now, copy cached data.
+    glyphData = iter->element;
+
+    DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, "FontClient::Plugin::GlyphCacheManager::GetGlyphCacheDataFromIndex. Find cache for index : %u flag : %d isBold : %d isBitmap : %d, glyph : %p\n", index, static_cast<int>(flag), isBoldRequired, glyphData.mIsBitmap, glyphData.mGlyph);
+    return true;
+  }
+}
+
+bool GlyphCacheManager::LoadGlyphDataFromIndex(
+  const GlyphIndex& index,
+  const FT_Int32&   flag,
+  const bool&       isBoldRequired,
+  GlyphCacheData&   glyphData,
+  FT_Error&         error)
+{
+  error = FT_Load_Glyph(mFreeTypeFace, index, flag);
+  if(FT_Err_Ok == error)
+  {
+    glyphData.mStyleFlags = mFreeTypeFace->style_flags;
+
+    const bool isEmboldeningRequired = isBoldRequired && !(glyphData.mStyleFlags & FT_STYLE_FLAG_BOLD);
+    if(isEmboldeningRequired)
+    {
+      // Does the software bold.
+      FT_GlyphSlot_Embolden(mFreeTypeFace->glyph);
+    }
+
+    glyphData.mGlyphMetrics = mFreeTypeFace->glyph->metrics;
+    glyphData.mIsBitmap     = false;
+    // Load glyph
+    error = FT_Get_Glyph(mFreeTypeFace->glyph, &glyphData.mGlyph);
+
+    if(glyphData.mGlyph->format == FT_GLYPH_FORMAT_BITMAP)
+    {
+      // Copy original glyph infomation. Due to we use union, we should keep original handle.
+      FT_Glyph bitmapGlyph = glyphData.mGlyph;
+
+      // Copy rendered bitmap
+      // TODO : Is there any way to keep bitmap buffer without copy?
+      glyphData.mBitmap  = new FT_Bitmap();
+      *glyphData.mBitmap = mFreeTypeFace->glyph->bitmap;
+
+      // New allocate buffer
+      size_t bufferSize = 0;
+      switch(glyphData.mBitmap->pixel_mode)
+      {
+        case FT_PIXEL_MODE_GRAY:
+        {
+          if(glyphData.mBitmap->pitch == static_cast<int>(glyphData.mBitmap->width))
+          {
+            bufferSize = static_cast<size_t>(glyphData.mBitmap->width) * static_cast<size_t>(glyphData.mBitmap->rows);
+          }
+          break;
+        }
+#ifdef FREETYPE_BITMAP_SUPPORT
+        case FT_PIXEL_MODE_BGRA:
+        {
+          if(glyphData.mBitmap->pitch == static_cast<int>(glyphData.mBitmap->width << 2u))
+          {
+            bufferSize = (static_cast<size_t>(glyphData.mBitmap->width) * static_cast<size_t>(glyphData.mBitmap->rows)) << 2u;
+          }
+          break;
+        }
+#endif
+        default:
+        {
+          DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GlyphCacheManager::LoadGlyphDataFromIndex. FontClient Unable to create Bitmap of this PixelType\n");
+          break;
+        }
+      }
+
+      if(bufferSize > 0)
+      {
+        glyphData.mIsBitmap       = true;
+        glyphData.mBitmap->buffer = new uint8_t[bufferSize];
+        memcpy(glyphData.mBitmap->buffer, mFreeTypeFace->glyph->bitmap.buffer, bufferSize);
+      }
+      else
+      {
+        DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GlyphCacheManager::LoadGlyphDataFromIndex. Bitmap glyph buffer size is zero\n");
+        delete glyphData.mBitmap;
+        glyphData.mBitmap = nullptr;
+        error             = static_cast<FT_Error>(-1);
+      }
+
+      // Release glyph data.
+      FT_Done_Glyph(bitmapGlyph);
+    }
+
+    if(FT_Err_Ok == error)
+    {
+      return true;
+    }
+  }
+  return false;
+}
+
+void GlyphCacheManager::GlyphCacheData::ReleaseGlyphData()
+{
+  if(mIsBitmap && mBitmap)
+  {
+    // Created FT_Bitmap object must be released with FT_Bitmap_Done
+    delete[] mBitmap->buffer;
+    delete mBitmap;
+    mBitmap = nullptr;
+  }
+  else if(mGlyph)
+  {
+    // Created FT_Glyph object must be released with FT_Done_Glyph
+    FT_Done_Glyph(mGlyph);
+    mGlyph = nullptr;
+  }
+}
+
+} // namespace Dali::TextAbstraction::Internal
diff --git a/dali/internal/text/text-abstraction/plugin/font-face-glyph-cache-manager.h b/dali/internal/text/text-abstraction/plugin/font-face-glyph-cache-manager.h
new file mode 100644 (file)
index 0000000..beba02f
--- /dev/null
@@ -0,0 +1,167 @@
+#ifndef DALI_TEST_ABSTRACTION_INTERNAL_FONT_FACE_GLYPH_CACHE_MANAGER_H
+#define DALI_TEST_ABSTRACTION_INTERNAL_FONT_FACE_GLYPH_CACHE_MANAGER_H
+
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// INTERNAL INCLUDES
+#include <dali/internal/text/text-abstraction/plugin/lru-cache-container.h>
+
+// EXTERNAL INCLUDES
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_GLYPH_H
+
+namespace Dali::TextAbstraction::Internal
+{
+/**
+ * @brief Helper class to load and cache some glyphs of FT_Face.
+ */
+class GlyphCacheManager
+{
+public:
+  // Constructor
+  GlyphCacheManager(FT_Face ftFace, std::size_t maxNumberOfGlyphCache);
+
+  // Destructor
+  ~GlyphCacheManager();
+
+  GlyphCacheManager(const GlyphCacheManager& rhs) = delete; // Do not use copy construct
+  GlyphCacheManager(GlyphCacheManager&& rhs)      = delete; // Do not use move construct
+
+public:
+  // Public struct area.
+
+  /**
+   * @brief Result informations of glyph. It can be whether FT_Glyph or FT_Bitmap type.
+   * @note FontFaceCacheItem could use this.
+   */
+  struct GlyphCacheData
+  {
+    GlyphCacheData()
+    : mGlyph{nullptr}
+    {
+    }
+
+    union
+    {
+      FT_Glyph   mGlyph;
+      FT_Bitmap* mBitmap;
+    };
+    FT_Glyph_Metrics_ mGlyphMetrics{}; // Get from FT_GlyphSlot
+    FT_Int32          mStyleFlags{0};  // Get from FT_Face
+    bool              mIsBitmap{false};
+
+    /**
+     * @brief Release the memory of loaded mGlyph / mBitmap.
+     */
+    void ReleaseGlyphData();
+  };
+
+public:
+  // Public API area.
+
+  /**
+   * @brief Load GlyphCacheData from face. The result will be cached.
+   *
+   * @param[in] index Index of glyph in this face.
+   * @param[in] flag Flag when we load the glyph.
+   * @param[in] isBoldRequired True if we require some software bold.
+   * @param[out] data Result of glyph load.
+   * @param[out] error Error code during load glyph.
+   * @return True if load successfully. False if something error occured.
+   */
+  bool GetGlyphCacheDataFromIndex(
+    const GlyphIndex& index,
+    const FT_Int32&   flag,
+    const bool&       isBoldRequired,
+    GlyphCacheData&   data,
+    FT_Error&         error);
+
+  /**
+   * @brief Load GlyphCacheData from face. The result will be cached.
+   * @note If we call this API, We should release GlyphCacheData manually.
+   *
+   * @param[in] index Index of glyph in this face.
+   * @param[in] flag Flag when we load the glyph.
+   * @param[in] isBoldRequired True if we require some software bold.
+   * @param[out] data Result of glyph load.
+   * @param[out] error Error code during load glyph.
+   * @return True if load successfully. False if something error occured.
+   */
+  bool LoadGlyphDataFromIndex(
+    const GlyphIndex& index,
+    const FT_Int32&   flag,
+    const bool&       isBoldRequired,
+    GlyphCacheData&   data,
+    FT_Error&         error);
+
+private:
+  // Private struct area.
+  /**
+   * @brief Key of cached glyph.
+   */
+  struct GlyphCacheKey
+  {
+    GlyphCacheKey()
+    : mIndex(0u),
+      mFlag(0),
+      mIsBoldRequired(false)
+    {
+    }
+
+    GlyphCacheKey(const GlyphIndex& index, const FT_Int32& flag, const bool& boldRequired)
+    : mIndex(index),
+      mFlag(flag),
+      mIsBoldRequired(boldRequired)
+    {
+    }
+    GlyphIndex mIndex;
+    FT_Int32   mFlag;
+    bool       mIsBoldRequired : 1;
+
+    bool operator==(GlyphCacheKey const& rhs) const noexcept
+    {
+      return mIndex == rhs.mIndex && mFlag == rhs.mFlag && mIsBoldRequired == rhs.mIsBoldRequired;
+    }
+  };
+
+  /**
+   * @brief Hash function of GlyphCacheKey.
+   */
+  struct GlyphCacheKeyHash
+  {
+    std::size_t operator()(GlyphCacheKey const& key) const noexcept
+    {
+      return static_cast<std::size_t>(key.mIndex) ^ static_cast<std::size_t>(key.mFlag) ^ (static_cast<std::size_t>(key.mIsBoldRequired) << 29);
+    }
+  };
+
+private:
+  // Private member value area.
+  FT_Face mFreeTypeFace; ///< The FreeType face. Owned from font-face-cache-item
+
+  std::size_t mGlyphCacheMaxSize; ///< The maximum capacity of glyph cache.
+
+  using CacheContainer = LRUCacheContainer<GlyphCacheKey, GlyphCacheData, GlyphCacheKeyHash>;
+
+  CacheContainer mLRUGlyphCache; ///< LRU Cache container of glyph
+};
+
+} // namespace Dali::TextAbstraction::Internal
+
+#endif //DALI_TEST_ABSTRACTION_INTERNAL_FONT_FACE_GLYPH_CACHE_MANAGER_H
diff --git a/dali/internal/text/text-abstraction/plugin/lru-cache-container.h b/dali/internal/text/text-abstraction/plugin/lru-cache-container.h
new file mode 100644 (file)
index 0000000..d6f8d23
--- /dev/null
@@ -0,0 +1,432 @@
+#ifndef DALI_TEXT_ABSTRACTION_INTERNAL_LRU_CACHE_CONTAINER_H
+#define DALI_TEXT_ABSTRACTION_INTERNAL_LRU_CACHE_CONTAINER_H
+
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// EXTERNAL INCLUDES
+#include <limits> // for std::numeric_limits
+#include <unordered_map>
+#include <vector>
+
+namespace Dali::TextAbstraction::Internal
+{
+/**
+ * @brief Helper class to cache as LRU algirhtm.
+ * It implement as double-linked-list with header and footer.
+ *
+ * HEADER <-> n(LatestId) <-> n <-> ... <-> n(OldestId) <-> FOOTER <-> n(FreeId) <-> n <-> .. <-> n <-> HEADER
+ *
+ * @note This container cannot control the KeyType and ElementType construct and destruct timming.
+ *
+ * @todo Could we make "iteration" system here?
+ * @todo Could we move it into dali-core/devel-api?
+ *
+ * @tparam KeyType The type of the key that pairwise with element.
+ * @tparam ElementType The type of the data that the container holds
+ * @tparam KeyHash The custom hash funcion of KeyType. Default is std::hash
+ * @tparam KeyEqual The custom equal function of KeyType. Default is std::equal_to
+ */
+template<class KeyType, class ElementType, class KeyHash = std::hash<KeyType>, class KeyEqual = std::equal_to<KeyType>>
+class LRUCacheContainer
+{
+public:
+  // Constructor
+  LRUCacheContainer(std::size_t maxNumberOfCache = std::numeric_limits<std::size_t>::max())
+  : mCacheMaxSize(maxNumberOfCache)
+  {
+  }
+
+  // Destructor
+  ~LRUCacheContainer() = default;
+
+  LRUCacheContainer(const LRUCacheContainer& rhs) = default;
+  LRUCacheContainer(LRUCacheContainer&& rhs)      = default;
+
+public:
+  // Public struct area.
+  using CacheId          = std::size_t; ///< The id of cached element. It can be used until element poped out.
+  using CacheIdContainer = std::unordered_map<KeyType, CacheId, KeyHash, KeyEqual>;
+
+  /**
+   * @brief Special CacheId for header.
+   */
+  static constexpr CacheId CACHE_HEADER_ID = std::numeric_limits<std::size_t>::max();
+  /**
+   * @brief Special CacheId for footer.
+   */
+  static constexpr CacheId CACHE_FOOTER_ID = std::numeric_limits<std::size_t>::max() - 1u;
+
+  /**
+   * @brief Double linked CacheNode that this container used.
+   */
+  struct CacheNode
+  {
+    CacheNode()  = default;
+    ~CacheNode() = default;
+
+    CacheId     prev{CACHE_FOOTER_ID};
+    CacheId     next{CACHE_HEADER_ID};
+    ElementType element;
+
+    using CacheIdIterator = typename CacheIdContainer::iterator;
+
+    CacheIdIterator cacheIdIterator; ///< Note : It only validate until mCacheId rehashing.
+  };
+
+  using iterator = typename std::vector<CacheNode>::iterator;
+
+public:
+  // Public API area.
+
+  /**
+   * @brief Push an element into the cache. It will be marked as recent
+   * If it is already existed key, it will replace element.
+   *
+   * @param[in] key The key to push
+   * @param[in] element The element to push
+   * @warning This method pop oldest elements if the user attempts to push
+   * more elements than the maximum size specified in the constructor
+   */
+  void Push(const KeyType& key, const ElementType& element)
+  {
+    const auto iter = mCacheId.find(key);
+
+    // If already exist key, just replace element, and return.
+    if(iter != mCacheId.end())
+    {
+      const CacheId id = iter->second;
+
+      // Mark as recently used.
+      InternalPop(id);
+      InternalInsertAfterHeader(id);
+
+      mData[id].element = element;
+      return;
+    }
+
+    if(DALI_UNLIKELY(IsFull()))
+    {
+      // Pop latest element automatically.
+      Pop();
+    }
+
+    if(DALI_UNLIKELY(mNumberOfElements == mData.size()))
+    {
+      InternalReserve(mNumberOfElements == 0 ? 1 : (mNumberOfElements << 1));
+    }
+
+    ++mNumberOfElements;
+
+    const CacheId id = mFreeId;
+
+    // Mark as recently used.
+    InternalPop(id);
+    InternalInsertAfterHeader(id);
+
+    // Copy element
+    mData[id].element = element;
+
+    // Store cache iterator.
+    mData[id].cacheIdIterator = mCacheId.emplace(key, id).first;
+  }
+
+  /**
+   * @brief Pops an element off the oldest used element.
+   * After pop, CacheId relative with this element cannot be used.
+   * Access by poped element's CacheId is Undefined Behavior.
+   *
+   * @return A copy of the element
+   * @warning This method asserts if the container is empty
+   */
+  ElementType Pop()
+  {
+    DALI_ASSERT_ALWAYS(!IsEmpty() && "Reading from empty container");
+
+    const CacheId id = mOldestId;
+    InternalPop(id);
+    InternalInsertAfterFooter(id);
+
+    --mNumberOfElements;
+
+    // Erase cache id.
+    mCacheId.erase(mData[id].cacheIdIterator);
+
+    return mData[id].element;
+  }
+
+  /**
+   * @brief Get an element by the key. It will be marked as recent
+   *
+   * @param[in] key The key of element
+   * @return A reference of the element
+   * @warning This method asserts if invalid key inputed
+   */
+  ElementType& Get(const KeyType& key)
+  {
+    const auto iter = mCacheId.find(key);
+    DALI_ASSERT_ALWAYS((iter != mCacheId.end()) && "Try to get invalid key");
+
+    const auto id = iter->second;
+
+    // Mark as recently used.
+    InternalPop(id);
+    InternalInsertAfterHeader(id);
+
+    return mData[id].element;
+  }
+
+  /**
+   * @brief Find an element by the key. It will be marked as recent
+   *
+   * @param[in] key The key of element
+   * @return A iterator of cache node. If key not exist, return End()
+   */
+  iterator Find(const KeyType& key)
+  {
+    if(mCacheId.find(key) == mCacheId.end())
+    {
+      return End();
+    }
+
+    const auto id = mCacheId[key];
+
+    // Mark as recently used.
+    InternalPop(id);
+    InternalInsertAfterHeader(id);
+
+    return Begin() + id;
+  }
+
+  /**
+   * @brief Clear all data
+   */
+  void Clear()
+  {
+    mCacheId.clear();
+    mCacheId.rehash(0);
+    mData.clear();
+    mData.shrink_to_fit();
+
+    mNumberOfElements = 0;
+    mLatestId         = CACHE_FOOTER_ID;
+    mOldestId         = CACHE_HEADER_ID;
+    mFreeId           = CACHE_HEADER_ID;
+  }
+
+  /**
+   * @brief Predicate to determine if the container is empty
+   *
+   * @return true if the container is empty
+   */
+  bool IsEmpty() const
+  {
+    return mNumberOfElements == 0;
+  }
+
+  /**
+   * @brief Predicate to determine if the container is full
+   *
+   * @return true if the container is full
+   */
+  bool IsFull() const
+  {
+    return (mNumberOfElements == mCacheMaxSize);
+  }
+
+  iterator Begin()
+  {
+    return mData.begin();
+  }
+
+  iterator End()
+  {
+    return mData.end();
+  }
+
+  /**
+   * @brief Get a count of the elements in the container
+   *
+   * @return the number of elements in the container.
+   */
+  std::size_t Count() const
+  {
+    return mNumberOfElements;
+  }
+
+private:
+  // Private struct area.
+
+private:
+  // Private API area.
+
+  /**
+   * @brief Allocate cache memory as reserveSize.
+   * @note We assume that mFreeId is header.
+   *
+   * @param reserveSize Reserved size of cache.
+   */
+  void InternalReserve(std::size_t reserveSize) noexcept
+  {
+    // Increase mData capacity
+    if(reserveSize > mCacheMaxSize)
+    {
+      reserveSize = mCacheMaxSize;
+    }
+
+    CacheId newCreatedIdBegin = mData.size();
+    CacheId newCreatedIdEnd   = reserveSize - 1;
+
+    // Make temporary array for re-validate iterator.
+    std::vector<KeyType> keyList(mData.size());
+    for(auto i = static_cast<std::size_t>(0); i < newCreatedIdBegin; ++i)
+    {
+      keyList[i] = mData[i].cacheIdIterator->first;
+    }
+
+    // Reserve data and cacheid capacity.
+    mData.resize(reserveSize);
+    mCacheId.rehash(reserveSize);
+
+    // Revalidate each iterator.
+    for(auto i = static_cast<std::size_t>(0); i < newCreatedIdBegin; ++i)
+    {
+      mData[i].cacheIdIterator = mCacheId.find(keyList[i]);
+    }
+
+    // Setup new created CacheNode's prev and next id.
+    for(auto i = newCreatedIdBegin;; ++i)
+    {
+      mData[i].prev = DALI_UNLIKELY(i == newCreatedIdBegin) ? CACHE_FOOTER_ID : i - 1;
+      mData[i].next = DALI_UNLIKELY(i == newCreatedIdEnd) ? CACHE_HEADER_ID : i + 1;
+      if(DALI_UNLIKELY(i == newCreatedIdEnd))
+      {
+        break;
+      }
+    }
+    mFreeId = newCreatedIdBegin;
+  }
+
+  /**
+   * @brief Temperary pop of node. After call this, we should call
+   * InternalInsertAfterHeader or InternalInsertAfterFooter
+   *
+   * @param id CacheId that removed temperary.
+   */
+  void InternalPop(const CacheId& id) noexcept
+  {
+    const CacheId prev = mData[id].prev;
+    const CacheId next = mData[id].next;
+
+    // Disconnect prev -> id. and connect prev -> next
+    if(prev == CACHE_HEADER_ID)
+    {
+      mLatestId = next;
+    }
+    else if(prev == CACHE_FOOTER_ID)
+    {
+      mFreeId = next;
+    }
+    else
+    {
+      mData[prev].next = next;
+    }
+
+    // Disconnect id <- next. and connect prev <- next
+    if(next == CACHE_HEADER_ID)
+    {
+      // Do nothing.
+    }
+    else if(next == CACHE_FOOTER_ID)
+    {
+      mOldestId = prev;
+    }
+    else
+    {
+      mData[next].prev = prev;
+    }
+  }
+
+  /**
+   * @brief Insert the node after the header. That mean, this id recently used.
+   *
+   * @param id CacheId that insert after header.
+   */
+  void InternalInsertAfterHeader(const CacheId& id) noexcept
+  {
+    const CacheId next = mLatestId;
+
+    // Connect Header -> id.
+    mLatestId = id;
+
+    // Connect id <- next
+    if(next == CACHE_FOOTER_ID)
+    {
+      mOldestId = id;
+    }
+    else
+    {
+      mData[next].prev = id;
+    }
+
+    // Connect Header <- id -> next
+    mData[id].prev = CACHE_HEADER_ID;
+    mData[id].next = next;
+  }
+
+  /**
+   * @brief Insert the node after the footer. That mean, this id become free.
+   *
+   * @param id CacheId that insert after footer.
+   */
+  void InternalInsertAfterFooter(const CacheId& id) noexcept
+  {
+    const CacheId next = mFreeId;
+
+    // Connect Footer -> id.
+    mFreeId = id;
+
+    // Connect id <- next
+    if(next == CACHE_HEADER_ID)
+    {
+      // Do nothing.
+    }
+    else
+    {
+      mData[next].prev = id;
+    }
+
+    // Connect Footer <- id -> next
+    mData[id].prev = CACHE_FOOTER_ID;
+    mData[id].next = next;
+  }
+
+private:
+  // Private member value area.
+  std::size_t mCacheMaxSize{0};     ///< The maximum capacity of cache.
+  std::size_t mNumberOfElements{0}; ///< The number of elements.
+
+  CacheId mLatestId{CACHE_FOOTER_ID}; ///< The recently used element id
+  CacheId mOldestId{CACHE_HEADER_ID}; ///< The oldest used element id
+  CacheId mFreeId{CACHE_HEADER_ID};   ///< The free element id that can be allocated.
+
+  std::unordered_map<KeyType, CacheId, KeyHash, KeyEqual> mCacheId{}; ///< LRU Cache id container
+  std::vector<CacheNode>                                  mData{};    ///< The real data container.
+};
+
+} // namespace Dali::TextAbstraction::Internal
+
+#endif //DALI_TEXT_ABSTRACTION_INTERNAL_LRU_CACHE_CONTAINER_H