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
)
--- /dev/null
+/*
+ * 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;
+}
${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
)
/*
- * 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.
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)
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;
#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.
*/
BitmapFontCacheItem(const BitmapFont& bitmapFont, FontId fontId);
+ /**
+ * Destructor
+ */
+ ~BitmapFontCacheItem() = default;
+
/**
* @copydoc FontCacheItemInterface::GetFontMetrics()
*/
/**
* @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()
#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.
*
* @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
{
}
-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)
{
}
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
mCharacterSetCache.Clear();
mFontDescriptionSizeCache.clear();
+ mFontDescriptionSizeCache.rehash(0); // Note : unordered_map.clear() didn't deallocate memory
mEllipsisCache.Clear();
mPixelBufferCache.clear();
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));
}
{
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;
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:
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];
{
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:
{
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;
}
}
// 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,
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);
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);
}
void FontClient::Plugin::ValidateFont(const FontDescription& fontDescription,
- FontDescriptionId& validatedFontId)
+ FontDescriptionId& fontDescriptionId)
{
DALI_LOG_TRACE_METHOD(gFontClientLogFilter);
FONT_LOG_DESCRIPTION(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));
{
// Cache the given font's description if it's different than the matched.
FontDescriptionCacheItem item(fontDescription,
- validatedFontId);
+ fontDescriptionId);
mValidatedFontCache.push_back(std::move(item));
}
if((fontId > 0u) &&
(fontId - 1u) < mFontIdCache.Count())
{
- FontFaceCacheItem& font = mFontFaceCache[mFontIdCache[fontId - 1u].id];
+ FontFaceCacheItem& font = mFontFaceCache[mFontIdCache[fontId - 1u].index];
if(!font.mVectorFontId)
{
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)
{
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);
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.
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.
}
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)
{
(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;
}
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");
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.
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);
}
}
#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.
#endif
// EXTERNAL INCLUDES
+#include <unordered_map>
+
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
*/
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
};
/**
};
/**
- * @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.
*
/**
* @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.
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.
/*
- * 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>
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,
const FontMetrics& metrics)
: mFreeTypeLibrary(freeTypeLibrary),
mFreeTypeFace(ftFace),
+ mGlyphCacheManager(new GlyphCacheManager(mFreeTypeFace, GetMaxNumberOfGlyphCache())),
mPath(path),
mRequestedPointSize(requestedPointSize),
mFaceIndex(face),
bool hasColorTables)
: mFreeTypeLibrary(freeTypeLibrary),
mFreeTypeFace(ftFace),
+ mGlyphCacheManager(new GlyphCacheManager(mFreeTypeFace, GetMaxNumberOfGlyphCache())),
mPath(path),
mRequestedPointSize(requestedPointSize),
mFaceIndex(face),
{
}
+// 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;
}
}
-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
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
// 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
{
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;
// 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
// 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
{
// 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;
#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.
*/
// 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>
float fixedHeight,
bool hasColorTables);
+ FontFaceCacheItem(const FontFaceCacheItem& rhs) = delete; // Do not use copy construct
+ FontFaceCacheItem(FontFaceCacheItem&& rhs);
+
+ ~FontFaceCacheItem();
+
/**
* @copydoc FontCacheItemInterface::GetFontMetrics()
*/
/**
* @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()
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
--- /dev/null
+/*
+ * 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
--- /dev/null
+#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
--- /dev/null
+#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