2 * Copyright (c) 2023 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 memcpy(glyphData.mBitmap->buffer, freeTypeFace->glyph->bitmap.buffer, bufferSize);
175 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GlyphCacheManager::LoadGlyphDataFromIndex. Bitmap glyph buffer size is zero\n");
176 delete glyphData.mBitmap;
177 glyphData.mBitmap = nullptr;
178 error = static_cast<FT_Error>(-1);
181 // Release glyph data.
182 FT_Done_Glyph(bitmapGlyph);
185 if(FT_Err_Ok == error)
193 void GlyphCacheManager::ResizeBitmapGlyph(
194 const FT_Face freeTypeFace,
195 const GlyphIndex index,
197 const bool isBoldRequired,
198 const uint32_t desiredWidth,
199 const uint32_t desiredHeight)
201 if(desiredWidth * desiredHeight <= 0)
203 // Skip this API if desired size is zero
207 GlyphCacheDataPtr glyphDataPtr;
208 if(GetGlyphCacheDataFromIndex(freeTypeFace, index, flag, isBoldRequired, glyphDataPtr, error))
210 GlyphCacheData& glyphData = *glyphDataPtr.get();
211 if(DALI_LIKELY(glyphData.mIsBitmap && glyphData.mBitmap))
213 const bool requiredResize = (glyphData.mBitmap->rows != desiredHeight) || (glyphData.mBitmap->width != desiredWidth);
216 const ImageDimensions inputDimensions(glyphData.mBitmap->width, glyphData.mBitmap->rows);
217 const ImageDimensions desiredDimensions(desiredWidth, desiredHeight);
219 uint8_t* desiredBuffer = nullptr;
221 switch(glyphData.mBitmap->pixel_mode)
223 case FT_PIXEL_MODE_GRAY:
225 if(glyphData.mBitmap->pitch == static_cast<int>(glyphData.mBitmap->width))
227 desiredBuffer = (uint8_t*)malloc(desiredWidth * desiredHeight * sizeof(uint8_t)); // @note The caller is responsible for deallocating the bitmap data using free.
228 // Resize bitmap here.
229 Dali::Internal::Platform::LanczosSample1BPP(glyphData.mBitmap->buffer,
231 glyphData.mBitmap->width,
237 #ifdef FREETYPE_BITMAP_SUPPORT
238 case FT_PIXEL_MODE_BGRA:
240 if(glyphData.mBitmap->pitch == static_cast<int>(glyphData.mBitmap->width << 2u))
242 desiredBuffer = (uint8_t*)malloc((desiredWidth * desiredHeight * sizeof(uint8_t)) << 2u); // @note The caller is responsible for deallocating the bitmap data using free.
243 // Resize bitmap here.
244 Dali::Internal::Platform::LanczosSample4BPP(glyphData.mBitmap->buffer,
246 glyphData.mBitmap->width,
255 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GlyphCacheManager::ResizeBitmapGlyph. FontClient Unable to create Bitmap of this PixelType\n");
262 // Success to resize bitmap glyph.
263 // Release origin bitmap buffer.
264 free(glyphData.mBitmap->buffer);
266 // Replace as desired buffer and size.
267 glyphData.mBitmap->buffer = desiredBuffer;
268 glyphData.mBitmap->width = desiredWidth;
269 glyphData.mBitmap->rows = desiredHeight;
270 switch(glyphData.mBitmap->pixel_mode)
272 case FT_PIXEL_MODE_GRAY:
274 glyphData.mBitmap->pitch = desiredWidth;
277 #ifdef FREETYPE_BITMAP_SUPPORT
278 case FT_PIXEL_MODE_BGRA:
280 glyphData.mBitmap->pitch = desiredWidth << 2u;
291 void GlyphCacheManager::CacheRenderedGlyphBuffer(
292 const FT_Face freeTypeFace,
293 const GlyphIndex index,
295 const bool isBoldRequired,
296 const FT_Bitmap& srcBitmap,
297 const CompressionPolicyType policy)
299 if(srcBitmap.width * srcBitmap.rows <= 0)
301 // Skip this API if rendered bitmap size is zero
305 GlyphCacheDataPtr glyphDataPtr;
306 if(GetGlyphCacheDataFromIndex(freeTypeFace, index, flag, isBoldRequired, glyphDataPtr, error))
308 GlyphCacheData& glyphData = *glyphDataPtr.get();
309 if(DALI_LIKELY(!glyphData.mIsBitmap && glyphData.mRenderedBuffer == nullptr))
311 glyphData.mRenderedBuffer = new TextAbstraction::GlyphBufferData();
312 if(DALI_UNLIKELY(!glyphData.mRenderedBuffer))
314 DALI_LOG_ERROR("Allocate GlyphBufferData failed\n");
318 TextAbstraction::GlyphBufferData& renderBuffer = *glyphData.mRenderedBuffer;
320 // Set basic informations.
321 renderBuffer.width = srcBitmap.width;
322 renderBuffer.height = srcBitmap.rows;
324 switch(srcBitmap.pixel_mode)
326 case FT_PIXEL_MODE_GRAY:
328 renderBuffer.format = Pixel::L8;
330 if(policy == CompressionPolicyType::SPEED)
332 // If policy is SPEED, we will not compress bitmap.
333 renderBuffer.compressionType = TextAbstraction::GlyphBufferData::CompressionType::NO_COMPRESSION;
337 // If small enough glyph, compress as BPP4 method.
338 if(srcBitmap.width < THRESHOLD_WIDTH_FOR_RLE4_COMPRESSION)
340 renderBuffer.compressionType = TextAbstraction::GlyphBufferData::CompressionType::BPP_4;
344 renderBuffer.compressionType = TextAbstraction::GlyphBufferData::CompressionType::RLE_4;
348 const auto compressedBufferSize = TextAbstraction::GlyphBufferData::Compress(srcBitmap.buffer, renderBuffer);
349 if(DALI_UNLIKELY(compressedBufferSize == 0u))
351 DALI_ASSERT_DEBUG(0 == "Compress failed at FT_PIXEL_MODE_GRAY");
352 DALI_LOG_ERROR("Compress failed. Ignore cache\n");
353 delete glyphData.mRenderedBuffer;
354 glyphData.mRenderedBuffer = nullptr;
359 #ifdef FREETYPE_BITMAP_SUPPORT
360 case FT_PIXEL_MODE_BGRA:
362 // Copy buffer without compress
363 renderBuffer.compressionType = TextAbstraction::GlyphBufferData::CompressionType::NO_COMPRESSION;
364 renderBuffer.format = Pixel::BGRA8888;
366 const auto compressedBufferSize = TextAbstraction::GlyphBufferData::Compress(srcBitmap.buffer, renderBuffer);
367 if(DALI_UNLIKELY(compressedBufferSize == 0u))
369 DALI_ASSERT_DEBUG(0 == "Compress failed at FT_PIXEL_MODE_BGRA");
370 DALI_LOG_ERROR("Compress failed. Ignore cache\n");
371 delete glyphData.mRenderedBuffer;
372 glyphData.mRenderedBuffer = nullptr;
380 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GlyphCacheManager::CacheRenderedGlyphBuffer. FontClient Unable to create Bitmap of this PixelType\n");
381 delete glyphData.mRenderedBuffer;
382 glyphData.mRenderedBuffer = nullptr;
390 void GlyphCacheManager::RemoveGlyphFromFace(const FT_Face freeTypeFace)
392 uint32_t removedItemCount = 0;
394 auto endIter = mLRUGlyphCache.End();
395 for(auto iter = mLRUGlyphCache.Begin(); iter != endIter;)
397 // Check whether this cached item has inputed freeTypeFace as key.
398 auto keyFace = mLRUGlyphCache.GetKey(iter).mFreeTypeFace;
399 if(keyFace == freeTypeFace)
402 iter = mLRUGlyphCache.Erase(iter);
410 DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, "FontClient::Plugin::GlyphCacheManager::RemoveGlyphFromFace. Remove all cached glyph with face : %p, removed glyph count : %u\n", freeTypeFace, removedItemCount);
413 void GlyphCacheManager::ClearCache(const std::size_t remainCount)
415 if(remainCount == 0u)
418 mLRUGlyphCache.Clear();
422 // While the cache count is bigger than remainCount, remove oldest glyph.
423 while(mLRUGlyphCache.Count() > remainCount)
425 auto removedData = mLRUGlyphCache.Pop();
427 DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, "FontClient::Plugin::GlyphCacheManager::ClearCache[%zu / %zu]. Remove oldest cache for glyph : %p\n", mLRUGlyphCache.Count(), remainCount, removedData->mGlyph);
432 // GlyphCacheManager::GlyphCacheData
434 void GlyphCacheManager::GlyphCacheData::ReleaseGlyphData()
436 if(mIsBitmap && mBitmap)
438 // Created FT_Bitmap object must be released with FT_Bitmap_Done
439 // But, this class's mBitmap it not an actual FT_Bitmap object. So free buffer is enough.
440 free(mBitmap->buffer); // This buffer created by malloc
447 // Created FT_Glyph object must be released with FT_Done_Glyph
448 FT_Done_Glyph(mGlyph);
454 delete mRenderedBuffer;
455 mRenderedBuffer = nullptr;
461 GlyphCacheManager::GlyphCacheData::GlyphCacheData()
466 mRenderedBuffer{nullptr}
470 GlyphCacheManager::GlyphCacheData::~GlyphCacheData()
475 GlyphCacheManager::GlyphCacheData::GlyphCacheData(GlyphCacheData&& rhs) noexcept
480 mRenderedBuffer{nullptr}
482 *this = std::move(rhs);
485 GlyphCacheManager::GlyphCacheData& GlyphCacheManager::GlyphCacheData::operator=(GlyphCacheData&& rhs) noexcept
487 // Self-assignment detection
493 // Delete self data first.
498 if(rhs.mIsBitmap && rhs.mBitmap)
501 mBitmap = rhs.mBitmap;
503 rhs.mBitmap = nullptr;
509 rhs.mGlyph = nullptr;
516 if(rhs.mRenderedBuffer)
518 mRenderedBuffer = rhs.mRenderedBuffer;
519 rhs.mRenderedBuffer = nullptr;
523 mRenderedBuffer = nullptr;
526 mStyleFlags = rhs.mStyleFlags;
530 } // namespace Dali::TextAbstraction::Internal