Make GlyphBufferData as another class
[platform/core/uifw/dali-adaptor.git] / dali / internal / text / text-abstraction / plugin / font-face-glyph-cache-manager.cpp
1 /*
2  * Copyright (c) 2022 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   GlyphCacheData&  glyphData,
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       // Release Glyph data resource
75       removedData.ReleaseGlyphData();
76     }
77
78     const bool loadSuccess = LoadGlyphDataFromIndex(freeTypeFace, index, flag, isBoldRequired, glyphData, error);
79     if(loadSuccess)
80     {
81       // Copy and cached data.
82       mLRUGlyphCache.Push(key, glyphData);
83
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);
85     }
86
87     return loadSuccess;
88   }
89   else
90   {
91     error = FT_Err_Ok;
92
93     // We already notify that we use this glyph. And now, copy cached data.
94     glyphData = mLRUGlyphCache.GetElement(iter);
95
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);
97     return true;
98   }
99 }
100
101 bool GlyphCacheManager::LoadGlyphDataFromIndex(
102   const FT_Face    freeTypeFace,
103   const GlyphIndex index,
104   const FT_Int32   flag,
105   const bool       isBoldRequired,
106   GlyphCacheData&  glyphData,
107   FT_Error&        error)
108 {
109   error = FT_Load_Glyph(freeTypeFace, index, flag);
110   if(FT_Err_Ok == error)
111   {
112     glyphData.mStyleFlags = freeTypeFace->style_flags;
113
114     const bool isEmboldeningRequired = isBoldRequired && !(glyphData.mStyleFlags & FT_STYLE_FLAG_BOLD);
115     if(isEmboldeningRequired)
116     {
117       // Does the software bold.
118       FT_GlyphSlot_Embolden(freeTypeFace->glyph);
119     }
120
121     glyphData.mGlyphMetrics = freeTypeFace->glyph->metrics;
122     glyphData.mIsBitmap     = false;
123     // Load glyph
124     error = FT_Get_Glyph(freeTypeFace->glyph, &glyphData.mGlyph);
125
126     if(glyphData.mGlyph->format == FT_GLYPH_FORMAT_BITMAP)
127     {
128       // Copy original glyph infomation. Due to we use union, we should keep original handle.
129       FT_Glyph bitmapGlyph = glyphData.mGlyph;
130
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;
135
136       // New allocate buffer
137       size_t bufferSize = 0;
138       switch(glyphData.mBitmap->pixel_mode)
139       {
140         case FT_PIXEL_MODE_GRAY:
141         {
142           if(glyphData.mBitmap->pitch == static_cast<int>(glyphData.mBitmap->width))
143           {
144             bufferSize = static_cast<size_t>(glyphData.mBitmap->width) * static_cast<size_t>(glyphData.mBitmap->rows);
145           }
146           break;
147         }
148 #ifdef FREETYPE_BITMAP_SUPPORT
149         case FT_PIXEL_MODE_BGRA:
150         {
151           if(glyphData.mBitmap->pitch == static_cast<int>(glyphData.mBitmap->width << 2u))
152           {
153             bufferSize = (static_cast<size_t>(glyphData.mBitmap->width) * static_cast<size_t>(glyphData.mBitmap->rows)) << 2u;
154           }
155           break;
156         }
157 #endif
158         default:
159         {
160           DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GlyphCacheManager::LoadGlyphDataFromIndex. FontClient Unable to create Bitmap of this PixelType\n");
161           break;
162         }
163       }
164
165       if(bufferSize > 0)
166       {
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);
170       }
171       else
172       {
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);
177       }
178
179       // Release glyph data.
180       FT_Done_Glyph(bitmapGlyph);
181     }
182
183     if(FT_Err_Ok == error)
184     {
185       return true;
186     }
187   }
188   return false;
189 }
190
191 void GlyphCacheManager::ResizeBitmapGlyph(
192   const FT_Face    freeTypeFace,
193   const GlyphIndex index,
194   const FT_Int32   flag,
195   const bool       isBoldRequired,
196   const uint32_t   desiredWidth,
197   const uint32_t   desiredHeight)
198 {
199   if(desiredWidth * desiredHeight <= 0)
200   {
201     // Skip this API if desired size is zero
202     return;
203   }
204   FT_Error       error;
205   GlyphCacheData originGlyphData;
206   if(GetGlyphCacheDataFromIndex(freeTypeFace, index, flag, isBoldRequired, originGlyphData, error))
207   {
208     if(DALI_LIKELY(originGlyphData.mIsBitmap && originGlyphData.mBitmap))
209     {
210       const bool requiredResize = (originGlyphData.mBitmap->rows != desiredHeight) || (originGlyphData.mBitmap->width != desiredWidth);
211       if(requiredResize)
212       {
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);
216
217         GlyphCacheData& destinationGlpyhData = mLRUGlyphCache.GetElement(iter);
218
219         const ImageDimensions inputDimensions(destinationGlpyhData.mBitmap->width, destinationGlpyhData.mBitmap->rows);
220         const ImageDimensions desiredDimensions(desiredWidth, desiredHeight);
221
222         uint8_t* desiredBuffer = nullptr;
223
224         switch(destinationGlpyhData.mBitmap->pixel_mode)
225         {
226           case FT_PIXEL_MODE_GRAY:
227           {
228             if(destinationGlpyhData.mBitmap->pitch == static_cast<int>(destinationGlpyhData.mBitmap->width))
229             {
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,
233                                                           inputDimensions,
234                                                           destinationGlpyhData.mBitmap->width,
235                                                           desiredBuffer,
236                                                           desiredDimensions);
237             }
238             break;
239           }
240 #ifdef FREETYPE_BITMAP_SUPPORT
241           case FT_PIXEL_MODE_BGRA:
242           {
243             if(destinationGlpyhData.mBitmap->pitch == static_cast<int>(destinationGlpyhData.mBitmap->width << 2u))
244             {
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,
248                                                           inputDimensions,
249                                                           destinationGlpyhData.mBitmap->width,
250                                                           desiredBuffer,
251                                                           desiredDimensions);
252             }
253             break;
254           }
255 #endif
256           default:
257           {
258             DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GlyphCacheManager::ResizeBitmapGlyph. FontClient Unable to create Bitmap of this PixelType\n");
259             break;
260           }
261         }
262
263         if(desiredBuffer)
264         {
265           // Success to resize bitmap glyph.
266           // Release origin bitmap buffer.
267           free(destinationGlpyhData.mBitmap->buffer);
268
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)
274           {
275             case FT_PIXEL_MODE_GRAY:
276             {
277               destinationGlpyhData.mBitmap->pitch = desiredWidth;
278               break;
279             }
280 #ifdef FREETYPE_BITMAP_SUPPORT
281             case FT_PIXEL_MODE_BGRA:
282             {
283               destinationGlpyhData.mBitmap->pitch = desiredWidth << 2u;
284               break;
285             }
286 #endif
287           }
288         }
289       }
290     }
291   }
292 }
293
294 void GlyphCacheManager::CacheRenderedGlyphBuffer(
295   const FT_Face               freeTypeFace,
296   const GlyphIndex            index,
297   const FT_Int32              flag,
298   const bool                  isBoldRequired,
299   const FT_Bitmap&            srcBitmap,
300   const CompressionPolicyType policy)
301 {
302   if(srcBitmap.width * srcBitmap.rows <= 0)
303   {
304     // Skip this API if rendered bitmap size is zero
305     return;
306   }
307   FT_Error       error;
308   GlyphCacheData originGlyphData;
309   if(GetGlyphCacheDataFromIndex(freeTypeFace, index, flag, isBoldRequired, originGlyphData, error))
310   {
311     if(DALI_LIKELY(!originGlyphData.mIsBitmap && originGlyphData.mRenderedBuffer == nullptr))
312     {
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);
316
317       GlyphCacheData& destinationGlpyhData = mLRUGlyphCache.GetElement(iter);
318
319       destinationGlpyhData.mRenderedBuffer = new TextAbstraction::GlyphBufferData();
320       if(DALI_UNLIKELY(!destinationGlpyhData.mRenderedBuffer))
321       {
322         DALI_LOG_ERROR("Allocate GlyphBufferData failed\n");
323         return;
324       }
325
326       TextAbstraction::GlyphBufferData& renderBuffer = *destinationGlpyhData.mRenderedBuffer;
327
328       // Set basic informations.
329       renderBuffer.width  = srcBitmap.width;
330       renderBuffer.height = srcBitmap.rows;
331
332       switch(srcBitmap.pixel_mode)
333       {
334         case FT_PIXEL_MODE_GRAY:
335         {
336           renderBuffer.format = Pixel::L8;
337
338           if(policy == CompressionPolicyType::SPEED)
339           {
340             // If policy is SPEED, we will not compress bitmap.
341             renderBuffer.compressionType = TextAbstraction::GlyphBufferData::CompressionType::NO_COMPRESSION;
342           }
343           else
344           {
345             // If small enough glyph, compress as BPP4 method.
346             if(srcBitmap.width < THRESHOLD_WIDTH_FOR_RLE4_COMPRESSION)
347             {
348               renderBuffer.compressionType = TextAbstraction::GlyphBufferData::CompressionType::BPP_4;
349             }
350             else
351             {
352               renderBuffer.compressionType = TextAbstraction::GlyphBufferData::CompressionType::RLE_4;
353             }
354           }
355
356           const auto compressedBufferSize = TextAbstraction::GlyphBufferData::Compress(srcBitmap.buffer, renderBuffer);
357           if(DALI_UNLIKELY(compressedBufferSize == 0u))
358           {
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;
363             return;
364           }
365           break;
366         }
367 #ifdef FREETYPE_BITMAP_SUPPORT
368         case FT_PIXEL_MODE_BGRA:
369         {
370           // Copy buffer without compress
371           renderBuffer.compressionType = TextAbstraction::GlyphBufferData::CompressionType::NO_COMPRESSION;
372           renderBuffer.format          = Pixel::BGRA8888;
373
374           const auto compressedBufferSize = TextAbstraction::GlyphBufferData::Compress(srcBitmap.buffer, renderBuffer);
375           if(DALI_UNLIKELY(compressedBufferSize == 0u))
376           {
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;
381             return;
382           }
383           break;
384         }
385 #endif
386         default:
387         {
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;
391           break;
392         }
393       }
394     }
395   }
396 }
397
398 void GlyphCacheManager::RemoveGlyphFromFace(const FT_Face freeTypeFace)
399 {
400   uint32_t removedItemCount = 0;
401
402   auto endIter = mLRUGlyphCache.End();
403   for(auto iter = mLRUGlyphCache.Begin(); iter != endIter;)
404   {
405     // Check whether this cached item has inputed freeTypeFace as key.
406     auto keyFace = mLRUGlyphCache.GetKey(iter).mFreeTypeFace;
407     if(keyFace == freeTypeFace)
408     {
409       ++removedItemCount;
410       iter = mLRUGlyphCache.Erase(iter);
411     }
412     else
413     {
414       ++iter;
415     }
416   }
417
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);
419 }
420
421 void GlyphCacheManager::ClearCache(const std::size_t remainCount)
422 {
423   if(remainCount == 0u)
424   {
425     // Release all data memory first.
426     auto endIter = mLRUGlyphCache.End();
427     for(auto iter = mLRUGlyphCache.Begin(); iter != endIter; ++iter)
428     {
429       // Get the reference of data. and release it.
430       auto& removedData = mLRUGlyphCache.GetElement(iter);
431       removedData.ReleaseGlyphData();
432     }
433
434     // Clear all cache.
435     mLRUGlyphCache.Clear();
436   }
437   else
438   {
439     // While the cache count is bigger than remainCount, remove oldest glyph.
440     while(mLRUGlyphCache.Count() > remainCount)
441     {
442       auto removedData = mLRUGlyphCache.Pop();
443
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);
445
446       // Release Glyph data resource
447       removedData.ReleaseGlyphData();
448     }
449   }
450 }
451
452 void GlyphCacheManager::GlyphCacheData::ReleaseGlyphData()
453 {
454   if(mIsBitmap && mBitmap)
455   {
456     // Created FT_Bitmap object must be released with FT_Bitmap_Done
457     free(mBitmap->buffer); // This buffer created by malloc
458
459     delete mBitmap;
460     mBitmap = nullptr;
461   }
462   else if(mGlyph)
463   {
464     // Created FT_Glyph object must be released with FT_Done_Glyph
465     FT_Done_Glyph(mGlyph);
466     mGlyph = nullptr;
467   }
468
469   if(mRenderedBuffer)
470   {
471     delete mRenderedBuffer;
472     mRenderedBuffer = nullptr;
473   }
474
475   mStyleFlags = 0;
476 }
477
478 } // namespace Dali::TextAbstraction::Internal