Merge "[AT-SPI] Rework intercepting key events" into devel/master
[platform/core/uifw/dali-adaptor.git] / dali / internal / text / text-abstraction / plugin / font-face-glyph-cache-manager.cpp
1 /*
2  * Copyright (c) 2024 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 // CLASS HEADER
18 #include <dali/internal/text/text-abstraction/plugin/font-face-glyph-cache-manager.h>
19
20 // INTERNAL INCLUDES
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>
24
25 // EXTERNAL INCLUDES
26 #include FT_BITMAP_H
27
28 #if defined(DEBUG_ENABLED)
29 extern Dali::Integration::Log::Filter* gFontClientLogFilter;
30 #endif
31
32 namespace Dali::TextAbstraction::Internal
33 {
34 namespace
35 {
36 constexpr uint32_t THRESHOLD_WIDTH_FOR_RLE4_COMPRESSION = 8; // The smallest width of glyph that we use RLE4 method.
37 } // namespace
38
39 GlyphCacheManager::GlyphCacheManager(std::size_t maxNumberOfGlyphCache)
40 : mGlyphCacheMaxSize(maxNumberOfGlyphCache),
41   mLRUGlyphCache(mGlyphCacheMaxSize)
42 {
43   DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, "FontClient::Plugin::GlyphCacheManager Create with maximum size : %d\n", static_cast<int>(mGlyphCacheMaxSize));
44 }
45
46 GlyphCacheManager::~GlyphCacheManager()
47 {
48   ClearCache();
49 }
50
51 bool GlyphCacheManager::GetGlyphCacheDataFromIndex(
52   const FT_Face      freeTypeFace,
53   const GlyphIndex   index,
54   const FT_Int32     flag,
55   const bool         isBoldRequired,
56   GlyphCacheDataPtr& glyphDataPtr,
57   FT_Error&          error)
58 {
59   // Append some error value here instead of FT_Err_Ok.
60   error = static_cast<FT_Error>(-1);
61
62   const GlyphCacheKey key  = GlyphCacheKey(freeTypeFace, index, flag, isBoldRequired);
63   auto                iter = mLRUGlyphCache.Find(key);
64
65   if(iter == mLRUGlyphCache.End())
66   {
67     // If cache size is full, remove oldest glyph.
68     if(mLRUGlyphCache.IsFull())
69     {
70       auto removedData = mLRUGlyphCache.Pop();
71
72       DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, "FontClient::Plugin::GlyphCacheManager::GetGlyphCacheDataFromIndex. Remove oldest cache for glyph : %p\n", removedData->mGlyph);
73     }
74
75     // Create new GlyphCacheData.
76     glyphDataPtr = std::make_shared<GlyphCacheData>();
77
78     GlyphCacheData& glyphData = *glyphDataPtr.get();
79
80     const bool loadSuccess = LoadGlyphDataFromIndex(freeTypeFace, index, flag, isBoldRequired, glyphData, error);
81     if(loadSuccess)
82     {
83       // Copy and cached data.
84       mLRUGlyphCache.Push(key, glyphDataPtr);
85
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);
87     }
88
89     return loadSuccess;
90   }
91   else
92   {
93     error = FT_Err_Ok;
94
95     // We already notify that we use this glyph. And now, copy cached data.
96     glyphDataPtr = mLRUGlyphCache.GetElement(iter);
97
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);
99     return true;
100   }
101 }
102
103 bool GlyphCacheManager::LoadGlyphDataFromIndex(
104   const FT_Face    freeTypeFace,
105   const GlyphIndex index,
106   const FT_Int32   flag,
107   const bool       isBoldRequired,
108   GlyphCacheData&  glyphData,
109   FT_Error&        error)
110 {
111   error = FT_Load_Glyph(freeTypeFace, index, flag);
112   if(FT_Err_Ok == error)
113   {
114     glyphData.mStyleFlags = freeTypeFace->style_flags;
115
116     const bool isEmboldeningRequired = isBoldRequired && !(glyphData.mStyleFlags & FT_STYLE_FLAG_BOLD);
117     if(isEmboldeningRequired)
118     {
119       // Does the software bold.
120       FT_GlyphSlot_Embolden(freeTypeFace->glyph);
121     }
122
123     glyphData.mGlyphMetrics = freeTypeFace->glyph->metrics;
124     glyphData.mIsBitmap     = false;
125     // Load glyph
126     error = FT_Get_Glyph(freeTypeFace->glyph, &glyphData.mGlyph);
127
128     if(glyphData.mGlyph->format == FT_GLYPH_FORMAT_BITMAP)
129     {
130       // Copy original glyph infomation. Due to we use union, we should keep original handle.
131       FT_Glyph bitmapGlyph = glyphData.mGlyph;
132
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;
137
138       // New allocate buffer
139       size_t bufferSize = 0;
140       switch(glyphData.mBitmap->pixel_mode)
141       {
142         case FT_PIXEL_MODE_GRAY:
143         {
144           if(glyphData.mBitmap->pitch == static_cast<int>(glyphData.mBitmap->width))
145           {
146             bufferSize = static_cast<size_t>(glyphData.mBitmap->width) * static_cast<size_t>(glyphData.mBitmap->rows);
147           }
148           break;
149         }
150 #ifdef FREETYPE_BITMAP_SUPPORT
151         case FT_PIXEL_MODE_BGRA:
152         {
153           if(glyphData.mBitmap->pitch == static_cast<int>(glyphData.mBitmap->width << 2u))
154           {
155             bufferSize = (static_cast<size_t>(glyphData.mBitmap->width) * static_cast<size_t>(glyphData.mBitmap->rows)) << 2u;
156           }
157           break;
158         }
159 #endif
160         default:
161         {
162           DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GlyphCacheManager::LoadGlyphDataFromIndex. FontClient Unable to create Bitmap of this PixelType\n");
163           break;
164         }
165       }
166
167       if(bufferSize > 0)
168       {
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))
172         {
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);
178         }
179         else
180         {
181           memcpy(glyphData.mBitmap->buffer, freeTypeFace->glyph->bitmap.buffer, bufferSize);
182         }
183       }
184       else
185       {
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);
190       }
191
192       // Release glyph data.
193       FT_Done_Glyph(bitmapGlyph);
194     }
195
196     if(FT_Err_Ok == error)
197     {
198       return true;
199     }
200   }
201   return false;
202 }
203
204 void GlyphCacheManager::ResizeBitmapGlyph(
205   const FT_Face    freeTypeFace,
206   const GlyphIndex index,
207   const FT_Int32   flag,
208   const bool       isBoldRequired,
209   const uint32_t   desiredWidth,
210   const uint32_t   desiredHeight)
211 {
212   if(desiredWidth * desiredHeight <= 0)
213   {
214     // Skip this API if desired size is zero
215     return;
216   }
217   FT_Error          error;
218   GlyphCacheDataPtr glyphDataPtr;
219   if(GetGlyphCacheDataFromIndex(freeTypeFace, index, flag, isBoldRequired, glyphDataPtr, error))
220   {
221     GlyphCacheData& glyphData = *glyphDataPtr.get();
222     if(DALI_LIKELY(glyphData.mIsBitmap && glyphData.mBitmap))
223     {
224       const bool requiredResize = (glyphData.mBitmap->rows != desiredHeight) || (glyphData.mBitmap->width != desiredWidth);
225       if(requiredResize)
226       {
227         const ImageDimensions inputDimensions(glyphData.mBitmap->width, glyphData.mBitmap->rows);
228         const ImageDimensions desiredDimensions(desiredWidth, desiredHeight);
229
230         uint8_t* desiredBuffer = nullptr;
231
232         switch(glyphData.mBitmap->pixel_mode)
233         {
234           case FT_PIXEL_MODE_GRAY:
235           {
236             if(glyphData.mBitmap->pitch == static_cast<int>(glyphData.mBitmap->width))
237             {
238               desiredBuffer = (uint8_t*)malloc(desiredWidth * desiredHeight * sizeof(uint8_t)); // @note The caller is responsible for deallocating the bitmap data using free.
239
240               if(DALI_UNLIKELY(!desiredBuffer))
241               {
242                 DALI_LOG_ERROR("malloc is failed. request malloc size : %u x %u x 1\n", desiredWidth, desiredHeight);
243               }
244               else
245               {
246                 // Resize bitmap here.
247                 Dali::Internal::Platform::LanczosSample1BPP(glyphData.mBitmap->buffer,
248                                                             inputDimensions,
249                                                             glyphData.mBitmap->width,
250                                                             desiredBuffer,
251                                                             desiredDimensions);
252               }
253             }
254             break;
255           }
256 #ifdef FREETYPE_BITMAP_SUPPORT
257           case FT_PIXEL_MODE_BGRA:
258           {
259             if(glyphData.mBitmap->pitch == static_cast<int>(glyphData.mBitmap->width << 2u))
260             {
261               desiredBuffer = (uint8_t*)malloc((desiredWidth * desiredHeight * sizeof(uint8_t)) << 2u); // @note The caller is responsible for deallocating the bitmap data using free.
262
263               if(DALI_UNLIKELY(!desiredBuffer))
264               {
265                 DALI_LOG_ERROR("malloc is failed. request malloc size : %u x %u x 4\n", desiredWidth, desiredHeight);
266               }
267               else
268               {
269                 // Resize bitmap here.
270                 Dali::Internal::Platform::LanczosSample4BPP(glyphData.mBitmap->buffer,
271                                                             inputDimensions,
272                                                             glyphData.mBitmap->width,
273                                                             desiredBuffer,
274                                                             desiredDimensions);
275               }
276             }
277             break;
278           }
279 #endif
280           default:
281           {
282             DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GlyphCacheManager::ResizeBitmapGlyph. FontClient Unable to create Bitmap of this PixelType\n");
283             break;
284           }
285         }
286
287         if(desiredBuffer)
288         {
289           // Success to resize bitmap glyph.
290           // Release origin bitmap buffer.
291           free(glyphData.mBitmap->buffer);
292
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)
298           {
299             case FT_PIXEL_MODE_GRAY:
300             {
301               glyphData.mBitmap->pitch = desiredWidth;
302               break;
303             }
304 #ifdef FREETYPE_BITMAP_SUPPORT
305             case FT_PIXEL_MODE_BGRA:
306             {
307               glyphData.mBitmap->pitch = desiredWidth << 2u;
308               break;
309             }
310 #endif
311           }
312         }
313       }
314     }
315   }
316 }
317
318 void GlyphCacheManager::CacheRenderedGlyphBuffer(
319   const FT_Face               freeTypeFace,
320   const GlyphIndex            index,
321   const FT_Int32              flag,
322   const bool                  isBoldRequired,
323   const FT_Bitmap&            srcBitmap,
324   const CompressionPolicyType policy)
325 {
326   if(srcBitmap.width * srcBitmap.rows <= 0)
327   {
328     // Skip this API if rendered bitmap size is zero
329     return;
330   }
331   FT_Error          error;
332   GlyphCacheDataPtr glyphDataPtr;
333   if(GetGlyphCacheDataFromIndex(freeTypeFace, index, flag, isBoldRequired, glyphDataPtr, error))
334   {
335     GlyphCacheData& glyphData = *glyphDataPtr.get();
336     if(DALI_LIKELY(!glyphData.mIsBitmap && glyphData.mRenderedBuffer == nullptr))
337     {
338       glyphData.mRenderedBuffer = new TextAbstraction::GlyphBufferData();
339       if(DALI_UNLIKELY(!glyphData.mRenderedBuffer))
340       {
341         DALI_LOG_ERROR("Allocate GlyphBufferData failed\n");
342         return;
343       }
344
345       TextAbstraction::GlyphBufferData& renderBuffer = *glyphData.mRenderedBuffer;
346
347       // Set basic informations.
348       renderBuffer.width  = srcBitmap.width;
349       renderBuffer.height = srcBitmap.rows;
350
351       switch(srcBitmap.pixel_mode)
352       {
353         case FT_PIXEL_MODE_GRAY:
354         {
355           renderBuffer.format = Pixel::L8;
356
357           if(policy == CompressionPolicyType::SPEED)
358           {
359             // If policy is SPEED, we will not compress bitmap.
360             renderBuffer.compressionType = TextAbstraction::GlyphBufferData::CompressionType::NO_COMPRESSION;
361           }
362           else
363           {
364             // If small enough glyph, compress as BPP4 method.
365             if(srcBitmap.width < THRESHOLD_WIDTH_FOR_RLE4_COMPRESSION)
366             {
367               renderBuffer.compressionType = TextAbstraction::GlyphBufferData::CompressionType::BPP_4;
368             }
369             else
370             {
371               renderBuffer.compressionType = TextAbstraction::GlyphBufferData::CompressionType::RLE_4;
372             }
373           }
374
375           const auto compressedBufferSize = TextAbstraction::GlyphBufferData::Compress(srcBitmap.buffer, renderBuffer);
376           if(DALI_UNLIKELY(compressedBufferSize == 0u))
377           {
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;
382             return;
383           }
384           break;
385         }
386 #ifdef FREETYPE_BITMAP_SUPPORT
387         case FT_PIXEL_MODE_BGRA:
388         {
389           // Copy buffer without compress
390           renderBuffer.compressionType = TextAbstraction::GlyphBufferData::CompressionType::NO_COMPRESSION;
391           renderBuffer.format          = Pixel::BGRA8888;
392
393           const auto compressedBufferSize = TextAbstraction::GlyphBufferData::Compress(srcBitmap.buffer, renderBuffer);
394           if(DALI_UNLIKELY(compressedBufferSize == 0u))
395           {
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;
400             return;
401           }
402           break;
403         }
404 #endif
405         default:
406         {
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;
410           break;
411         }
412       }
413     }
414   }
415 }
416
417 void GlyphCacheManager::RemoveGlyphFromFace(const FT_Face freeTypeFace)
418 {
419   uint32_t removedItemCount = 0;
420
421   auto endIter = mLRUGlyphCache.End();
422   for(auto iter = mLRUGlyphCache.Begin(); iter != endIter;)
423   {
424     // Check whether this cached item has inputed freeTypeFace as key.
425     auto keyFace = mLRUGlyphCache.GetKey(iter).mFreeTypeFace;
426     if(keyFace == freeTypeFace)
427     {
428       ++removedItemCount;
429       iter = mLRUGlyphCache.Erase(iter);
430     }
431     else
432     {
433       ++iter;
434     }
435   }
436
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);
438 }
439
440 void GlyphCacheManager::ClearCache(const std::size_t remainCount)
441 {
442   if(remainCount == 0u)
443   {
444     // Clear all cache.
445     mLRUGlyphCache.Clear();
446   }
447   else
448   {
449     // While the cache count is bigger than remainCount, remove oldest glyph.
450     while(mLRUGlyphCache.Count() > remainCount)
451     {
452       auto removedData = mLRUGlyphCache.Pop();
453
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);
455     }
456   }
457 }
458
459 // GlyphCacheManager::GlyphCacheData
460
461 void GlyphCacheManager::GlyphCacheData::ReleaseGlyphData()
462 {
463   if(mIsBitmap && mBitmap)
464   {
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
468
469     delete mBitmap;
470     mBitmap = nullptr;
471   }
472   else if(mGlyph)
473   {
474     // Created FT_Glyph object must be released with FT_Done_Glyph
475     FT_Done_Glyph(mGlyph);
476     mGlyph = nullptr;
477   }
478
479   if(mRenderedBuffer)
480   {
481     delete mRenderedBuffer;
482     mRenderedBuffer = nullptr;
483   }
484
485   mStyleFlags = 0;
486 }
487
488 GlyphCacheManager::GlyphCacheData::GlyphCacheData()
489 : mGlyph{nullptr},
490   mGlyphMetrics{},
491   mStyleFlags{0},
492   mIsBitmap{false},
493   mRenderedBuffer{nullptr}
494 {
495 }
496
497 GlyphCacheManager::GlyphCacheData::~GlyphCacheData()
498 {
499   ReleaseGlyphData();
500 }
501
502 GlyphCacheManager::GlyphCacheData::GlyphCacheData(GlyphCacheData&& rhs) noexcept
503 : mGlyph{nullptr},
504   mGlyphMetrics{},
505   mStyleFlags{0},
506   mIsBitmap{false},
507   mRenderedBuffer{nullptr}
508 {
509   *this = std::move(rhs);
510 }
511
512 GlyphCacheManager::GlyphCacheData& GlyphCacheManager::GlyphCacheData::operator=(GlyphCacheData&& rhs) noexcept
513 {
514   // Self-assignment detection
515   if(this == &rhs)
516   {
517     return *this;
518   }
519
520   // Delete self data first.
521   ReleaseGlyphData();
522
523   mIsBitmap = false;
524
525   if(rhs.mIsBitmap && rhs.mBitmap)
526   {
527     mIsBitmap = true;
528     mBitmap   = rhs.mBitmap;
529
530     rhs.mBitmap = nullptr;
531   }
532   else if(rhs.mGlyph)
533   {
534     mGlyph = rhs.mGlyph;
535
536     rhs.mGlyph = nullptr;
537   }
538   else
539   {
540     mGlyph = nullptr;
541   }
542
543   if(rhs.mRenderedBuffer)
544   {
545     mRenderedBuffer     = rhs.mRenderedBuffer;
546     rhs.mRenderedBuffer = nullptr;
547   }
548   else
549   {
550     mRenderedBuffer = nullptr;
551   }
552
553   mStyleFlags = rhs.mStyleFlags;
554   return *this;
555 }
556
557 } // namespace Dali::TextAbstraction::Internal