2 * Copyright (c) 2018 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.
19 #include <dali/internal/text/text-abstraction/font-client-plugin-impl.h>
22 #include <dali/devel-api/text-abstraction/font-list.h>
23 #include <dali/public-api/common/dali-vector.h>
24 #include <dali/public-api/common/vector-wrapper.h>
25 #include <dali/integration-api/debug.h>
26 #include <dali/integration-api/platform-abstraction.h>
27 #include <dali/internal/text/text-abstraction/font-client-helper.h>
28 #include <dali/internal/imaging/common/image-operations.h>
29 #include <dali/internal/adaptor/common/adaptor-impl.h>
30 #include <dali/devel-api/adaptor-framework/image-loading.h>
33 #include <fontconfig/fontconfig.h>
38 #if defined(DEBUG_ENABLED)
39 Dali::Integration::Log::Filter* gLogFilter = Dali::Integration::Log::Filter::New(Debug::NoLogging, false, "LOG_FONT_CLIENT");
43 * Conversion from Fractional26.6 to float
45 const float FROM_266 = 1.0f / 64.0f;
46 const float POINTS_PER_INCH = 72.f;
48 const std::string DEFAULT_FONT_FAMILY_NAME( "Tizen" );
49 const int DEFAULT_FONT_WIDTH = 100; // normal
50 const int DEFAULT_FONT_WEIGHT = 80; // normal
51 const int DEFAULT_FONT_SLANT = 0; // normal
53 const uint32_t ELLIPSIS_CHARACTER = 0x2026;
55 // http://www.freedesktop.org/software/fontconfig/fontconfig-user.html
57 // NONE -1 --> DEFAULT_FONT_WIDTH (NORMAL) will be used.
67 const int FONT_WIDTH_TYPE_TO_INT[] = { -1, 50, 63, 75, 87, 100, 113, 125, 150, 200 };
68 const unsigned int NUM_FONT_WIDTH_TYPE = sizeof( FONT_WIDTH_TYPE_TO_INT ) / sizeof( int );
70 // NONE -1 --> DEFAULT_FONT_WEIGHT (NORMAL) will be used.
72 // ULTRA_LIGHT, EXTRA_LIGHT 40
74 // DEMI_LIGHT, SEMI_LIGHT 55
78 // DEMI_BOLD, SEMI_BOLD 180
80 // ULTRA_BOLD, EXTRA_BOLD 205
81 // BLACK, HEAVY, EXTRA_BLACK 210
82 const int FONT_WEIGHT_TYPE_TO_INT[] = { -1, 0, 40, 50, 55, 75, 80, 100, 180, 200, 205, 210 };
83 const unsigned int NUM_FONT_WEIGHT_TYPE = sizeof( FONT_WEIGHT_TYPE_TO_INT ) / sizeof( int );
85 // NONE -1 --> DEFAULT_FONT_SLANT (NORMAL) will be used.
89 const int FONT_SLANT_TYPE_TO_INT[] = { -1, 0, 100, 110 };
90 const unsigned int NUM_FONT_SLANT_TYPE = sizeof( FONT_SLANT_TYPE_TO_INT ) / sizeof( int );
99 namespace TextAbstraction
106 * @brief Returns the FontWidth's enum index for the given width value.
108 * @param[in] width The width value.
110 * @return The FontWidth's enum index.
112 FontWidth::Type IntToWidthType( int width )
114 return static_cast<FontWidth::Type>( ValueToIndex( width, FONT_WIDTH_TYPE_TO_INT, NUM_FONT_WIDTH_TYPE - 1u ) );
118 * @brief Returns the FontWeight's enum index for the given weight value.
120 * @param[in] weight The weight value.
122 * @return The FontWeight's enum index.
124 FontWeight::Type IntToWeightType( int weight )
126 return static_cast<FontWeight::Type>( ValueToIndex( weight, FONT_WEIGHT_TYPE_TO_INT, NUM_FONT_WEIGHT_TYPE - 1u ) );
130 * @brief Returns the FontSlant's enum index for the given slant value.
132 * @param[in] slant The slant value.
134 * @return The FontSlant's enum index.
136 FontSlant::Type IntToSlantType( int slant )
138 return static_cast<FontSlant::Type>( ValueToIndex( slant, FONT_SLANT_TYPE_TO_INT, NUM_FONT_SLANT_TYPE - 1u ) );
142 * @brief Free the resources allocated by the FcCharSet objects.
144 * @param[in] characterSets The vector of character sets.
146 void DestroyCharacterSets( CharacterSetList& characterSets )
148 for( auto& item : characterSets )
150 FcCharSetDestroy( item );
154 FontClient::Plugin::FallbackCacheItem::FallbackCacheItem( FontDescription&& font, FontList* fallbackFonts, CharacterSetList* characterSets )
155 : fontDescription{ std::move( font ) },
156 fallbackFonts{ fallbackFonts },
157 characterSets{ characterSets }
161 FontClient::Plugin::FontDescriptionCacheItem::FontDescriptionCacheItem( const FontDescription& fontDescription,
162 FontDescriptionId index )
163 : fontDescription{ fontDescription },
168 FontClient::Plugin::FontDescriptionCacheItem::FontDescriptionCacheItem( FontDescription&& fontDescription,
169 FontDescriptionId index )
170 : fontDescription{ std::move( fontDescription ) },
175 FontClient::Plugin::FontDescriptionSizeCacheItem::FontDescriptionSizeCacheItem( FontDescriptionId validatedFontId,
176 PointSize26Dot6 requestedPointSize,
178 : validatedFontId( validatedFontId ),
179 requestedPointSize( requestedPointSize ),
184 FontClient::Plugin::FontFaceCacheItem::FontFaceCacheItem( FT_Face ftFace,
185 const FontPath& path,
186 PointSize26Dot6 requestedPointSize,
188 const FontMetrics& metrics )
189 : mFreeTypeFace( ftFace ),
191 mRequestedPointSize( requestedPointSize ),
194 mCharacterSet( nullptr ),
195 mFixedSizeIndex( 0 ),
196 mFixedWidthPixels( 0.f ),
197 mFixedHeightPixels( 0.f ),
200 mIsFixedSizeBitmap( false ),
201 mHasColorTables( false )
205 FontClient::Plugin::FontFaceCacheItem::FontFaceCacheItem( FT_Face ftFace,
206 const FontPath& path,
207 PointSize26Dot6 requestedPointSize,
209 const FontMetrics& metrics,
213 bool hasColorTables )
214 : mFreeTypeFace( ftFace ),
216 mRequestedPointSize( requestedPointSize ),
219 mCharacterSet( nullptr ),
220 mFixedSizeIndex( fixedSizeIndex ),
221 mFixedWidthPixels( fixedWidth ),
222 mFixedHeightPixels( fixedHeight ),
225 mIsFixedSizeBitmap( true ),
226 mHasColorTables( hasColorTables )
230 FontClient::Plugin::Plugin( unsigned int horizontalDpi,
231 unsigned int verticalDpi )
232 : mFreeTypeLibrary( nullptr ),
233 mDpiHorizontal( horizontalDpi ),
234 mDpiVertical( verticalDpi ),
235 mDefaultFontDescription(),
240 mValidatedFontCache(),
241 mFontDescriptionCache( 1u ),
242 mCharacterSetCache(),
243 mFontDescriptionSizeCache(),
244 mVectorFontCache( nullptr ),
246 mEmbeddedItemCache(),
247 mDefaultFontDescriptionCached( false )
249 mCharacterSetCache.Resize( 1u );
251 int error = FT_Init_FreeType( &mFreeTypeLibrary );
252 if( FT_Err_Ok != error )
254 DALI_LOG_INFO( gLogFilter, Debug::General, "FreeType Init error: %d\n", error );
257 #ifdef ENABLE_VECTOR_BASED_TEXT_RENDERING
258 mVectorFontCache = new VectorFontCache( mFreeTypeLibrary );
262 FontClient::Plugin::~Plugin()
264 ClearFallbackCache( mFallbackCache );
266 // Free the resources allocated by the FcCharSet objects.
267 DestroyCharacterSets( mDefaultFontCharacterSets );
268 DestroyCharacterSets( mCharacterSetCache );
269 ClearCharacterSetFromFontFaceCache();
271 #ifdef ENABLE_VECTOR_BASED_TEXT_RENDERING
272 delete mVectorFontCache;
274 FT_Done_FreeType( mFreeTypeLibrary );
277 void FontClient::Plugin::SetDpi( unsigned int horizontalDpi,
278 unsigned int verticalDpi )
280 mDpiHorizontal = horizontalDpi;
281 mDpiVertical = verticalDpi;
284 void FontClient::Plugin::ResetSystemDefaults()
286 mDefaultFontDescriptionCached = false;
289 void FontClient::Plugin::SetFontList( const FontDescription& fontDescription, FontList& fontList, CharacterSetList& characterSetList )
291 DALI_LOG_INFO( gLogFilter, Debug::General, "-->FontClient::Plugin::SetFontList\n" );
292 DALI_LOG_INFO( gLogFilter, Debug::General, " description; family : [%s]\n", fontDescription.family.c_str() );
293 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " width : [%s]\n", FontWidth::Name[fontDescription.width] );
294 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " weight : [%s]\n", FontWeight::Name[fontDescription.weight] );
295 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " slant : [%s]\n\n", FontSlant::Name[fontDescription.slant] );
299 FcPattern* fontFamilyPattern = CreateFontFamilyPattern( fontDescription ); // Creates a pattern that needs to be destroyed by calling FcPatternDestroy.
301 FcResult result = FcResultMatch;
303 // Match the pattern.
304 FcFontSet* fontSet = FcFontSort( nullptr /* use default configure */,
306 false /* don't trim */,
308 &result ); // FcFontSort creates a font set that needs to be destroyed by calling FcFontSetDestroy.
310 if( nullptr != fontSet )
312 DALI_LOG_INFO( gLogFilter, Debug::General, " number of fonts found : [%d]\n", fontSet->nfont );
313 // Reserve some space to avoid reallocations.
314 fontList.reserve( fontSet->nfont );
316 for( int i = 0u; i < fontSet->nfont; ++i )
318 FcPattern* fontPattern = fontSet->fonts[i];
322 // Skip fonts with no path
323 if( GetFcString( fontPattern, FC_FILE, path ) )
325 // Retrieve the character set. Need to call FcCharSetDestroy to free the resources.
326 FcCharSet* characterSet = nullptr;
327 FcPatternGetCharSet( fontPattern, FC_CHARSET, 0u, &characterSet );
329 // Increase the reference counter of the character set.
330 characterSetList.PushBack( FcCharSetCopy( characterSet ) );
332 fontList.push_back( FontDescription() );
333 FontDescription& newFontDescription = fontList.back();
335 newFontDescription.path = std::move( path );
340 GetFcString( fontPattern, FC_FAMILY, newFontDescription.family );
341 GetFcInt( fontPattern, FC_WIDTH, width );
342 GetFcInt( fontPattern, FC_WEIGHT, weight );
343 GetFcInt( fontPattern, FC_SLANT, slant );
344 newFontDescription.width = IntToWidthType( width );
345 newFontDescription.weight = IntToWeightType( weight );
346 newFontDescription.slant = IntToSlantType( slant );
348 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " description; family : [%s]\n", newFontDescription.family.c_str() );
349 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " path : [%s]\n", newFontDescription.path.c_str() );
350 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " width : [%s]\n", FontWidth::Name[newFontDescription.width] );
351 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " weight : [%s]\n", FontWeight::Name[newFontDescription.weight] );
352 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " slant : [%s]\n\n", FontSlant::Name[newFontDescription.slant] );
356 // Destroys the font set created by FcFontSort.
357 FcFontSetDestroy( fontSet );
361 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " No fonts found.\n" );
364 // Destroys the pattern created by FcPatternCreate in CreateFontFamilyPattern.
365 FcPatternDestroy( fontFamilyPattern );
367 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::SetFontList\n" );
370 void FontClient::Plugin::GetDefaultFonts( FontList& defaultFonts )
372 DALI_LOG_INFO( gLogFilter, Debug::General, "-->FontClient::Plugin::GetDefaultFonts\n" );
374 if( mDefaultFonts.empty() )
376 FontDescription fontDescription;
377 fontDescription.family = DEFAULT_FONT_FAMILY_NAME; // todo This could be set to the Platform font
378 fontDescription.width = IntToWidthType( DEFAULT_FONT_WIDTH );
379 fontDescription.weight = IntToWeightType( DEFAULT_FONT_WEIGHT );
380 fontDescription.slant = IntToSlantType( DEFAULT_FONT_SLANT );
381 SetFontList( fontDescription, mDefaultFonts, mDefaultFontCharacterSets );
384 defaultFonts = mDefaultFonts;
386 DALI_LOG_INFO( gLogFilter, Debug::General, " number of default fonts : [%d]\n", mDefaultFonts.size() );
387 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::GetDefaultFonts\n" );
390 void FontClient::Plugin::GetDefaultPlatformFontDescription( FontDescription& fontDescription )
392 DALI_LOG_INFO( gLogFilter, Debug::General, "-->FontClient::Plugin::GetDefaultPlatformFontDescription\n");
394 if( !mDefaultFontDescriptionCached )
396 // Clear any font config stored info in the caches.
398 // Decrease the reference counter and eventually free the resources allocated by FcCharSet objects.
399 DestroyCharacterSets( mDefaultFontCharacterSets );
400 DestroyCharacterSets( mCharacterSetCache );
401 mDefaultFontCharacterSets.Clear();
402 mCharacterSetCache.Clear();
404 for( auto& item : mFallbackCache )
406 // Decrease the reference counter and eventually free the resources allocated by FcCharSet objects.
407 DestroyCharacterSets( *item.characterSets );
409 delete item.characterSets;
410 item.characterSets = nullptr;
413 // Set the character set pointer as null. Will be created again the next time IsCharacterSupportedByFont()
414 ClearCharacterSetFromFontFaceCache();
416 // FcInitBringUptoDate did not seem to reload config file as was still getting old default font.
417 FcInitReinitialize();
419 FcPattern* matchPattern = FcPatternCreate(); // Creates a pattern that needs to be destroyed by calling FcPatternDestroy.
421 if( nullptr != matchPattern )
423 FcConfigSubstitute( nullptr, matchPattern, FcMatchPattern );
424 FcDefaultSubstitute( matchPattern );
426 FcCharSet* characterSet = nullptr;
427 MatchFontDescriptionToPattern( matchPattern, mDefaultFontDescription, &characterSet );
428 // Decrease the reference counter of the character set as it's not stored.
429 FcCharSetDestroy( characterSet );
431 // Destroys the pattern created.
432 FcPatternDestroy( matchPattern );
435 // Create again the character sets as they are not valid after FcInitReinitialize()
437 for( const auto& description : mDefaultFonts )
439 mDefaultFontCharacterSets.PushBack( FcCharSetCopy( CreateCharacterSetFromDescription( description ) ) );
442 for( const auto& description : mFontDescriptionCache )
444 mCharacterSetCache.PushBack( FcCharSetCopy( CreateCharacterSetFromDescription( description ) ) );
447 for( auto& item : mFallbackCache )
449 if( nullptr != item.fallbackFonts )
451 if( nullptr == item.characterSets )
453 item.characterSets = new CharacterSetList;
456 for( const auto& description : *( item.fallbackFonts ) )
458 item.characterSets->PushBack( FcCharSetCopy( CreateCharacterSetFromDescription( description ) ) );
463 mDefaultFontDescriptionCached = true;
466 fontDescription.path = mDefaultFontDescription.path;
467 fontDescription.family = mDefaultFontDescription.family;
468 fontDescription.width = mDefaultFontDescription.width;
469 fontDescription.weight = mDefaultFontDescription.weight;
470 fontDescription.slant = mDefaultFontDescription.slant;
472 DALI_LOG_INFO( gLogFilter, Debug::General, " description; family : [%s]\n", fontDescription.family.c_str() );
473 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " path : [%s]\n", fontDescription.path.c_str() );
474 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " width : [%s]\n", FontWidth::Name[fontDescription.width] );
475 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " weight : [%s]\n", FontWeight::Name[fontDescription.weight] );
476 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " slant : [%s]\n\n", FontSlant::Name[fontDescription.slant] );
477 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::GetDefaultPlatformFontDescription\n");
480 void FontClient::Plugin::GetSystemFonts( FontList& systemFonts )
482 DALI_LOG_INFO( gLogFilter, Debug::General, "-->FontClient::Plugin::GetSystemFonts\n");
484 if( mSystemFonts.empty() )
489 systemFonts = mSystemFonts;
490 DALI_LOG_INFO( gLogFilter, Debug::General, " number of system fonts : [%d]\n", mSystemFonts.size() );
491 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::GetSystemFonts\n");
494 void FontClient::Plugin::GetDescription( FontId id,
495 FontDescription& fontDescription ) const
497 DALI_LOG_INFO( gLogFilter, Debug::General, "-->FontClient::Plugin::GetDescription\n");
498 DALI_LOG_INFO( gLogFilter, Debug::General, " font id : %d\n", id );
499 const FontId index = id - 1u;
501 if( ( id > 0u ) && ( index < mFontIdCache.Count() ) )
503 const FontIdCacheItem& fontIdCacheItem = mFontIdCache[index];
504 switch( fontIdCacheItem.type )
506 case FontDescription::FACE_FONT:
508 for( const auto& item : mFontDescriptionSizeCache )
510 if( item.fontId == fontIdCacheItem.id )
512 fontDescription = *( mFontDescriptionCache.begin() + item.validatedFontId );
514 DALI_LOG_INFO( gLogFilter, Debug::General, " description; family : [%s]\n", fontDescription.family.c_str() );
515 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " path : [%s]\n", fontDescription.path.c_str() );
516 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " width : [%s]\n", FontWidth::Name[fontDescription.width] );
517 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " weight : [%s]\n", FontWeight::Name[fontDescription.weight] );
518 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " slant : [%s]\n\n", FontSlant::Name[fontDescription.slant] );
519 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::GetDescription\n");
525 case FontDescription::BITMAP_FONT:
527 fontDescription.type = FontDescription::BITMAP_FONT;
528 fontDescription.family = mBitmapFontCache[fontIdCacheItem.id].font.name;
533 DALI_LOG_INFO(gLogFilter, Debug::General, " Invalid type of font\n");
534 fontDescription.type = FontDescription::INVALID;
535 fontDescription.family.clear();
540 DALI_LOG_INFO( gLogFilter, Debug::General, " No description found for the font ID %d\n", id );
541 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::GetDescription\n");
544 PointSize26Dot6 FontClient::Plugin::GetPointSize( FontId id )
546 DALI_LOG_INFO( gLogFilter, Debug::General, "-->FontClient::Plugin::GetPointSize\n");
547 DALI_LOG_INFO( gLogFilter, Debug::General, " font id : %d\n", id );
548 const FontId index = id - 1u;
551 ( index < mFontIdCache.Count() ) )
553 const FontIdCacheItem& fontIdCacheItem = mFontIdCache[index];
555 switch( fontIdCacheItem.type )
557 case FontDescription::FACE_FONT:
559 DALI_LOG_INFO( gLogFilter, Debug::General, " point size : %d\n", ( *( mFontFaceCache.begin() + fontIdCacheItem.id ) ).mRequestedPointSize );
560 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::GetPointSize\n");
561 return ( *( mFontFaceCache.begin() + fontIdCacheItem.id ) ).mRequestedPointSize;
563 case FontDescription::BITMAP_FONT:
565 return TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
569 DALI_LOG_INFO(gLogFilter, Debug::General, " Invalid type of font\n");
575 DALI_LOG_INFO( gLogFilter, Debug::General, " Invalid font ID %d\n", id );
578 DALI_LOG_INFO( gLogFilter, Debug::General, " default point size : %d\n", TextAbstraction::FontClient::DEFAULT_POINT_SIZE );
579 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::GetPointSize\n");
580 return TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
583 bool FontClient::Plugin::IsCharacterSupportedByFont( FontId fontId, Character character )
585 DALI_LOG_INFO( gLogFilter, Debug::General, "-->FontClient::Plugin::IsCharacterSupportedByFont\n");
586 DALI_LOG_INFO( gLogFilter, Debug::General, " font id : %d\n", fontId );
587 DALI_LOG_INFO( gLogFilter, Debug::General, " character : %p\n", character );
589 if( ( fontId < 1u ) || ( fontId > mFontIdCache.Count() ) )
591 DALI_LOG_INFO( gLogFilter, Debug::General, " Invalid font id. Number of items in the cache: %d\n",mFontIdCache.Count());
592 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::IsCharacterSupportedByFont\n");
598 bool isSupported = false;
600 const FontIdCacheItem& fontIdCacheItem = mFontIdCache[fontId];
602 switch( fontIdCacheItem.type )
604 case FontDescription::FACE_FONT:
606 if( fontIdCacheItem.id < mFontFaceCache.size() )
608 FontFaceCacheItem& cacheItem = mFontFaceCache[fontIdCacheItem.id];
610 if( nullptr == cacheItem.mCharacterSet )
612 // Create again the character set.
613 // It can be null if the ResetSystemDefaults() method has been called.
615 FontDescription description;
616 description.path = cacheItem.mPath;
617 description.family = std::move( FontFamily( cacheItem.mFreeTypeFace->family_name ) );
618 description.weight = FontWeight::NONE;
619 description.width = FontWidth::NONE;
620 description.slant = FontSlant::NONE;
622 // Note FreeType doesn't give too much info to build a proper font style.
623 if( cacheItem.mFreeTypeFace->style_flags & FT_STYLE_FLAG_ITALIC )
625 description.slant = FontSlant::ITALIC;
627 if( cacheItem.mFreeTypeFace->style_flags & FT_STYLE_FLAG_BOLD )
629 description.weight = FontWeight::BOLD;
632 cacheItem.mCharacterSet = FcCharSetCopy( CreateCharacterSetFromDescription( description ) );
635 isSupported = FcCharSetHasChar( cacheItem.mCharacterSet, character );
639 case FontDescription::BITMAP_FONT:
641 const BitmapFont& bitmapFont = mBitmapFontCache[fontIdCacheItem.id].font;
643 for( const auto& glyph : bitmapFont.glyphs )
645 if( glyph.utf32 == character )
655 DALI_LOG_INFO(gLogFilter, Debug::General, " Invalid type of font\n");
659 DALI_LOG_INFO( gLogFilter, Debug::General, " is supported : %s\n", (isSupported ? "true" : "false") );
660 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::IsCharacterSupportedByFont\n");
664 FontId FontClient::Plugin::FindFontForCharacter( const FontList& fontList,
665 const CharacterSetList& characterSetList,
667 PointSize26Dot6 requestedPointSize,
670 DALI_ASSERT_DEBUG( ( fontList.size() == characterSetList.Count() ) && "FontClient::Plugin::FindFontForCharacter. Different number of fonts and character sets." );
672 DALI_LOG_INFO( gLogFilter, Debug::General, "-->FontClient::Plugin::FindFontForCharacter\n" );
673 DALI_LOG_INFO( gLogFilter, Debug::General, " character : %p\n", character );
674 DALI_LOG_INFO( gLogFilter, Debug::General, " requestedPointSize : %d\n", requestedPointSize );
675 DALI_LOG_INFO( gLogFilter, Debug::General, " preferColor : %s\n", ( preferColor ? "true" : "false" ) );
678 bool foundColor = false;
680 DALI_LOG_INFO( gLogFilter, Debug::General, " number of fonts : %d\n", fontList.size() );
682 // Traverse the list of fonts.
683 // Check for each font if supports the character.
684 for( unsigned int index = 0u, numberOfFonts = fontList.size(); index < numberOfFonts; ++index )
686 const FontDescription& description = fontList[index];
687 const FcCharSet* const characterSet = characterSetList[index];
689 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " description; family : [%s]\n", description.family.c_str() );
690 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " path : [%s]\n", description.path.c_str() );
691 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " width : [%s]\n", FontWidth::Name[description.width] );
692 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " weight : [%s]\n", FontWeight::Name[description.weight] );
693 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " slant : [%s]\n\n", FontSlant::Name[description.slant] );
695 bool foundInRanges = false;
696 if( nullptr != characterSet )
698 foundInRanges = FcCharSetHasChar( characterSet, character );
703 fontId = GetFontId( description,
707 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " font id : %d\n", fontId );
711 if( ( fontId > 0 ) &&
712 ( fontId - 1u < mFontIdCache.Count() ) )
714 const FontFaceCacheItem& item = mFontFaceCache[mFontIdCache[fontId - 1u].id];
716 foundColor = item.mHasColorTables;
719 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " foundColor : %s\n", ( foundColor ? "true" : "false" ) );
722 // Keep going unless we prefer a different (color) font.
723 if( !preferColor || foundColor )
730 DALI_LOG_INFO( gLogFilter, Debug::General, " font id : %d\n", fontId );
731 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::FindFontForCharacter\n" );
735 FontId FontClient::Plugin::FindDefaultFont( Character charcode,
736 PointSize26Dot6 requestedPointSize,
739 DALI_LOG_INFO( gLogFilter, Debug::General, "-->FontClient::Plugin::FindDefaultFont\n" );
740 DALI_LOG_INFO( gLogFilter, Debug::General, " character : %p\n", charcode );
741 DALI_LOG_INFO( gLogFilter, Debug::General, " requestedPointSize : %d\n", requestedPointSize );
742 DALI_LOG_INFO( gLogFilter, Debug::General, " preferColor : %s\n", ( preferColor ? "true" : "false" ) );
746 // Create the list of default fonts if it has not been created.
747 if( mDefaultFonts.empty() )
749 FontDescription fontDescription;
750 fontDescription.family = DEFAULT_FONT_FAMILY_NAME;
751 fontDescription.width = IntToWidthType( DEFAULT_FONT_WIDTH );
752 fontDescription.weight = IntToWeightType( DEFAULT_FONT_WEIGHT );
753 fontDescription.slant = IntToSlantType( DEFAULT_FONT_SLANT );
755 SetFontList( fontDescription, mDefaultFonts, mDefaultFontCharacterSets );
757 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of default fonts : %d\n", mDefaultFonts.size() );
760 // Traverse the list of default fonts.
761 // Check for each default font if supports the character.
762 fontId = FindFontForCharacter( mDefaultFonts, mDefaultFontCharacterSets, charcode, requestedPointSize, preferColor );
764 DALI_LOG_INFO( gLogFilter, Debug::General, " font id : %d\n", fontId );
765 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::FindDefaultFont\n" );
770 FontId FontClient::Plugin::FindFallbackFont( Character charcode,
771 const FontDescription& preferredFontDescription,
772 PointSize26Dot6 requestedPointSize,
775 DALI_LOG_INFO( gLogFilter, Debug::General, "-->FontClient::Plugin::FindFallbackFont\n" );
776 DALI_LOG_INFO( gLogFilter, Debug::General, " character : %p\n", charcode );
777 DALI_LOG_INFO( gLogFilter, Debug::General, " requestedPointSize : %d\n", requestedPointSize );
778 DALI_LOG_INFO( gLogFilter, Debug::General, " preferColor : %s\n", ( preferColor ? "true" : "false" ) );
780 // The font id to be returned.
783 FontDescription fontDescription;
785 // Fill the font description with the preferred font description and complete with the defaults.
786 fontDescription.family = preferredFontDescription.family.empty() ? DEFAULT_FONT_FAMILY_NAME : preferredFontDescription.family;
787 fontDescription.weight = ( ( FontWeight::NONE == preferredFontDescription.weight ) ? IntToWeightType( DEFAULT_FONT_WEIGHT ) : preferredFontDescription.weight );
788 fontDescription.width = ( ( FontWidth::NONE == preferredFontDescription.width ) ? IntToWidthType( DEFAULT_FONT_WIDTH ) : preferredFontDescription.width );
789 fontDescription.slant = ( ( FontSlant::NONE == preferredFontDescription.slant ) ? IntToSlantType( DEFAULT_FONT_SLANT ) : preferredFontDescription.slant );
791 DALI_LOG_INFO( gLogFilter, Debug::General, " preferredFontDescription --> fontDescription\n" );
792 DALI_LOG_INFO( gLogFilter, Debug::General, " [%s] --> [%s]\n", preferredFontDescription.family.c_str(), fontDescription.family.c_str() );
793 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " [%s] --> [%s]\n", FontWeight::Name[preferredFontDescription.weight], FontWeight::Name[fontDescription.weight] );
794 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " [%s] --> [%s]\n", FontWidth::Name[preferredFontDescription.width], FontWidth::Name[fontDescription.width] );
795 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " [%s] --> [%s]\n", FontSlant::Name[preferredFontDescription.slant], FontSlant::Name[fontDescription.slant] );
797 // Check first if the font's description has been queried before.
798 FontList* fontList = nullptr;
799 CharacterSetList* characterSetList = nullptr;
801 if( !FindFallbackFontList( fontDescription, fontList, characterSetList ) )
803 fontList = new FontList;
804 characterSetList = new CharacterSetList;
806 SetFontList( fontDescription, *fontList, *characterSetList );
808 // Add the font-list to the cache.
809 mFallbackCache.push_back( std::move( FallbackCacheItem( std::move( fontDescription ), fontList, characterSetList ) ) );
812 if( fontList && characterSetList )
814 fontId = FindFontForCharacter( *fontList, *characterSetList, charcode, requestedPointSize, preferColor );
817 DALI_LOG_INFO( gLogFilter, Debug::General, " font id : %d\n", fontId );
818 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::FindFallbackFont\n");
822 FontId FontClient::Plugin::GetFontId( const FontPath& path,
823 PointSize26Dot6 requestedPointSize,
825 bool cacheDescription )
827 DALI_LOG_INFO( gLogFilter, Debug::General, "-->FontClient::Plugin::GetFontId\n" );
828 DALI_LOG_INFO( gLogFilter, Debug::General, " path : [%s]\n", path.c_str() );
829 DALI_LOG_INFO( gLogFilter, Debug::General, " requestedPointSize : %d\n", requestedPointSize );
833 if( nullptr != mFreeTypeLibrary )
836 if( FindFont( path, requestedPointSize, faceIndex, foundId ) )
842 id = CreateFont( path, requestedPointSize, faceIndex, cacheDescription );
846 DALI_LOG_INFO( gLogFilter, Debug::General, " font id : %d\n", id );
847 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::GetFontId\n" );
852 FontId FontClient::Plugin::GetFontId( const FontDescription& fontDescription,
853 PointSize26Dot6 requestedPointSize,
854 FaceIndex faceIndex )
856 DALI_LOG_INFO( gLogFilter, Debug::General, "-->FontClient::Plugin::GetFontId\n" );
857 DALI_LOG_INFO( gLogFilter, Debug::General, " description; family : [%s]\n", fontDescription.family.c_str() );
858 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " path : [%s]\n", fontDescription.path.c_str() );
859 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " width : [%s]\n", FontWidth::Name[fontDescription.width] );
860 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " weight : [%s]\n", FontWeight::Name[fontDescription.weight] );
861 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " slant : [%s]\n\n", FontSlant::Name[fontDescription.slant] );
862 DALI_LOG_INFO( gLogFilter, Debug::General, " requestedPointSize : %d\n", requestedPointSize );
864 // This method uses three vectors which caches:
865 // * The bitmap font cache
866 // * Pairs of non validated font descriptions and an index to a vector with paths to font file names.
867 // * The path to font file names.
868 // * The font ids of pairs 'font point size, index to the vector with paths to font file names'.
870 // 1) Checks if the font description matches with a previously loaded bitmap font.
871 // Returns if a font is found.
872 // 2) Checks in the cache if the font's description has been validated before.
873 // If it was it gets an index to the vector with paths to font file names. Otherwise,
874 // retrieves using font config a path to a font file name which matches with the
875 // font's description. The path is stored in the cache.
877 // 3) Checks in the cache if the pair 'font point size, index to the vector with paths to
878 // font file names' exists. If exists, it gets the font id. If it doesn't it calls
879 // the GetFontId() method with the path to the font file name and the point size to
882 // The font id to be returned.
885 // Check first if the font description matches with a previously loaded bitmap font.
886 if( FindBitmapFont( fontDescription.family, fontId ) )
891 // Check if the font's description have been validated before.
892 FontDescriptionId validatedFontId = 0u;
894 if( !FindValidatedFont( fontDescription,
897 // Use font config to validate the font's description.
898 ValidateFont( fontDescription,
902 FontId fontFaceId = 0u;
903 // Check if exists a pair 'validatedFontId, requestedPointSize' in the cache.
904 if( !FindFont( validatedFontId, requestedPointSize, fontFaceId ) )
906 // Retrieve the font file name path.
907 const FontDescription& description = *( mFontDescriptionCache.begin() + validatedFontId );
909 // Retrieve the font id. Do not cache the description as it has been already cached.
910 fontId = GetFontId( description.path,
915 fontFaceId = mFontIdCache[fontId-1u].id;
916 mFontFaceCache[fontFaceId].mCharacterSet = mCharacterSetCache[validatedFontId];
918 // Cache the pair 'validatedFontId, requestedPointSize' to improve the following queries.
919 mFontDescriptionSizeCache.push_back( FontDescriptionSizeCacheItem( validatedFontId,
925 fontId = mFontFaceCache[fontFaceId].mFontId + 1u;
928 DALI_LOG_INFO( gLogFilter, Debug::General, " font id : %d\n", fontId );
929 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::GetFontId\n" );
934 FontId FontClient::Plugin::GetFontId( const BitmapFont& bitmapFont )
936 for( const auto& item : mBitmapFontCache )
938 if( bitmapFont.name == item.font.name )
944 BitmapFontCacheItem bitmapFontCacheItem;
945 bitmapFontCacheItem.font = bitmapFont;
946 bitmapFontCacheItem.id = mFontIdCache.Count();
948 // Resize the vector with the pixel buffers.
949 bitmapFontCacheItem.pixelBuffers.resize( bitmapFont.glyphs.size() );
951 // Traverse all the glyphs and load the pixel buffer of those with ascender and descender equal to zero.
952 unsigned int index = 0u;
953 for( auto& glyph : bitmapFontCacheItem.font.glyphs )
955 Devel::PixelBuffer& pixelBuffer = bitmapFontCacheItem.pixelBuffers[index];
957 if( EqualsZero( glyph.ascender ) && EqualsZero( glyph.descender ) )
960 pixelBuffer = LoadImageFromFile( glyph.url );
964 glyph.ascender = static_cast<float>(pixelBuffer.GetHeight());
968 bitmapFontCacheItem.font.ascender = std::max( glyph.ascender, bitmapFontCacheItem.font.ascender );
969 bitmapFontCacheItem.font.descender = std::min( glyph.descender, bitmapFontCacheItem.font.descender );
974 FontIdCacheItem fontIdCacheItem;
975 fontIdCacheItem.type = FontDescription::BITMAP_FONT;
976 fontIdCacheItem.id = mBitmapFontCache.size();
978 mBitmapFontCache.push_back( std::move( bitmapFontCacheItem ) );
979 mFontIdCache.PushBack( fontIdCacheItem );
981 return bitmapFontCacheItem.id + 1u;
984 void FontClient::Plugin::ValidateFont( const FontDescription& fontDescription,
985 FontDescriptionId& validatedFontId )
987 DALI_LOG_INFO( gLogFilter, Debug::General, "-->FontClient::Plugin::ValidateFont\n" );
988 DALI_LOG_INFO( gLogFilter, Debug::General, " description; family : [%s]\n", fontDescription.family.c_str() );
989 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " path : [%s]\n", fontDescription.path.c_str() );
990 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " width : [%s]\n", FontWidth::Name[fontDescription.width] );
991 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " weight : [%s]\n", FontWeight::Name[fontDescription.weight] );
992 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " slant : [%s]\n\n", FontSlant::Name[fontDescription.slant] );
994 // Create a font pattern.
995 FcPattern* fontFamilyPattern = CreateFontFamilyPattern( fontDescription );
997 FontDescription description;
999 FcCharSet* characterSet = nullptr;
1000 bool matched = MatchFontDescriptionToPattern( fontFamilyPattern, description, &characterSet );
1001 FcPatternDestroy( fontFamilyPattern );
1003 if( matched && ( nullptr != characterSet ) )
1005 // Set the index to the vector of paths to font file names.
1006 validatedFontId = mFontDescriptionCache.size();
1008 DALI_LOG_INFO( gLogFilter, Debug::General, " matched description; family : [%s]\n", description.family.c_str() );
1009 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " path : [%s]\n", description.path.c_str() );
1010 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " width : [%s]\n", FontWidth::Name[description.width] );
1011 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " weight : [%s]\n", FontWeight::Name[description.weight] );
1012 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " slant : [%s]\n\n", FontSlant::Name[description.slant] );
1013 DALI_LOG_INFO( gLogFilter, Debug::General, " validatedFontId : %d\n", validatedFontId );
1015 // Add the path to the cache.
1016 description.type = FontDescription::FACE_FONT;
1017 mFontDescriptionCache.push_back( description );
1019 // The reference counter of the character set has already been increased in MatchFontDescriptionToPattern.
1020 mCharacterSetCache.PushBack( characterSet );
1022 // Cache the index and the matched font's description.
1023 FontDescriptionCacheItem item( description,
1026 mValidatedFontCache.push_back( std::move( item ) );
1028 if( ( fontDescription.family != description.family ) ||
1029 ( fontDescription.width != description.width ) ||
1030 ( fontDescription.weight != description.weight ) ||
1031 ( fontDescription.slant != description.slant ) )
1033 // Cache the given font's description if it's different than the matched.
1034 FontDescriptionCacheItem item( fontDescription,
1037 mValidatedFontCache.push_back( std::move( item ) );
1042 DALI_LOG_INFO( gLogFilter, Debug::General, " font validation failed for font [%s]\n", fontDescription.family.c_str() );
1045 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::ValidateFont\n" );
1048 void FontClient::Plugin::GetFontMetrics( FontId fontId,
1049 FontMetrics& metrics )
1051 const FontId index = fontId - 1u;
1053 if( ( fontId > 0 ) &&
1054 ( index < mFontIdCache.Count() ) )
1056 const FontIdCacheItem& fontIdCacheItem = mFontIdCache[index];
1058 switch( fontIdCacheItem.type )
1060 case FontDescription::FACE_FONT:
1062 const FontFaceCacheItem& font = mFontFaceCache[fontIdCacheItem.id];
1064 metrics = font.mMetrics;
1066 // Adjust the metrics if the fixed-size font should be down-scaled
1067 if( font.mIsFixedSizeBitmap )
1069 const float desiredFixedSize = static_cast<float>( font.mRequestedPointSize ) * FROM_266 / POINTS_PER_INCH * mDpiVertical;
1071 if( desiredFixedSize > 0.f )
1073 const float scaleFactor = desiredFixedSize / font.mFixedHeightPixels;
1075 metrics.ascender = metrics.ascender * scaleFactor;
1076 metrics.descender = metrics.descender * scaleFactor;
1077 metrics.height = metrics.height * scaleFactor;
1078 metrics.underlinePosition = metrics.underlinePosition * scaleFactor;
1079 metrics.underlineThickness = metrics.underlineThickness * scaleFactor;
1084 case FontDescription::BITMAP_FONT:
1086 const BitmapFontCacheItem& bitmapFontCacheItem = mBitmapFontCache[fontIdCacheItem.id];
1088 metrics.ascender = bitmapFontCacheItem.font.ascender;
1089 metrics.descender = bitmapFontCacheItem.font.descender;
1090 metrics.height = metrics.ascender - metrics.descender;
1091 metrics.underlinePosition = bitmapFontCacheItem.font.underlinePosition;
1092 metrics.underlineThickness = bitmapFontCacheItem.font.underlineThickness;
1097 DALI_LOG_INFO(gLogFilter, Debug::General, " Invalid type of font\n");
1103 DALI_LOG_INFO( gLogFilter, Debug::General, "FontClient::Plugin::GetFontMetrics. Invalid font id : %d\n", fontId );
1107 GlyphIndex FontClient::Plugin::GetGlyphIndex( FontId fontId,
1108 Character charcode )
1110 GlyphIndex glyphIndex = 0u;
1111 const FontId index = fontId - 1u;
1113 if( ( fontId > 0u ) &&
1114 ( index < mFontIdCache.Count() ) )
1116 const FontIdCacheItem& fontIdCacheItem = mFontIdCache[index];
1118 if( FontDescription::FACE_FONT == fontIdCacheItem.type )
1120 FT_Face ftFace = mFontFaceCache[fontIdCacheItem.id].mFreeTypeFace;
1122 glyphIndex = FT_Get_Char_Index( ftFace, charcode );
1129 bool FontClient::Plugin::GetGlyphMetrics( GlyphInfo* array,
1134 if( VECTOR_GLYPH == type )
1136 return GetVectorMetrics( array, size, horizontal );
1139 return GetBitmapMetrics( array, size, horizontal );
1142 bool FontClient::Plugin::GetBitmapMetrics( GlyphInfo* array,
1146 bool success( true );
1148 for( unsigned int i=0; i<size; ++i )
1150 GlyphInfo& glyph = array[i];
1152 FontId index = glyph.fontId - 1u;
1154 if( ( glyph.fontId > 0u ) &&
1155 ( index < mFontIdCache.Count() ) )
1157 const FontIdCacheItem& fontIdCacheItem = mFontIdCache[index];
1159 switch( fontIdCacheItem.type )
1161 case FontDescription::FACE_FONT:
1163 const FontFaceCacheItem& font = mFontFaceCache[fontIdCacheItem.id];
1165 FT_Face ftFace = font.mFreeTypeFace;
1167 #ifdef FREETYPE_BITMAP_SUPPORT
1168 // Check to see if we should be loading a Fixed Size bitmap?
1169 if( font.mIsFixedSizeBitmap )
1171 FT_Select_Size( ftFace, font.mFixedSizeIndex ); ///< @todo: needs to be investigated why it's needed to select the size again.
1172 int error = FT_Load_Glyph( ftFace, glyph.index, FT_LOAD_COLOR );
1173 if ( FT_Err_Ok == error )
1175 glyph.width = font.mFixedWidthPixels;
1176 glyph.height = font.mFixedHeightPixels;
1177 glyph.advance = font.mFixedWidthPixels;
1178 glyph.xBearing = 0.0f;
1179 glyph.yBearing = font.mFixedHeightPixels;
1181 // Adjust the metrics if the fixed-size font should be down-scaled
1182 const float desiredFixedSize = static_cast<float>( font.mRequestedPointSize ) * FROM_266 / POINTS_PER_INCH * mDpiVertical;
1184 if( desiredFixedSize > 0.f )
1186 const float scaleFactor = desiredFixedSize / font.mFixedHeightPixels;
1188 glyph.width = glyph.width * scaleFactor ;
1189 glyph.height = glyph.height * scaleFactor;
1190 glyph.advance = glyph.advance * scaleFactor;
1191 glyph.xBearing = glyph.xBearing * scaleFactor;
1192 glyph.yBearing = glyph.yBearing * scaleFactor;
1194 glyph.scaleFactor = scaleFactor;
1199 DALI_LOG_INFO( gLogFilter, Debug::General, "FontClient::Plugin::GetBitmapMetrics. FreeType Bitmap Load_Glyph error %d\n", error );
1206 int error = FT_Load_Glyph( ftFace, glyph.index, FT_LOAD_NO_AUTOHINT );
1208 if( FT_Err_Ok == error )
1210 glyph.width = static_cast< float >( ftFace->glyph->metrics.width ) * FROM_266;
1211 glyph.height = static_cast< float >( ftFace->glyph->metrics.height ) * FROM_266 ;
1214 glyph.xBearing += static_cast< float >( ftFace->glyph->metrics.horiBearingX ) * FROM_266;
1215 glyph.yBearing += static_cast< float >( ftFace->glyph->metrics.horiBearingY ) * FROM_266;
1219 glyph.xBearing += static_cast< float >( ftFace->glyph->metrics.vertBearingX ) * FROM_266;
1220 glyph.yBearing += static_cast< float >( ftFace->glyph->metrics.vertBearingY ) * FROM_266;
1230 case FontDescription::BITMAP_FONT:
1232 BitmapFontCacheItem& bitmapFontCacheItem = mBitmapFontCache[fontIdCacheItem.id];
1234 unsigned int index = 0u;
1235 for( auto& item : bitmapFontCacheItem.font.glyphs )
1237 if( item.utf32 == glyph.index )
1239 Devel::PixelBuffer& pixelBuffer = bitmapFontCacheItem.pixelBuffers[index];
1242 pixelBuffer = LoadImageFromFile( item.url );
1245 glyph.width = static_cast< float >( pixelBuffer.GetWidth() );
1246 glyph.height = static_cast< float >( pixelBuffer.GetHeight() );
1247 glyph.xBearing = 0.f;
1248 glyph.yBearing = glyph.height + item.descender;
1249 glyph.advance = glyph.width;
1250 glyph.scaleFactor = 1.f;
1261 DALI_LOG_INFO(gLogFilter, Debug::General, " Invalid type of font\n");
1267 // Check if it's an embedded image.
1268 if( ( 0u == glyph.fontId ) && ( 0u != glyph.index ) && ( glyph.index <= mEmbeddedItemCache.Count() ) )
1270 const EmbeddedItem& item = mEmbeddedItemCache[glyph.index - 1u];
1272 glyph.width = static_cast<float>( item.width );
1273 glyph.height = static_cast<float>( item.height );
1274 glyph.xBearing = 0.f;
1275 glyph.yBearing = glyph.height;
1276 glyph.advance = glyph.width;
1277 glyph.scaleFactor = 1.f;
1289 bool FontClient::Plugin::GetVectorMetrics( GlyphInfo* array,
1293 #ifdef ENABLE_VECTOR_BASED_TEXT_RENDERING
1294 bool success( true );
1296 for( unsigned int i = 0u; i < size; ++i )
1298 FontId fontId = array[i].fontId;
1300 if( ( fontId > 0u ) &&
1301 ( fontId - 1u ) < mFontIdCache.Count() )
1303 FontFaceCacheItem& font = mFontFaceCache[mFontIdCache[fontId - 1u].id];
1305 if( ! font.mVectorFontId )
1307 font.mVectorFontId = mVectorFontCache->GetFontId( font.mPath );
1310 mVectorFontCache->GetGlyphMetrics( font.mVectorFontId, array[i] );
1312 // Vector metrics are in EMs, convert to pixels
1313 const float scale = ( static_cast<float>( font.mRequestedPointSize ) * FROM_266 ) * static_cast<float>( mDpiVertical ) / POINTS_PER_INCH;
1314 array[i].width *= scale;
1315 array[i].height *= scale;
1316 array[i].xBearing *= scale;
1317 array[i].yBearing *= scale;
1318 array[i].advance *= scale;
1332 void FontClient::Plugin::CreateBitmap( FontId fontId, GlyphIndex glyphIndex, bool isItalicRequired, bool isBoldRequired, Dali::TextAbstraction::FontClient::GlyphBufferData& data, int outlineWidth )
1334 const FontId index = fontId - 1u;
1336 if( ( fontId > 0u ) &&
1337 ( index < mFontIdCache.Count() ) )
1339 const FontIdCacheItem& fontIdCacheItem = mFontIdCache[index];
1341 switch( fontIdCacheItem.type )
1343 case FontDescription::FACE_FONT:
1345 // For the software italics.
1346 bool isShearRequired = false;
1348 const FontFaceCacheItem& fontFaceCacheItem = mFontFaceCache[fontIdCacheItem.id];
1349 FT_Face ftFace = fontFaceCacheItem.mFreeTypeFace;
1353 #ifdef FREETYPE_BITMAP_SUPPORT
1354 // Check to see if this is fixed size bitmap
1355 if( fontFaceCacheItem.mIsFixedSizeBitmap )
1357 error = FT_Load_Glyph( ftFace, glyphIndex, FT_LOAD_COLOR );
1362 error = FT_Load_Glyph( ftFace, glyphIndex, FT_LOAD_NO_AUTOHINT );
1364 if( FT_Err_Ok == error )
1366 if( isBoldRequired && !( ftFace->style_flags & FT_STYLE_FLAG_BOLD ) )
1368 // Does the software bold.
1369 FT_GlyphSlot_Embolden( ftFace->glyph );
1372 if( isItalicRequired && !( ftFace->style_flags & FT_STYLE_FLAG_ITALIC ) )
1374 // Will do the software italic.
1375 isShearRequired = true;
1379 error = FT_Get_Glyph( ftFace->glyph, &glyph );
1381 // Convert to bitmap if necessary
1382 if ( FT_Err_Ok == error )
1384 if( glyph->format != FT_GLYPH_FORMAT_BITMAP )
1386 // Check whether we should create a bitmap for the outline
1387 if( glyph->format == FT_GLYPH_FORMAT_OUTLINE && outlineWidth > 0 )
1391 error = FT_Stroker_New( mFreeTypeLibrary, &stroker );
1393 if( FT_Err_Ok == error )
1395 FT_Stroker_Set( stroker, outlineWidth * 64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0 );
1396 error = FT_Glyph_StrokeBorder( &glyph, stroker, 0, 1 );
1398 if( FT_Err_Ok == error )
1400 FT_Stroker_Done( stroker );
1404 DALI_LOG_ERROR( "FT_Glyph_StrokeBorder Failed with error: %d\n", error );
1409 DALI_LOG_ERROR( "FT_Stroker_New Failed with error: %d\n", error );
1413 error = FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, 0, 1 );
1414 if( FT_Err_Ok == error )
1416 FT_BitmapGlyph bitmapGlyph = reinterpret_cast< FT_BitmapGlyph >( glyph );
1417 ConvertBitmap( data, bitmapGlyph->bitmap, isShearRequired );
1421 DALI_LOG_INFO( gLogFilter, Debug::General, "FontClient::Plugin::CreateBitmap. FT_Get_Glyph Failed with error: %d\n", error );
1426 ConvertBitmap( data, ftFace->glyph->bitmap, isShearRequired );
1429 // Created FT_Glyph object must be released with FT_Done_Glyph
1430 FT_Done_Glyph( glyph );
1435 DALI_LOG_INFO( gLogFilter, Debug::General, "FontClient::Plugin::CreateBitmap. FT_Load_Glyph Failed with error: %d\n", error );
1439 case FontDescription::BITMAP_FONT:
1441 BitmapFontCacheItem& bitmapFontCacheItem = mBitmapFontCache[fontIdCacheItem.id];
1443 unsigned int index = 0u;
1444 for( auto& item : bitmapFontCacheItem.font.glyphs )
1446 if( item.utf32 == glyphIndex )
1448 Devel::PixelBuffer& pixelBuffer = bitmapFontCacheItem.pixelBuffers[index];
1451 pixelBuffer = LoadImageFromFile( item.url );
1454 data.width = pixelBuffer.GetWidth();
1455 data.height = pixelBuffer.GetHeight();
1457 ConvertBitmap( data, data.width, data.height, pixelBuffer.GetBuffer() );
1459 // Sets the pixel format.
1460 data.format = pixelBuffer.GetPixelFormat();
1469 DALI_LOG_INFO(gLogFilter, Debug::General, " Invalid type of font\n");
1475 if( ( 0u != glyphIndex ) && ( glyphIndex <= mEmbeddedItemCache.Count() ) )
1477 // It's an embedded item.
1478 const EmbeddedItem& item = mEmbeddedItemCache[glyphIndex - 1u];
1480 data.width = item.width;
1481 data.height = item.height;
1482 if( 0u != item.pixelBufferId )
1484 Devel::PixelBuffer pixelBuffer = mPixelBufferCache[item.pixelBufferId-1u].pixelBuffer;
1487 ConvertBitmap( data, pixelBuffer.GetWidth(), pixelBuffer.GetHeight(), pixelBuffer.GetBuffer() );
1489 // Sets the pixel format.
1490 data.format = pixelBuffer.GetPixelFormat();
1495 // Creates the output buffer
1496 const unsigned int bufferSize = data.width * data.height * 4u;
1497 data.buffer = new unsigned char[bufferSize]; // @note The caller is responsible for deallocating the bitmap data using delete[].
1499 memset( data.buffer, 0u, bufferSize );
1501 // Just creates a void buffer. Doesn't matter what pixel format is set as is the application code the responsible of filling it.
1507 PixelData FontClient::Plugin::CreateBitmap( FontId fontId, GlyphIndex glyphIndex, int outlineWidth )
1509 TextAbstraction::FontClient::GlyphBufferData data;
1511 CreateBitmap( fontId, glyphIndex, false, false, data, outlineWidth );
1513 return PixelData::New( data.buffer,
1514 data.width * data.height * Pixel::GetBytesPerPixel( data.format ),
1518 PixelData::DELETE_ARRAY );
1521 void FontClient::Plugin::CreateVectorBlob( FontId fontId, GlyphIndex glyphIndex, VectorBlob*& blob, unsigned int& blobLength, unsigned int& nominalWidth, unsigned int& nominalHeight )
1526 #ifdef ENABLE_VECTOR_BASED_TEXT_RENDERING
1527 if( ( fontId > 0u ) &&
1528 ( fontId - 1u < mFontIdCache.Count() ) )
1530 const FontId fontFaceId = mFontIdCache[fontId - 1u].id;
1531 FontFaceCacheItem& font = mFontFaceCache[fontFaceId];
1533 if( ! font.mVectorFontId )
1535 font.mVectorFontId = mVectorFontCache->GetFontId( font.mPath );
1538 mVectorFontCache->GetVectorBlob( font.mVectorFontId, fontFaceId, glyphIndex, blob, blobLength, nominalWidth, nominalHeight );
1543 const GlyphInfo& FontClient::Plugin::GetEllipsisGlyph( PointSize26Dot6 requestedPointSize )
1545 DALI_LOG_INFO( gLogFilter, Debug::General, "-->FontClient::Plugin::GetEllipsisGlyph\n" );
1546 DALI_LOG_INFO( gLogFilter, Debug::General, " requestedPointSize %d.\n", requestedPointSize );
1548 // First look into the cache if there is an ellipsis glyph for the requested point size.
1549 for( const auto& item : mEllipsisCache )
1551 if( item.requestedPointSize != requestedPointSize )
1553 // Use the glyph in the cache.
1555 DALI_LOG_INFO( gLogFilter, Debug::General, " glyph id %d found in the cache.\n", item.glyph.index );
1556 DALI_LOG_INFO( gLogFilter, Debug::General, " font %d.\n", item.glyph.fontId );
1557 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::GetEllipsisGlyph\n" );
1563 // No glyph has been found. Create one.
1564 mEllipsisCache.PushBack( EllipsisItem() );
1565 EllipsisItem& item = *( mEllipsisCache.End() - 1u );
1567 item.requestedPointSize = requestedPointSize;
1569 // Find a font for the ellipsis glyph.
1570 item.glyph.fontId = FindDefaultFont( ELLIPSIS_CHARACTER,
1574 // Set the character index to access the glyph inside the font.
1575 item.glyph.index = FT_Get_Char_Index( mFontFaceCache[mFontIdCache[item.glyph.fontId-1u].id].mFreeTypeFace,
1576 ELLIPSIS_CHARACTER );
1578 GetBitmapMetrics( &item.glyph, 1u, true );
1580 DALI_LOG_INFO( gLogFilter, Debug::General, " glyph id %d found in the cache.\n", item.glyph.index );
1581 DALI_LOG_INFO( gLogFilter, Debug::General, " font %d.\n", item.glyph.fontId );
1582 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::GetEllipsisGlyph\n" );
1587 bool FontClient::Plugin::IsColorGlyph( FontId fontId, GlyphIndex glyphIndex )
1589 FT_Error error = -1;
1591 const FontId index = fontId - 1u;
1593 #ifdef FREETYPE_BITMAP_SUPPORT
1594 if( ( fontId > 0u ) &&
1595 ( index < mFontIdCache.Count() ) )
1597 const FontIdCacheItem& fontIdCacheItem = mFontIdCache[index];
1599 switch( fontIdCacheItem.type )
1601 case FontDescription::FACE_FONT:
1603 const FontFaceCacheItem& item = mFontFaceCache[fontIdCacheItem.id];
1604 FT_Face ftFace = item.mFreeTypeFace;
1606 // Check to see if this is fixed size bitmap
1607 if( item.mHasColorTables )
1609 error = FT_Load_Glyph( ftFace, glyphIndex, FT_LOAD_COLOR );
1613 case FontDescription::BITMAP_FONT:
1615 error = FT_Err_Ok; // Will return true;
1620 DALI_LOG_INFO(gLogFilter, Debug::General, " Invalid type of font\n");
1626 return FT_Err_Ok == error;
1629 FT_FaceRec_* FontClient::Plugin::GetFreetypeFace( FontId fontId )
1631 FT_Face fontFace = nullptr;
1633 const FontId index = fontId - 1u;
1634 if( ( fontId > 0u ) &&
1635 ( index < mFontIdCache.Count() ) )
1637 const FontIdCacheItem& fontIdCacheItem = mFontIdCache[index];
1639 if( FontDescription::FACE_FONT == fontIdCacheItem.type )
1641 fontFace = mFontFaceCache[fontIdCacheItem.id].mFreeTypeFace;
1647 FontDescription::Type FontClient::Plugin::GetFontType( FontId fontId )
1649 const FontId index = fontId - 1u;
1650 if( ( fontId > 0u ) &&
1651 ( index < mFontIdCache.Count() ) )
1653 return mFontIdCache[index].type;
1655 return FontDescription::INVALID;
1658 bool FontClient::Plugin::AddCustomFontDirectory( const FontPath& path )
1660 // NULL as first parameter means the current configuration is used.
1661 return FcConfigAppFontAddDir( NULL, reinterpret_cast<const FcChar8 *>( path.c_str() ) );
1664 GlyphIndex FontClient::Plugin::CreateEmbeddedItem( const TextAbstraction::FontClient::EmbeddedItemDescription& description, Pixel::Format& pixelFormat )
1666 EmbeddedItem embeddedItem;
1668 embeddedItem.pixelBufferId = 0u;
1669 embeddedItem.width = description.width;
1670 embeddedItem.height = description.height;
1672 pixelFormat = Pixel::A8;
1674 if( !description.url.empty() )
1676 // Check if the url is in the cache.
1677 PixelBufferId index = 0u;
1679 for( const auto& cacheItem : mPixelBufferCache )
1682 if( cacheItem.url == description.url )
1684 // The url is in the pixel buffer cache.
1685 // Set the index +1 to the vector.
1686 embeddedItem.pixelBufferId = index;
1691 Devel::PixelBuffer pixelBuffer;
1692 if( 0u == embeddedItem.pixelBufferId )
1694 // The pixel buffer is not in the cache. Create one and cache it.
1696 // Load the image from the url.
1697 pixelBuffer = LoadImageFromFile( description.url );
1699 // Create the cache item.
1700 PixelBufferCacheItem pixelBufferCacheItem;
1701 pixelBufferCacheItem.pixelBuffer = pixelBuffer;
1702 pixelBufferCacheItem.url = description.url;
1704 // Store the cache item in the cache.
1705 mPixelBufferCache.push_back( std::move( pixelBufferCacheItem ) );
1707 // Set the pixel buffer id to the embedded item.
1708 embeddedItem.pixelBufferId = mPixelBufferCache.size();
1712 // Retrieve the pixel buffer from the cache to set the pixel format.
1713 pixelBuffer = mPixelBufferCache[embeddedItem.pixelBufferId-1u].pixelBuffer;
1718 // Set the size of the embedded item if it has not been set.
1719 if( 0u == embeddedItem.width )
1721 embeddedItem.width = static_cast<unsigned int>( pixelBuffer.GetWidth() );
1724 if( 0u == embeddedItem.height )
1726 embeddedItem.height = static_cast<unsigned int>( pixelBuffer.GetHeight() );
1729 // Set the pixel format.
1730 pixelFormat = pixelBuffer.GetPixelFormat();
1734 // Find if the same embeddedItem has already been created.
1735 unsigned int index = 0u;
1736 for( const auto& item : mEmbeddedItemCache )
1739 if( ( item.pixelBufferId == embeddedItem.pixelBufferId ) &&
1740 ( item.width == embeddedItem.width ) &&
1741 ( item.height == embeddedItem.height ) )
1747 // Cache the embedded item.
1748 mEmbeddedItemCache.PushBack( embeddedItem );
1750 return mEmbeddedItemCache.Count();
1753 void FontClient::Plugin::InitSystemFonts()
1755 DALI_LOG_INFO( gLogFilter, Debug::General, "-->FontClient::Plugin::InitSystemFonts\n" );
1757 FcFontSet* fontSet = GetFcFontSet(); // Creates a FcFontSet that needs to be destroyed by calling FcFontSetDestroy.
1761 DALI_LOG_INFO( gLogFilter, Debug::General, " number of system fonts : %d\n", fontSet->nfont );
1763 // Reserve some space to avoid reallocations.
1764 mSystemFonts.reserve( fontSet->nfont );
1766 for( int i = 0u; i < fontSet->nfont; ++i )
1768 FcPattern* fontPattern = fontSet->fonts[i];
1772 // Skip fonts with no path
1773 if( GetFcString( fontPattern, FC_FILE, path ) )
1775 mSystemFonts.push_back( FontDescription() );
1776 FontDescription& fontDescription = mSystemFonts.back();
1778 fontDescription.path = std::move( path );
1783 GetFcString( fontPattern, FC_FAMILY, fontDescription.family );
1784 GetFcInt( fontPattern, FC_WIDTH, width );
1785 GetFcInt( fontPattern, FC_WEIGHT, weight );
1786 GetFcInt( fontPattern, FC_SLANT, slant );
1787 fontDescription.width = IntToWidthType( width );
1788 fontDescription.weight = IntToWeightType( weight );
1789 fontDescription.slant = IntToSlantType( slant );
1791 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " description; family : [%s]\n", fontDescription.family.c_str() );
1792 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " path : [%s]\n", fontDescription.path.c_str() );
1793 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " width : [%s]\n", FontWidth::Name[fontDescription.width] );
1794 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " weight : [%s]\n", FontWeight::Name[fontDescription.weight] );
1795 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " slant : [%s]\n\n", FontSlant::Name[fontDescription.slant] );
1799 // Destroys the font set created.
1800 FcFontSetDestroy( fontSet );
1802 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::InitSystemFonts\n" );
1805 bool FontClient::Plugin::MatchFontDescriptionToPattern( FcPattern* pattern, Dali::TextAbstraction::FontDescription& fontDescription, FcCharSet** characterSet )
1807 DALI_LOG_INFO( gLogFilter, Debug::General, "-->FontClient::Plugin::MatchFontDescriptionToPattern\n" );
1809 FcResult result = FcResultMatch;
1810 FcPattern* match = FcFontMatch( nullptr /* use default configure */, pattern, &result ); // Creates a new font pattern that needs to be destroyed by calling FcPatternDestroy.
1812 const bool matched = nullptr != match;
1813 DALI_LOG_INFO( gLogFilter, Debug::General, " pattern matched : %s\n", ( matched ? "true" : "false" ) );
1820 GetFcString( match, FC_FILE, fontDescription.path );
1821 GetFcString( match, FC_FAMILY, fontDescription.family );
1822 GetFcInt( match, FC_WIDTH, width );
1823 GetFcInt( match, FC_WEIGHT, weight );
1824 GetFcInt( match, FC_SLANT, slant );
1825 fontDescription.width = IntToWidthType( width );
1826 fontDescription.weight = IntToWeightType( weight );
1827 fontDescription.slant = IntToSlantType( slant );
1829 // Retrieve the character set and increase the reference counter.
1830 FcPatternGetCharSet( match, FC_CHARSET, 0u, characterSet );
1831 *characterSet = FcCharSetCopy( *characterSet );
1833 // destroyed the matched pattern
1834 FcPatternDestroy( match );
1836 DALI_LOG_INFO( gLogFilter, Debug::General, " description; family : [%s]\n", fontDescription.family.c_str() );
1837 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " path : [%s]\n", fontDescription.path.c_str() );
1838 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " width : [%s]\n", FontWidth::Name[fontDescription.width] );
1839 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " weight : [%s]\n", FontWeight::Name[fontDescription.weight] );
1840 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " slant : [%s]\n\n", FontSlant::Name[fontDescription.slant] );
1843 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::MatchFontDescriptionToPattern\n" );
1847 FcPattern* FontClient::Plugin::CreateFontFamilyPattern( const FontDescription& fontDescription ) const
1849 // create the cached font family lookup pattern
1850 // a pattern holds a set of names, each name refers to a property of the font
1851 FcPattern* fontFamilyPattern = FcPatternCreate(); // FcPatternCreate creates a new pattern that needs to be destroyed by calling FcPatternDestroy.
1853 if( !fontFamilyPattern )
1858 // add a property to the pattern for the font family
1859 FcPatternAddString( fontFamilyPattern, FC_FAMILY, reinterpret_cast<const FcChar8*>( fontDescription.family.c_str() ) );
1861 // add a property to the pattern for local setting.
1862 const char* locale = setlocale( LC_MESSAGES, NULL );
1865 FcPatternAddString( fontFamilyPattern, FC_LANG, reinterpret_cast<const FcChar8*>( locale ) );
1868 int width = FONT_WIDTH_TYPE_TO_INT[fontDescription.width];
1872 width = DEFAULT_FONT_WIDTH;
1875 int weight = FONT_WEIGHT_TYPE_TO_INT[fontDescription.weight];
1879 weight = DEFAULT_FONT_WEIGHT;
1882 int slant = FONT_SLANT_TYPE_TO_INT[fontDescription.slant];
1886 slant = DEFAULT_FONT_SLANT;
1889 FcPatternAddInteger( fontFamilyPattern, FC_WIDTH, width );
1890 FcPatternAddInteger( fontFamilyPattern, FC_WEIGHT, weight );
1891 FcPatternAddInteger( fontFamilyPattern, FC_SLANT, slant );
1893 // modify the config, with the mFontFamilyPatterm
1894 FcConfigSubstitute( nullptr /* use default configure */, fontFamilyPattern, FcMatchPattern );
1896 // provide default values for unspecified properties in the font pattern
1897 // e.g. patterns without a specified style or weight are set to Medium
1898 FcDefaultSubstitute( fontFamilyPattern );
1900 return fontFamilyPattern;
1903 _FcFontSet* FontClient::Plugin::GetFcFontSet() const
1905 FcFontSet* fontset = nullptr;
1907 // create a new pattern.
1908 // a pattern holds a set of names, each name refers to a property of the font
1909 FcPattern* pattern = FcPatternCreate();
1911 if( nullptr != pattern )
1913 // create an object set used to define which properties are to be returned in the patterns from FcFontList.
1914 FcObjectSet* objectSet = FcObjectSetCreate();
1916 if( nullptr != objectSet )
1918 // build an object set from a list of property names
1919 FcObjectSetAdd( objectSet, FC_FILE );
1920 FcObjectSetAdd( objectSet, FC_FAMILY );
1921 FcObjectSetAdd( objectSet, FC_WIDTH );
1922 FcObjectSetAdd( objectSet, FC_WEIGHT );
1923 FcObjectSetAdd( objectSet, FC_SLANT );
1925 // get a list of fonts
1926 // creates patterns from those fonts containing only the objects in objectSet and returns the set of unique such patterns
1927 fontset = FcFontList( nullptr /* the default configuration is checked to be up to date, and used */, pattern, objectSet ); // Creates a FcFontSet that needs to be destroyed by calling FcFontSetDestroy.
1929 // clear up the object set
1930 FcObjectSetDestroy( objectSet );
1933 // clear up the pattern
1934 FcPatternDestroy( pattern );
1940 bool FontClient::Plugin::GetFcString( const FcPattern* const pattern,
1941 const char* const n,
1942 std::string& string )
1944 FcChar8* file = nullptr;
1945 const FcResult retVal = FcPatternGetString( pattern, n, 0u, &file );
1947 if( FcResultMatch == retVal )
1949 // Have to use reinterpret_cast because FcChar8 is unsigned char*, not a const char*.
1950 string.assign( reinterpret_cast<const char*>( file ) );
1958 bool FontClient::Plugin::GetFcInt( const _FcPattern* const pattern, const char* const n, int& intVal )
1960 const FcResult retVal = FcPatternGetInteger( pattern, n, 0u, &intVal );
1962 if( FcResultMatch == retVal )
1970 FontId FontClient::Plugin::CreateFont( const FontPath& path,
1971 PointSize26Dot6 requestedPointSize,
1972 FaceIndex faceIndex,
1973 bool cacheDescription )
1975 DALI_LOG_INFO( gLogFilter, Debug::General, "-->FontClient::Plugin::CreateFont\n" );
1976 DALI_LOG_INFO( gLogFilter, Debug::General, " path : [%s]\n", path.c_str() );
1977 DALI_LOG_INFO( gLogFilter, Debug::General, " requestedPointSize : %d\n", requestedPointSize );
1981 // Create & cache new font face
1983 int error = FT_New_Face( mFreeTypeLibrary,
1988 if( FT_Err_Ok == error )
1990 // Check if a font is scalable.
1991 const bool isScalable = ( 0 != ( ftFace->face_flags & FT_FACE_FLAG_SCALABLE ) );
1992 const bool hasFixedSizedBitmaps = ( 0 != ( ftFace->face_flags & FT_FACE_FLAG_FIXED_SIZES ) ) && ( 0 != ftFace->num_fixed_sizes );
1993 const bool hasColorTables = ( 0 != ( ftFace->face_flags & FT_FACE_FLAG_COLOR ) );
1994 FontId fontFaceId = 0u;
1996 DALI_LOG_INFO( gLogFilter, Debug::General, " isScalable : [%s]\n", ( isScalable ? "true" : "false" ) );
1997 DALI_LOG_INFO( gLogFilter, Debug::General, " hasFixedSizedBitmaps : [%s]\n", ( hasFixedSizedBitmaps ? "true" : "false" ) );
1998 DALI_LOG_INFO( gLogFilter, Debug::General, " hasColorTables : [%s]\n", ( hasColorTables ? "true" : "false" ) );
2000 // Check to see if the font contains fixed sizes?
2001 if( !isScalable && hasFixedSizedBitmaps )
2003 PointSize26Dot6 actualPointSize = 0u;
2004 int fixedSizeIndex = 0;
2005 for( ; fixedSizeIndex < ftFace->num_fixed_sizes; ++fixedSizeIndex )
2007 const PointSize26Dot6 fixedSize = ftFace->available_sizes[fixedSizeIndex].size;
2008 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " size index : %d, size : %d\n", fixedSizeIndex, fixedSize );
2010 if( fixedSize >= requestedPointSize )
2012 actualPointSize = fixedSize;
2017 if( 0u == actualPointSize )
2019 // The requested point size is bigger than the bigest fixed size.
2020 fixedSizeIndex = ftFace->num_fixed_sizes - 1;
2021 actualPointSize = ftFace->available_sizes[fixedSizeIndex].size;
2024 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " size index : %d, actual size : %d\n", fixedSizeIndex, actualPointSize );
2026 // Tell Freetype to use this size
2027 error = FT_Select_Size( ftFace, fixedSizeIndex );
2028 if ( FT_Err_Ok != error )
2030 DALI_LOG_INFO( gLogFilter, Debug::General, "FreeType Select_Size error: %d\n", error );
2034 const float fixedWidth = static_cast<float>( ftFace->available_sizes[ fixedSizeIndex ].width );
2035 const float fixedHeight = static_cast<float>( ftFace->available_sizes[ fixedSizeIndex ].height );
2037 // Indicate that the font is a fixed sized bitmap
2038 FontMetrics metrics( fixedHeight, // The ascender in pixels.
2040 fixedHeight, // The height in pixels.
2044 // Create the FreeType font face item to cache.
2045 FontFaceCacheItem fontFaceCacheItem( ftFace, path, requestedPointSize, faceIndex, metrics, fixedSizeIndex, fixedWidth, fixedHeight, hasColorTables );
2047 // Set the index to the font's id cache.
2048 fontFaceCacheItem.mFontId = mFontIdCache.Count();
2050 // Create the font id item to cache.
2051 FontIdCacheItem fontIdCacheItem;
2052 fontIdCacheItem.type = FontDescription::FACE_FONT;
2054 // Set the index to the FreeType font face cache.
2055 fontIdCacheItem.id = mFontFaceCache.size();
2056 fontFaceId = fontIdCacheItem.id + 1u;
2059 mFontFaceCache.push_back( fontFaceCacheItem );
2060 mFontIdCache.PushBack( fontIdCacheItem );
2062 // Set the font id to be returned.
2063 id = mFontIdCache.Count();
2068 error = FT_Set_Char_Size( ftFace,
2074 if( FT_Err_Ok == error )
2077 FT_Size_Metrics& ftMetrics = ftFace->size->metrics;
2079 FontMetrics metrics( static_cast< float >( ftMetrics.ascender ) * FROM_266,
2080 static_cast< float >( ftMetrics.descender ) * FROM_266,
2081 static_cast< float >( ftMetrics.height ) * FROM_266,
2082 static_cast< float >( ftFace->underline_position ) * FROM_266,
2083 static_cast< float >( ftFace->underline_thickness ) * FROM_266 );
2085 // Create the FreeType font face item to cache.
2086 FontFaceCacheItem fontFaceCacheItem( ftFace, path, requestedPointSize, faceIndex, metrics );
2088 // Set the index to the font's id cache.
2089 fontFaceCacheItem.mFontId = mFontIdCache.Count();
2091 // Create the font id item to cache.
2092 FontIdCacheItem fontIdCacheItem;
2093 fontIdCacheItem.type = FontDescription::FACE_FONT;
2095 // Set the index to the FreeType font face cache.
2096 fontIdCacheItem.id = mFontFaceCache.size();
2097 fontFaceId = fontIdCacheItem.id + 1u;
2100 mFontFaceCache.push_back( fontFaceCacheItem );
2101 mFontIdCache.PushBack( fontIdCacheItem );
2103 // Set the font id to be returned.
2104 id = mFontIdCache.Count();
2108 DALI_LOG_INFO( gLogFilter, Debug::General, " FreeType Set_Char_Size error: %d for pointSize %d\n", error, requestedPointSize );
2112 if( 0u != fontFaceId )
2114 if( cacheDescription )
2116 CacheFontPath( ftFace, fontFaceId, requestedPointSize, path );
2122 DALI_LOG_INFO( gLogFilter, Debug::General, " FreeType New_Face error: %d for [%s]\n", error, path.c_str() );
2125 DALI_LOG_INFO( gLogFilter, Debug::General, " font id : %d\n", id );
2126 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::CreateFont\n" );
2131 void FontClient::Plugin::ConvertBitmap( TextAbstraction::FontClient::GlyphBufferData& data, unsigned int srcWidth, unsigned int srcHeight, const unsigned char* const srcBuffer )
2133 // Set the input dimensions.
2134 const ImageDimensions inputDimensions( srcWidth, srcHeight );
2136 // Set the output dimensions.
2137 // If the output dimension is not given, the input dimension is set
2138 // and won't be downscaling.
2139 data.width = ( data.width == 0 ) ? srcWidth : data.width;
2140 data.height = ( data.height == 0 ) ? srcHeight : data.height;
2141 const ImageDimensions desiredDimensions( data.width, data.height );
2143 // Creates the output buffer
2144 const unsigned int bufferSize = data.width * data.height * 4u;
2145 data.buffer = new unsigned char[bufferSize]; // @note The caller is responsible for deallocating the bitmap data using delete[].
2147 if( inputDimensions == desiredDimensions )
2149 // There isn't downscaling.
2150 memcpy( data.buffer, srcBuffer, bufferSize );
2154 Dali::Internal::Platform::LanczosSample4BPP( srcBuffer,
2157 desiredDimensions );
2161 void FontClient::Plugin::ConvertBitmap( TextAbstraction::FontClient::GlyphBufferData& data, FT_Bitmap srcBitmap, bool isShearRequired )
2163 if( srcBitmap.width*srcBitmap.rows > 0 )
2165 switch( srcBitmap.pixel_mode )
2167 case FT_PIXEL_MODE_GRAY:
2169 if( srcBitmap.pitch == static_cast<int>( srcBitmap.width ) )
2171 uint8_t* pixelsIn = srcBitmap.buffer;
2172 unsigned int width = srcBitmap.width;
2173 unsigned height = srcBitmap.rows;
2175 std::unique_ptr<uint8_t, void(*)(void*)> pixelsOutPtr( nullptr, free );
2177 if( isShearRequired )
2180 * Glyphs' bitmaps with no slant retrieved from FreeType:
2190 * Expected glyphs' bitmaps with italic slant:
2191 * ____________ ______
2198 * ------------ ------
2200 * Glyphs' bitmaps with software italic slant retrieved from FreeType:
2210 * This difference in some bitmaps' width causes an overlap of some glyphs. This is the reason why a shear operation is done here instead of relying on the experimental FT_GlyphSlot_Oblique() implementation.
2212 unsigned int widthOut = 0u;
2213 unsigned int heightOut = 0u;
2214 uint8_t* pixelsOut = nullptr;
2216 Dali::Internal::Platform::HorizontalShear( pixelsIn,
2220 -TextAbstraction::FontClient::DEFAULT_ITALIC_ANGLE,
2227 pixelsIn = pixelsOut;
2228 pixelsOutPtr.reset( pixelsOut );
2231 const unsigned int bufferSize = width * height;
2232 data.buffer = new unsigned char[bufferSize]; // @note The caller is responsible for deallocating the bitmap data using delete[].
2234 data.height = height;
2235 data.format = Pixel::L8; // Sets the pixel format.
2236 memcpy( data.buffer, pixelsIn, bufferSize );
2241 #ifdef FREETYPE_BITMAP_SUPPORT
2242 case FT_PIXEL_MODE_BGRA:
2244 if( srcBitmap.pitch == static_cast<int>( srcBitmap.width << 2u ) )
2246 ConvertBitmap( data, srcBitmap.width, srcBitmap.rows, srcBitmap.buffer );
2248 // Sets the pixel format.
2249 data.format = Pixel::BGRA8888;
2256 DALI_LOG_INFO( gLogFilter, Debug::General, "FontClient::Plugin::ConvertBitmap. FontClient Unable to create Bitmap of this PixelType\n" );
2263 bool FontClient::Plugin::FindFont( const FontPath& path,
2264 PointSize26Dot6 requestedPointSize,
2265 FaceIndex faceIndex,
2266 FontId& fontId ) const
2268 DALI_LOG_INFO( gLogFilter, Debug::General, "-->FontClient::Plugin::FindFont\n" );
2269 DALI_LOG_INFO( gLogFilter, Debug::General, " path : [%s]\n", path.c_str() );
2270 DALI_LOG_INFO( gLogFilter, Debug::General, " requestedPointSize : %d\n", requestedPointSize );
2271 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of fonts in the cache : %d\n", mFontFaceCache.size() );
2274 for( const auto& cacheItem : mFontFaceCache )
2276 if( cacheItem.mRequestedPointSize == requestedPointSize &&
2277 cacheItem.mFaceIndex == faceIndex &&
2278 cacheItem.mPath == path )
2280 fontId = cacheItem.mFontId + 1u;
2282 DALI_LOG_INFO( gLogFilter, Debug::General, " font found, id : %d\n", fontId );
2283 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::FindFont\n" );
2289 DALI_LOG_INFO( gLogFilter, Debug::General, " font not found\n" );
2290 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::FindFont\n" );
2295 bool FontClient::Plugin::FindValidatedFont( const FontDescription& fontDescription,
2296 FontDescriptionId& validatedFontId )
2298 DALI_LOG_INFO( gLogFilter, Debug::General, "-->FontClient::Plugin::FindValidatedFont\n" );
2299 DALI_LOG_INFO( gLogFilter, Debug::General, " description; family : [%s]\n", fontDescription.family.c_str() );
2300 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " path : [%s]\n", fontDescription.path.c_str() );
2301 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " width : [%s]\n", FontWidth::Name[fontDescription.width] );
2302 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " weight : [%s]\n", FontWeight::Name[fontDescription.weight] );
2303 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " slant : [%s]\n\n", FontSlant::Name[fontDescription.slant] );
2304 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of validated fonts in the cache : %d\n", mValidatedFontCache.size() );
2306 validatedFontId = 0u;
2308 for( const auto& item : mValidatedFontCache )
2310 if( !fontDescription.family.empty() &&
2311 ( fontDescription.family == item.fontDescription.family ) &&
2312 ( fontDescription.width == item.fontDescription.width ) &&
2313 ( fontDescription.weight == item.fontDescription.weight ) &&
2314 ( fontDescription.slant == item.fontDescription.slant ) )
2316 validatedFontId = item.index;
2318 DALI_LOG_INFO( gLogFilter, Debug::General, " validated font found, id : %d\n", validatedFontId );
2319 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::FindValidatedFont\n" );
2324 DALI_LOG_INFO( gLogFilter, Debug::General, " validated font not found\n" );
2325 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::FindValidatedFont\n" );
2329 bool FontClient::Plugin::FindFallbackFontList( const FontDescription& fontDescription,
2330 FontList*& fontList,
2331 CharacterSetList*& characterSetList )
2333 DALI_LOG_INFO( gLogFilter, Debug::General, "-->FontClient::Plugin::FindFallbackFontList\n" );
2334 DALI_LOG_INFO( gLogFilter, Debug::General, " description; family : [%s]\n", fontDescription.family.c_str() );
2335 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " path : [%s]\n", fontDescription.path.c_str() );
2336 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " width : [%s]\n", FontWidth::Name[fontDescription.width] );
2337 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " weight : [%s]\n", FontWeight::Name[fontDescription.weight] );
2338 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " slant : [%s]\n\n", FontSlant::Name[fontDescription.slant] );
2339 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of fallback font lists in the cache : %d\n", mFallbackCache.size() );
2343 for( const auto& item : mFallbackCache )
2345 if( !fontDescription.family.empty() &&
2346 ( fontDescription.family == item.fontDescription.family ) &&
2347 ( fontDescription.width == item.fontDescription.width ) &&
2348 ( fontDescription.weight == item.fontDescription.weight ) &&
2349 ( fontDescription.slant == item.fontDescription.slant ) )
2351 fontList = item.fallbackFonts;
2352 characterSetList = item.characterSets;
2354 DALI_LOG_INFO( gLogFilter, Debug::General, " fallback font list found.\n" );
2355 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::FindFallbackFontList\n" );
2360 DALI_LOG_INFO( gLogFilter, Debug::General, " fallback font list not found.\n" );
2361 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::FindFallbackFontList\n" );
2365 bool FontClient::Plugin::FindFont( FontDescriptionId validatedFontId,
2366 PointSize26Dot6 requestedPointSize,
2369 DALI_LOG_INFO( gLogFilter, Debug::General, "-->FontClient::Plugin::FindFont\n" );
2370 DALI_LOG_INFO( gLogFilter, Debug::General, " validatedFontId : %d\n", validatedFontId );
2371 DALI_LOG_INFO( gLogFilter, Debug::General, " requestedPointSize : %d\n", requestedPointSize );
2375 for( const auto& item : mFontDescriptionSizeCache )
2377 if( ( validatedFontId == item.validatedFontId ) &&
2378 ( requestedPointSize == item.requestedPointSize ) )
2380 fontId = item.fontId;
2382 DALI_LOG_INFO( gLogFilter, Debug::General, " font found, id : %d\n", fontId );
2383 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::FindFont\n" );
2388 DALI_LOG_INFO( gLogFilter, Debug::General, " font not found.\n" );
2389 DALI_LOG_INFO( gLogFilter, Debug::General, "<--FontClient::Plugin::FindFont\n" );
2393 bool FontClient::Plugin::FindBitmapFont( const FontFamily& bitmapFont, FontId& fontId ) const
2397 for( const auto& item : mBitmapFontCache )
2399 if( bitmapFont == item.font.name )
2401 fontId = item.id + 1u;
2409 bool FontClient::Plugin::IsScalable( const FontPath& path )
2411 bool isScalable = false;
2414 int error = FT_New_Face( mFreeTypeLibrary,
2418 if( FT_Err_Ok != error )
2420 DALI_LOG_INFO( gLogFilter, Debug::General, "FontClient::Plugin::IsScalable. FreeType Cannot check font: %s\n", path.c_str() );
2424 isScalable = ftFace->face_flags & FT_FACE_FLAG_SCALABLE;
2430 bool FontClient::Plugin::IsScalable( const FontDescription& fontDescription )
2432 // Create a font pattern.
2433 FcPattern* fontFamilyPattern = CreateFontFamilyPattern( fontDescription ); // Creates a font pattern that needs to be destroyed by calling FcPatternDestroy.
2435 FcResult result = FcResultMatch;
2437 // match the pattern
2438 FcPattern* match = FcFontMatch( nullptr /* use default configure */, fontFamilyPattern, &result ); // Creates a font pattern that needs to be destroyed by calling FcPatternDestroy.
2439 bool isScalable = false;
2443 // Get the path to the font file name.
2445 GetFcString( match, FC_FILE, path );
2446 isScalable = IsScalable( path );
2450 DALI_LOG_INFO( gLogFilter, Debug::General, "FontClient::Plugin::IsScalable. FreeType Cannot check font: [%s]\n", fontDescription.family.c_str() );
2453 // Destroys the created patterns.
2454 FcPatternDestroy( match );
2455 FcPatternDestroy( fontFamilyPattern );
2460 void FontClient::Plugin::GetFixedSizes( const FontPath& path, Vector< PointSize26Dot6 >& sizes )
2462 // Empty the caller container
2466 int error = FT_New_Face( mFreeTypeLibrary,
2470 if( FT_Err_Ok != error )
2472 DALI_LOG_INFO( gLogFilter, Debug::General, "FontClient::Plugin::GetFixedSizes. FreeType Cannot check font path : [%s]\n", path.c_str() );
2475 // Fetch the number of fixed sizes available
2476 if ( ftFace->num_fixed_sizes && ftFace->available_sizes )
2478 for ( int i = 0; i < ftFace->num_fixed_sizes; ++i )
2480 sizes.PushBack( ftFace->available_sizes[ i ].size );
2485 void FontClient::Plugin::GetFixedSizes( const FontDescription& fontDescription,
2486 Vector< PointSize26Dot6 >& sizes )
2488 // Create a font pattern.
2489 FcPattern* fontFamilyPattern = CreateFontFamilyPattern( fontDescription ); // Creates a font pattern that needs to be destroyed by calling FcPatternDestroy.
2491 FcResult result = FcResultMatch;
2493 // match the pattern
2494 FcPattern* match = FcFontMatch( nullptr /* use default configure */, fontFamilyPattern, &result ); // Creates a font pattern that needs to be destroyed by calling FcPatternDestroy.
2498 // Get the path to the font file name.
2500 GetFcString( match, FC_FILE, path );
2501 GetFixedSizes( path, sizes );
2505 DALI_LOG_INFO( gLogFilter, Debug::General, "FontClient::Plugin::GetFixedSizes. FreeType Cannot check font: [%s]\n", fontDescription.family.c_str() );
2508 // Destroys the created patterns.
2509 FcPatternDestroy( match );
2510 FcPatternDestroy( fontFamilyPattern );
2513 bool FontClient::Plugin::HasItalicStyle( FontId fontId ) const
2515 bool hasItalicStyle = false;
2517 const FontId index = fontId - 1u;
2519 if( ( fontId > 0 ) &&
2520 ( index < mFontIdCache.Count() ) )
2522 const FontIdCacheItem& fontIdCacheItem = mFontIdCache[index];
2524 if( FontDescription::FACE_FONT == fontIdCacheItem.type )
2526 const FontFaceCacheItem& font = mFontFaceCache[fontIdCacheItem.id];
2528 hasItalicStyle = 0u != ( font.mFreeTypeFace->style_flags & FT_STYLE_FLAG_ITALIC );
2533 DALI_LOG_INFO( gLogFilter, Debug::General, "FontClient::Plugin::GetFontMetrics. Invalid font id : %d\n", fontId );
2536 return hasItalicStyle;
2539 void FontClient::Plugin::CacheFontPath( FT_Face ftFace, FontId id, PointSize26Dot6 requestedPointSize, const FontPath& path )
2541 FontDescription description;
2542 description.path = path;
2543 description.family = std::move( FontFamily( ftFace->family_name ) );
2544 description.weight = FontWeight::NONE;
2545 description.width = FontWidth::NONE;
2546 description.slant = FontSlant::NONE;
2548 // Note FreeType doesn't give too much info to build a proper font style.
2549 if( ftFace->style_flags & FT_STYLE_FLAG_ITALIC )
2551 description.slant = FontSlant::ITALIC;
2553 if( ftFace->style_flags & FT_STYLE_FLAG_BOLD )
2555 description.weight = FontWeight::BOLD;
2558 FontDescriptionId validatedFontId = 0u;
2559 if( !FindValidatedFont( description,
2562 // Set the index to the vector of paths to font file names.
2563 validatedFontId = mFontDescriptionCache.size();
2565 FcPattern* pattern = CreateFontFamilyPattern( description ); // Creates a new pattern that needs to be destroyed by calling FcPatternDestroy.
2567 FcResult result = FcResultMatch;
2568 FcPattern* match = FcFontMatch( nullptr, pattern, &result ); // FcFontMatch creates a new pattern that needs to be destroyed by calling FcPatternDestroy.
2570 FcCharSet* characterSet = nullptr;
2571 FcPatternGetCharSet( match, FC_CHARSET, 0u, &characterSet );
2573 const FontId fontFaceId = id - 1u;
2574 mFontFaceCache[fontFaceId].mCharacterSet = FcCharSetCopy( characterSet ); // Increases the reference counter.
2576 // Destroys the created patterns.
2577 FcPatternDestroy( match );
2578 FcPatternDestroy( pattern );
2580 // Add the path to the cache.
2581 description.type = FontDescription::FACE_FONT;
2582 mFontDescriptionCache.push_back( description );
2584 // Increase the reference counter and add the character set to the cache.
2585 mCharacterSetCache.PushBack( FcCharSetCopy( characterSet ) );
2587 // Cache the index and the font's description.
2588 mValidatedFontCache.push_back( std::move( FontDescriptionCacheItem( std::move( description ),
2589 validatedFontId) ) );
2591 // Cache the pair 'validatedFontId, requestedPointSize' to improve the following queries.
2592 mFontDescriptionSizeCache.push_back( FontDescriptionSizeCacheItem( validatedFontId,
2598 FcCharSet* FontClient::Plugin::CreateCharacterSetFromDescription( const FontDescription& description )
2600 FcCharSet* characterSet = nullptr;
2602 FcPattern* pattern = CreateFontFamilyPattern( description ); // Creates a new pattern that needs to be destroyed by calling FcPatternDestroy.
2604 if( nullptr != pattern )
2606 FcResult result = FcResultMatch;
2607 FcPattern* match = FcFontMatch( nullptr, pattern, &result ); // FcFontMatch creates a new pattern that needs to be destroyed by calling FcPatternDestroy.
2609 FcPatternGetCharSet( match, FC_CHARSET, 0u, &characterSet );
2611 // Destroys the created patterns.
2612 FcPatternDestroy( match );
2613 FcPatternDestroy( pattern );
2616 return characterSet;
2619 void FontClient::Plugin::ClearFallbackCache( std::vector<FallbackCacheItem>& fallbackCache )
2621 for( auto& item : fallbackCache )
2623 if( nullptr != item.fallbackFonts )
2625 delete item.fallbackFonts;
2628 if( nullptr != item.characterSets )
2630 // Free the resources allocated by the FcCharSet objects in the 'characterSets' vector.
2631 DestroyCharacterSets( *item.characterSets );
2632 delete item.characterSets;
2637 void FontClient::Plugin::ClearCharacterSetFromFontFaceCache()
2639 for( auto& item : mFontFaceCache )
2641 FcCharSetDestroy( item.mCharacterSet );
2642 item.mCharacterSet = nullptr;
2646 } // namespace Internal
2648 } // namespace TextAbstraction