2 * Copyright (c) 2022 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 #include <dali/internal/text/text-abstraction/plugin/font-face-glyph-cache-manager.h>
21 #include <dali/integration-api/debug.h>
22 #include <dali/internal/imaging/common/image-operations.h>
23 #include <dali/internal/text/text-abstraction/plugin/font-client-utils.h>
28 #if defined(DEBUG_ENABLED)
29 extern Dali::Integration::Log::Filter* gFontClientLogFilter;
32 namespace Dali::TextAbstraction::Internal
36 constexpr uint32_t THRESHOLD_WIDTH_FOR_RLE4_COMPRESSION = 8; // The smallest width of glyph that we use RLE4 method.
39 GlyphCacheManager::GlyphCacheManager(std::size_t maxNumberOfGlyphCache)
40 : mGlyphCacheMaxSize(maxNumberOfGlyphCache),
41 mLRUGlyphCache(mGlyphCacheMaxSize)
43 DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, "FontClient::Plugin::GlyphCacheManager Create with maximum size : %d\n", static_cast<int>(mGlyphCacheMaxSize));
46 GlyphCacheManager::~GlyphCacheManager()
51 bool GlyphCacheManager::GetGlyphCacheDataFromIndex(
52 const FT_Face freeTypeFace,
53 const GlyphIndex index,
55 const bool isBoldRequired,
56 GlyphCacheData& glyphData,
59 // Append some error value here instead of FT_Err_Ok.
60 error = static_cast<FT_Error>(-1);
62 const GlyphCacheKey key = GlyphCacheKey(freeTypeFace, index, flag, isBoldRequired);
63 auto iter = mLRUGlyphCache.Find(key);
65 if(iter == mLRUGlyphCache.End())
67 // If cache size is full, remove oldest glyph.
68 if(mLRUGlyphCache.IsFull())
70 auto removedData = mLRUGlyphCache.Pop();
72 DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, "FontClient::Plugin::GlyphCacheManager::GetGlyphCacheDataFromIndex. Remove oldest cache for glyph : %p\n", removedData.mGlyph);
74 // Release Glyph data resource
75 removedData.ReleaseGlyphData();
78 const bool loadSuccess = LoadGlyphDataFromIndex(freeTypeFace, index, flag, isBoldRequired, glyphData, error);
81 // Copy and cached data.
82 mLRUGlyphCache.Push(key, glyphData);
84 DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, "FontClient::Plugin::GlyphCacheManager::GetGlyphCacheDataFromIndex. Create cache for face : %p, index : %u flag : %d isBold : %d isBitmap : %d, glyph : %p\n", freeTypeFace, index, static_cast<int>(flag), isBoldRequired, glyphData.mIsBitmap, glyphData.mGlyph);
93 // We already notify that we use this glyph. And now, copy cached data.
94 glyphData = mLRUGlyphCache.GetElement(iter);
96 DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, "FontClient::Plugin::GlyphCacheManager::GetGlyphCacheDataFromIndex. Find cache for face : %p, index : %u flag : %d isBold : %d isBitmap : %d, glyph : %p\n", freeTypeFace, index, static_cast<int>(flag), isBoldRequired, glyphData.mIsBitmap, glyphData.mGlyph);
101 bool GlyphCacheManager::LoadGlyphDataFromIndex(
102 const FT_Face freeTypeFace,
103 const GlyphIndex index,
105 const bool isBoldRequired,
106 GlyphCacheData& glyphData,
109 error = FT_Load_Glyph(freeTypeFace, index, flag);
110 if(FT_Err_Ok == error)
112 glyphData.mStyleFlags = freeTypeFace->style_flags;
114 const bool isEmboldeningRequired = isBoldRequired && !(glyphData.mStyleFlags & FT_STYLE_FLAG_BOLD);
115 if(isEmboldeningRequired)
117 // Does the software bold.
118 FT_GlyphSlot_Embolden(freeTypeFace->glyph);
121 glyphData.mGlyphMetrics = freeTypeFace->glyph->metrics;
122 glyphData.mIsBitmap = false;
124 error = FT_Get_Glyph(freeTypeFace->glyph, &glyphData.mGlyph);
126 if(glyphData.mGlyph->format == FT_GLYPH_FORMAT_BITMAP)
128 // Copy original glyph infomation. Due to we use union, we should keep original handle.
129 FT_Glyph bitmapGlyph = glyphData.mGlyph;
131 // Copy rendered bitmap
132 // TODO : Is there any way to keep bitmap buffer without copy?
133 glyphData.mBitmap = new FT_Bitmap();
134 *glyphData.mBitmap = freeTypeFace->glyph->bitmap;
136 // New allocate buffer
137 size_t bufferSize = 0;
138 switch(glyphData.mBitmap->pixel_mode)
140 case FT_PIXEL_MODE_GRAY:
142 if(glyphData.mBitmap->pitch == static_cast<int>(glyphData.mBitmap->width))
144 bufferSize = static_cast<size_t>(glyphData.mBitmap->width) * static_cast<size_t>(glyphData.mBitmap->rows);
148 #ifdef FREETYPE_BITMAP_SUPPORT
149 case FT_PIXEL_MODE_BGRA:
151 if(glyphData.mBitmap->pitch == static_cast<int>(glyphData.mBitmap->width << 2u))
153 bufferSize = (static_cast<size_t>(glyphData.mBitmap->width) * static_cast<size_t>(glyphData.mBitmap->rows)) << 2u;
160 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GlyphCacheManager::LoadGlyphDataFromIndex. FontClient Unable to create Bitmap of this PixelType\n");
167 glyphData.mIsBitmap = true;
168 glyphData.mBitmap->buffer = (uint8_t*)malloc(bufferSize * sizeof(uint8_t)); // @note The caller is responsible for deallocating the bitmap data using free.
169 memcpy(glyphData.mBitmap->buffer, freeTypeFace->glyph->bitmap.buffer, bufferSize);
173 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GlyphCacheManager::LoadGlyphDataFromIndex. Bitmap glyph buffer size is zero\n");
174 delete glyphData.mBitmap;
175 glyphData.mBitmap = nullptr;
176 error = static_cast<FT_Error>(-1);
179 // Release glyph data.
180 FT_Done_Glyph(bitmapGlyph);
183 if(FT_Err_Ok == error)
191 void GlyphCacheManager::ResizeBitmapGlyph(
192 const FT_Face freeTypeFace,
193 const GlyphIndex index,
195 const bool isBoldRequired,
196 const uint32_t desiredWidth,
197 const uint32_t desiredHeight)
199 if(desiredWidth * desiredHeight <= 0)
201 // Skip this API if desired size is zero
205 GlyphCacheData originGlyphData;
206 if(GetGlyphCacheDataFromIndex(freeTypeFace, index, flag, isBoldRequired, originGlyphData, error))
208 if(DALI_LIKELY(originGlyphData.mIsBitmap && originGlyphData.mBitmap))
210 const bool requiredResize = (originGlyphData.mBitmap->rows != desiredHeight) || (originGlyphData.mBitmap->width != desiredWidth);
213 // originalGlyphData is copy data. For change cached information, we should access as iterator.
214 const GlyphCacheKey key = GlyphCacheKey(freeTypeFace, index, flag, isBoldRequired);
215 auto iter = mLRUGlyphCache.Find(key);
217 GlyphCacheData& destinationGlpyhData = mLRUGlyphCache.GetElement(iter);
219 const ImageDimensions inputDimensions(destinationGlpyhData.mBitmap->width, destinationGlpyhData.mBitmap->rows);
220 const ImageDimensions desiredDimensions(desiredWidth, desiredHeight);
222 uint8_t* desiredBuffer = nullptr;
224 switch(destinationGlpyhData.mBitmap->pixel_mode)
226 case FT_PIXEL_MODE_GRAY:
228 if(destinationGlpyhData.mBitmap->pitch == static_cast<int>(destinationGlpyhData.mBitmap->width))
230 desiredBuffer = (uint8_t*)malloc(desiredWidth * desiredHeight * sizeof(uint8_t)); // @note The caller is responsible for deallocating the bitmap data using free.
231 // Resize bitmap here.
232 Dali::Internal::Platform::LanczosSample1BPP(destinationGlpyhData.mBitmap->buffer,
234 destinationGlpyhData.mBitmap->width,
240 #ifdef FREETYPE_BITMAP_SUPPORT
241 case FT_PIXEL_MODE_BGRA:
243 if(destinationGlpyhData.mBitmap->pitch == static_cast<int>(destinationGlpyhData.mBitmap->width << 2u))
245 desiredBuffer = (uint8_t*)malloc((desiredWidth * desiredHeight * sizeof(uint8_t)) << 2u); // @note The caller is responsible for deallocating the bitmap data using free.
246 // Resize bitmap here.
247 Dali::Internal::Platform::LanczosSample4BPP(destinationGlpyhData.mBitmap->buffer,
249 destinationGlpyhData.mBitmap->width,
258 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GlyphCacheManager::ResizeBitmapGlyph. FontClient Unable to create Bitmap of this PixelType\n");
265 // Success to resize bitmap glyph.
266 // Release origin bitmap buffer.
267 free(destinationGlpyhData.mBitmap->buffer);
269 // Replace as desired buffer and size.
270 destinationGlpyhData.mBitmap->buffer = desiredBuffer;
271 destinationGlpyhData.mBitmap->width = desiredWidth;
272 destinationGlpyhData.mBitmap->rows = desiredHeight;
273 switch(destinationGlpyhData.mBitmap->pixel_mode)
275 case FT_PIXEL_MODE_GRAY:
277 destinationGlpyhData.mBitmap->pitch = desiredWidth;
280 #ifdef FREETYPE_BITMAP_SUPPORT
281 case FT_PIXEL_MODE_BGRA:
283 destinationGlpyhData.mBitmap->pitch = desiredWidth << 2u;
294 void GlyphCacheManager::CacheRenderedGlyphBuffer(
295 const FT_Face freeTypeFace,
296 const GlyphIndex index,
298 const bool isBoldRequired,
299 const FT_Bitmap& srcBitmap,
300 const CompressionPolicyType policy)
302 if(srcBitmap.width * srcBitmap.rows <= 0)
304 // Skip this API if rendered bitmap size is zero
308 GlyphCacheData originGlyphData;
309 if(GetGlyphCacheDataFromIndex(freeTypeFace, index, flag, isBoldRequired, originGlyphData, error))
311 if(DALI_LIKELY(!originGlyphData.mIsBitmap && originGlyphData.mRenderedBuffer == nullptr))
313 // originalGlyphData is copy data. For change cached information, we should access as iterator.
314 const GlyphCacheKey key = GlyphCacheKey(freeTypeFace, index, flag, isBoldRequired);
315 auto iter = mLRUGlyphCache.Find(key);
317 GlyphCacheData& destinationGlpyhData = mLRUGlyphCache.GetElement(iter);
319 destinationGlpyhData.mRenderedBuffer = new TextAbstraction::GlyphBufferData();
320 if(DALI_UNLIKELY(!destinationGlpyhData.mRenderedBuffer))
322 DALI_LOG_ERROR("Allocate GlyphBufferData failed\n");
326 TextAbstraction::GlyphBufferData& renderBuffer = *destinationGlpyhData.mRenderedBuffer;
328 // Set basic informations.
329 renderBuffer.width = srcBitmap.width;
330 renderBuffer.height = srcBitmap.rows;
332 switch(srcBitmap.pixel_mode)
334 case FT_PIXEL_MODE_GRAY:
336 renderBuffer.format = Pixel::L8;
338 if(policy == CompressionPolicyType::SPEED)
340 // If policy is SPEED, we will not compress bitmap.
341 renderBuffer.compressionType = TextAbstraction::GlyphBufferData::CompressionType::NO_COMPRESSION;
345 // If small enough glyph, compress as BPP4 method.
346 if(srcBitmap.width < THRESHOLD_WIDTH_FOR_RLE4_COMPRESSION)
348 renderBuffer.compressionType = TextAbstraction::GlyphBufferData::CompressionType::BPP_4;
352 renderBuffer.compressionType = TextAbstraction::GlyphBufferData::CompressionType::RLE_4;
356 const auto compressedBufferSize = TextAbstraction::GlyphBufferData::Compress(srcBitmap.buffer, renderBuffer);
357 if(DALI_UNLIKELY(compressedBufferSize == 0u))
359 DALI_ASSERT_DEBUG(0 == "Compress failed at FT_PIXEL_MODE_GRAY");
360 DALI_LOG_ERROR("Compress failed. Ignore cache\n");
361 delete destinationGlpyhData.mRenderedBuffer;
362 destinationGlpyhData.mRenderedBuffer = nullptr;
367 #ifdef FREETYPE_BITMAP_SUPPORT
368 case FT_PIXEL_MODE_BGRA:
370 // Copy buffer without compress
371 renderBuffer.compressionType = TextAbstraction::GlyphBufferData::CompressionType::NO_COMPRESSION;
372 renderBuffer.format = Pixel::BGRA8888;
374 const auto compressedBufferSize = TextAbstraction::GlyphBufferData::Compress(srcBitmap.buffer, renderBuffer);
375 if(DALI_UNLIKELY(compressedBufferSize == 0u))
377 DALI_ASSERT_DEBUG(0 == "Compress failed at FT_PIXEL_MODE_BGRA");
378 DALI_LOG_ERROR("Compress failed. Ignore cache\n");
379 delete destinationGlpyhData.mRenderedBuffer;
380 destinationGlpyhData.mRenderedBuffer = nullptr;
388 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GlyphCacheManager::CacheRenderedGlyphBuffer. FontClient Unable to create Bitmap of this PixelType\n");
389 delete destinationGlpyhData.mRenderedBuffer;
390 destinationGlpyhData.mRenderedBuffer = nullptr;
398 void GlyphCacheManager::RemoveGlyphFromFace(const FT_Face freeTypeFace)
400 uint32_t removedItemCount = 0;
402 auto endIter = mLRUGlyphCache.End();
403 for(auto iter = mLRUGlyphCache.Begin(); iter != endIter;)
405 // Check whether this cached item has inputed freeTypeFace as key.
406 auto keyFace = mLRUGlyphCache.GetKey(iter).mFreeTypeFace;
407 if(keyFace == freeTypeFace)
410 iter = mLRUGlyphCache.Erase(iter);
418 DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, "FontClient::Plugin::GlyphCacheManager::RemoveGlyphFromFace. Remove all cached glyph with face : %p, removed glyph count : %u\n", freeTypeFace, removedItemCount);
421 void GlyphCacheManager::ClearCache(const std::size_t remainCount)
423 if(remainCount == 0u)
425 // Release all data memory first.
426 auto endIter = mLRUGlyphCache.End();
427 for(auto iter = mLRUGlyphCache.Begin(); iter != endIter; ++iter)
429 // Get the reference of data. and release it.
430 auto& removedData = mLRUGlyphCache.GetElement(iter);
431 removedData.ReleaseGlyphData();
435 mLRUGlyphCache.Clear();
439 // While the cache count is bigger than remainCount, remove oldest glyph.
440 while(mLRUGlyphCache.Count() > remainCount)
442 auto removedData = mLRUGlyphCache.Pop();
444 DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, "FontClient::Plugin::GlyphCacheManager::ClearCache[%zu / %zu]. Remove oldest cache for glyph : %p\n", mLRUGlyphCache.Count(), remainCount, removedData.mGlyph);
446 // Release Glyph data resource
447 removedData.ReleaseGlyphData();
452 void GlyphCacheManager::GlyphCacheData::ReleaseGlyphData()
454 if(mIsBitmap && mBitmap)
456 // Created FT_Bitmap object must be released with FT_Bitmap_Done
457 free(mBitmap->buffer); // This buffer created by malloc
464 // Created FT_Glyph object must be released with FT_Done_Glyph
465 FT_Done_Glyph(mGlyph);
471 delete mRenderedBuffer;
472 mRenderedBuffer = nullptr;
478 } // namespace Dali::TextAbstraction::Internal