2 * Copyright (c) 2024 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 GlyphCacheDataPtr& glyphDataPtr,
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);
75 // Create new GlyphCacheData.
76 glyphDataPtr = std::make_shared<GlyphCacheData>();
78 GlyphCacheData& glyphData = *glyphDataPtr.get();
80 const bool loadSuccess = LoadGlyphDataFromIndex(freeTypeFace, index, flag, isBoldRequired, glyphData, error);
83 // Copy and cached data.
84 mLRUGlyphCache.Push(key, glyphDataPtr);
86 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);
95 // We already notify that we use this glyph. And now, copy cached data.
96 glyphDataPtr = mLRUGlyphCache.GetElement(iter);
98 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, glyphDataPtr->mIsBitmap, glyphDataPtr->mGlyph);
103 bool GlyphCacheManager::LoadGlyphDataFromIndex(
104 const FT_Face freeTypeFace,
105 const GlyphIndex index,
107 const bool isBoldRequired,
108 GlyphCacheData& glyphData,
111 error = FT_Load_Glyph(freeTypeFace, index, flag);
112 if(FT_Err_Ok == error)
114 glyphData.mStyleFlags = freeTypeFace->style_flags;
116 const bool isEmboldeningRequired = isBoldRequired && !(glyphData.mStyleFlags & FT_STYLE_FLAG_BOLD);
117 if(isEmboldeningRequired)
119 // Does the software bold.
120 FT_GlyphSlot_Embolden(freeTypeFace->glyph);
123 glyphData.mGlyphMetrics = freeTypeFace->glyph->metrics;
124 glyphData.mIsBitmap = false;
126 error = FT_Get_Glyph(freeTypeFace->glyph, &glyphData.mGlyph);
128 if(glyphData.mGlyph->format == FT_GLYPH_FORMAT_BITMAP)
130 // Copy original glyph infomation. Due to we use union, we should keep original handle.
131 FT_Glyph bitmapGlyph = glyphData.mGlyph;
133 // Copy rendered bitmap
134 // TODO : Is there any way to keep bitmap buffer without copy?
135 glyphData.mBitmap = new FT_Bitmap();
136 *glyphData.mBitmap = freeTypeFace->glyph->bitmap;
138 // New allocate buffer
139 size_t bufferSize = 0;
140 switch(glyphData.mBitmap->pixel_mode)
142 case FT_PIXEL_MODE_GRAY:
144 if(glyphData.mBitmap->pitch == static_cast<int>(glyphData.mBitmap->width))
146 bufferSize = static_cast<size_t>(glyphData.mBitmap->width) * static_cast<size_t>(glyphData.mBitmap->rows);
150 #ifdef FREETYPE_BITMAP_SUPPORT
151 case FT_PIXEL_MODE_BGRA:
153 if(glyphData.mBitmap->pitch == static_cast<int>(glyphData.mBitmap->width << 2u))
155 bufferSize = (static_cast<size_t>(glyphData.mBitmap->width) * static_cast<size_t>(glyphData.mBitmap->rows)) << 2u;
162 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GlyphCacheManager::LoadGlyphDataFromIndex. FontClient Unable to create Bitmap of this PixelType\n");
169 glyphData.mIsBitmap = true;
170 glyphData.mBitmap->buffer = (uint8_t*)malloc(bufferSize * sizeof(uint8_t)); // @note The caller is responsible for deallocating the bitmap data using free.
171 if(DALI_UNLIKELY(!glyphData.mBitmap->buffer))
173 DALI_LOG_ERROR("malloc is failed. request malloc size : %zu\n", bufferSize * sizeof(uint8_t));
174 delete glyphData.mBitmap;
175 glyphData.mIsBitmap = false;
176 glyphData.mBitmap = nullptr;
177 error = static_cast<FT_Error>(-1);
181 memcpy(glyphData.mBitmap->buffer, freeTypeFace->glyph->bitmap.buffer, bufferSize);
186 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GlyphCacheManager::LoadGlyphDataFromIndex. Bitmap glyph buffer size is zero\n");
187 delete glyphData.mBitmap;
188 glyphData.mBitmap = nullptr;
189 error = static_cast<FT_Error>(-1);
192 // Release glyph data.
193 FT_Done_Glyph(bitmapGlyph);
196 if(FT_Err_Ok == error)
204 void GlyphCacheManager::ResizeBitmapGlyph(
205 const FT_Face freeTypeFace,
206 const GlyphIndex index,
208 const bool isBoldRequired,
209 const uint32_t desiredWidth,
210 const uint32_t desiredHeight)
212 if(desiredWidth * desiredHeight <= 0)
214 // Skip this API if desired size is zero
218 GlyphCacheDataPtr glyphDataPtr;
219 if(GetGlyphCacheDataFromIndex(freeTypeFace, index, flag, isBoldRequired, glyphDataPtr, error))
221 GlyphCacheData& glyphData = *glyphDataPtr.get();
222 if(DALI_LIKELY(glyphData.mIsBitmap && glyphData.mBitmap))
224 const bool requiredResize = (glyphData.mBitmap->rows != desiredHeight) || (glyphData.mBitmap->width != desiredWidth);
227 const ImageDimensions inputDimensions(glyphData.mBitmap->width, glyphData.mBitmap->rows);
228 const ImageDimensions desiredDimensions(desiredWidth, desiredHeight);
230 uint8_t* desiredBuffer = nullptr;
232 switch(glyphData.mBitmap->pixel_mode)
234 case FT_PIXEL_MODE_GRAY:
236 if(glyphData.mBitmap->pitch == static_cast<int>(glyphData.mBitmap->width))
238 desiredBuffer = (uint8_t*)malloc(desiredWidth * desiredHeight * sizeof(uint8_t)); // @note The caller is responsible for deallocating the bitmap data using free.
240 if(DALI_UNLIKELY(!desiredBuffer))
242 DALI_LOG_ERROR("malloc is failed. request malloc size : %u x %u x 1\n", desiredWidth, desiredHeight);
246 // Resize bitmap here.
247 Dali::Internal::Platform::LanczosSample1BPP(glyphData.mBitmap->buffer,
249 glyphData.mBitmap->width,
256 #ifdef FREETYPE_BITMAP_SUPPORT
257 case FT_PIXEL_MODE_BGRA:
259 if(glyphData.mBitmap->pitch == static_cast<int>(glyphData.mBitmap->width << 2u))
261 desiredBuffer = (uint8_t*)malloc((desiredWidth * desiredHeight * sizeof(uint8_t)) << 2u); // @note The caller is responsible for deallocating the bitmap data using free.
263 if(DALI_UNLIKELY(!desiredBuffer))
265 DALI_LOG_ERROR("malloc is failed. request malloc size : %u x %u x 4\n", desiredWidth, desiredHeight);
269 // Resize bitmap here.
270 Dali::Internal::Platform::LanczosSample4BPP(glyphData.mBitmap->buffer,
272 glyphData.mBitmap->width,
282 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GlyphCacheManager::ResizeBitmapGlyph. FontClient Unable to create Bitmap of this PixelType\n");
289 // Success to resize bitmap glyph.
290 // Release origin bitmap buffer.
291 free(glyphData.mBitmap->buffer);
293 // Replace as desired buffer and size.
294 glyphData.mBitmap->buffer = desiredBuffer;
295 glyphData.mBitmap->width = desiredWidth;
296 glyphData.mBitmap->rows = desiredHeight;
297 switch(glyphData.mBitmap->pixel_mode)
299 case FT_PIXEL_MODE_GRAY:
301 glyphData.mBitmap->pitch = desiredWidth;
304 #ifdef FREETYPE_BITMAP_SUPPORT
305 case FT_PIXEL_MODE_BGRA:
307 glyphData.mBitmap->pitch = desiredWidth << 2u;
318 void GlyphCacheManager::CacheRenderedGlyphBuffer(
319 const FT_Face freeTypeFace,
320 const GlyphIndex index,
322 const bool isBoldRequired,
323 const FT_Bitmap& srcBitmap,
324 const CompressionPolicyType policy)
326 if(srcBitmap.width * srcBitmap.rows <= 0)
328 // Skip this API if rendered bitmap size is zero
332 GlyphCacheDataPtr glyphDataPtr;
333 if(GetGlyphCacheDataFromIndex(freeTypeFace, index, flag, isBoldRequired, glyphDataPtr, error))
335 GlyphCacheData& glyphData = *glyphDataPtr.get();
336 if(DALI_LIKELY(!glyphData.mIsBitmap && glyphData.mRenderedBuffer == nullptr))
338 glyphData.mRenderedBuffer = new TextAbstraction::GlyphBufferData();
339 if(DALI_UNLIKELY(!glyphData.mRenderedBuffer))
341 DALI_LOG_ERROR("Allocate GlyphBufferData failed\n");
345 TextAbstraction::GlyphBufferData& renderBuffer = *glyphData.mRenderedBuffer;
347 // Set basic informations.
348 renderBuffer.width = srcBitmap.width;
349 renderBuffer.height = srcBitmap.rows;
351 switch(srcBitmap.pixel_mode)
353 case FT_PIXEL_MODE_GRAY:
355 renderBuffer.format = Pixel::L8;
357 if(policy == CompressionPolicyType::SPEED)
359 // If policy is SPEED, we will not compress bitmap.
360 renderBuffer.compressionType = TextAbstraction::GlyphBufferData::CompressionType::NO_COMPRESSION;
364 // If small enough glyph, compress as BPP4 method.
365 if(srcBitmap.width < THRESHOLD_WIDTH_FOR_RLE4_COMPRESSION)
367 renderBuffer.compressionType = TextAbstraction::GlyphBufferData::CompressionType::BPP_4;
371 renderBuffer.compressionType = TextAbstraction::GlyphBufferData::CompressionType::RLE_4;
375 const auto compressedBufferSize = TextAbstraction::GlyphBufferData::Compress(srcBitmap.buffer, renderBuffer);
376 if(DALI_UNLIKELY(compressedBufferSize == 0u))
378 DALI_ASSERT_DEBUG(0 == "Compress failed at FT_PIXEL_MODE_GRAY");
379 DALI_LOG_ERROR("Compress failed. Ignore cache\n");
380 delete glyphData.mRenderedBuffer;
381 glyphData.mRenderedBuffer = nullptr;
386 #ifdef FREETYPE_BITMAP_SUPPORT
387 case FT_PIXEL_MODE_BGRA:
389 // Copy buffer without compress
390 renderBuffer.compressionType = TextAbstraction::GlyphBufferData::CompressionType::NO_COMPRESSION;
391 renderBuffer.format = Pixel::BGRA8888;
393 const auto compressedBufferSize = TextAbstraction::GlyphBufferData::Compress(srcBitmap.buffer, renderBuffer);
394 if(DALI_UNLIKELY(compressedBufferSize == 0u))
396 DALI_ASSERT_DEBUG(0 == "Compress failed at FT_PIXEL_MODE_BGRA");
397 DALI_LOG_ERROR("Compress failed. Ignore cache\n");
398 delete glyphData.mRenderedBuffer;
399 glyphData.mRenderedBuffer = nullptr;
407 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GlyphCacheManager::CacheRenderedGlyphBuffer. FontClient Unable to create Bitmap of this PixelType\n");
408 delete glyphData.mRenderedBuffer;
409 glyphData.mRenderedBuffer = nullptr;
417 void GlyphCacheManager::RemoveGlyphFromFace(const FT_Face freeTypeFace)
419 uint32_t removedItemCount = 0;
421 auto endIter = mLRUGlyphCache.End();
422 for(auto iter = mLRUGlyphCache.Begin(); iter != endIter;)
424 // Check whether this cached item has inputed freeTypeFace as key.
425 auto keyFace = mLRUGlyphCache.GetKey(iter).mFreeTypeFace;
426 if(keyFace == freeTypeFace)
429 iter = mLRUGlyphCache.Erase(iter);
437 DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, "FontClient::Plugin::GlyphCacheManager::RemoveGlyphFromFace. Remove all cached glyph with face : %p, removed glyph count : %u\n", freeTypeFace, removedItemCount);
440 void GlyphCacheManager::ClearCache(const std::size_t remainCount)
442 if(remainCount == 0u)
445 mLRUGlyphCache.Clear();
449 // While the cache count is bigger than remainCount, remove oldest glyph.
450 while(mLRUGlyphCache.Count() > remainCount)
452 auto removedData = mLRUGlyphCache.Pop();
454 DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, "FontClient::Plugin::GlyphCacheManager::ClearCache[%zu / %zu]. Remove oldest cache for glyph : %p\n", mLRUGlyphCache.Count(), remainCount, removedData->mGlyph);
459 // GlyphCacheManager::GlyphCacheData
461 void GlyphCacheManager::GlyphCacheData::ReleaseGlyphData()
463 if(mIsBitmap && mBitmap)
465 // Created FT_Bitmap object must be released with FT_Bitmap_Done
466 // But, this class's mBitmap it not an actual FT_Bitmap object. So free buffer is enough.
467 free(mBitmap->buffer); // This buffer created by malloc
474 // Created FT_Glyph object must be released with FT_Done_Glyph
475 FT_Done_Glyph(mGlyph);
481 delete mRenderedBuffer;
482 mRenderedBuffer = nullptr;
488 GlyphCacheManager::GlyphCacheData::GlyphCacheData()
493 mRenderedBuffer{nullptr}
497 GlyphCacheManager::GlyphCacheData::~GlyphCacheData()
502 GlyphCacheManager::GlyphCacheData::GlyphCacheData(GlyphCacheData&& rhs) noexcept
507 mRenderedBuffer{nullptr}
509 *this = std::move(rhs);
512 GlyphCacheManager::GlyphCacheData& GlyphCacheManager::GlyphCacheData::operator=(GlyphCacheData&& rhs) noexcept
514 // Self-assignment detection
520 // Delete self data first.
525 if(rhs.mIsBitmap && rhs.mBitmap)
528 mBitmap = rhs.mBitmap;
530 rhs.mBitmap = nullptr;
536 rhs.mGlyph = nullptr;
543 if(rhs.mRenderedBuffer)
545 mRenderedBuffer = rhs.mRenderedBuffer;
546 rhs.mRenderedBuffer = nullptr;
550 mRenderedBuffer = nullptr;
553 mStyleFlags = rhs.mStyleFlags;
557 } // namespace Dali::TextAbstraction::Internal