mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-02 11:10:31 +03:00
Fix CoreText system font family and face resolution
Require the requested family when matching CoreText descriptors to prevent substitutions such as Verdana resolving to Helvetica. Validate the matched descriptor family and derive TTC face indices by matching CoreText PostScript names against the font's FreeType faces instead of using the descriptor array position. Add macOS regression coverage for resolving and loading Verdana styles.
This commit is contained in:
@@ -137,6 +137,35 @@ struct FreeTypeState {
|
|||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool findFaceIndex( const std::string& path, const std::string& postScriptName,
|
||||||
|
EE::Uint32& faceIndex ) {
|
||||||
|
Lock lock( mutex );
|
||||||
|
FT_Face face{ nullptr };
|
||||||
|
if ( !library || FT_New_Face( library, path.c_str(), -1, &face ) != 0 )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const FT_Long numFaces = face->num_faces;
|
||||||
|
FT_Done_Face( face );
|
||||||
|
if ( numFaces == 1 ) {
|
||||||
|
faceIndex = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ( postScriptName.empty() )
|
||||||
|
return false;
|
||||||
|
for ( FT_Long i = 0; i < numFaces; ++i ) {
|
||||||
|
if ( FT_New_Face( library, path.c_str(), i, &face ) != 0 )
|
||||||
|
continue;
|
||||||
|
const char* facePostScriptName = FT_Get_Postscript_Name( face );
|
||||||
|
const bool matches = facePostScriptName && postScriptName == facePostScriptName;
|
||||||
|
FT_Done_Face( face );
|
||||||
|
if ( matches ) {
|
||||||
|
faceIndex = static_cast<EE::Uint32>( i );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void clearProbeCache() {
|
void clearProbeCache() {
|
||||||
Lock lock( mutex );
|
Lock lock( mutex );
|
||||||
probeCache.clear();
|
probeCache.clear();
|
||||||
@@ -777,6 +806,11 @@ void SystemFontResolver::populateFontList() const {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
CFIndex count = CFArrayGetCount( descriptors );
|
CFIndex count = CFArrayGetCount( descriptors );
|
||||||
|
std::shared_ptr<FreeTypeState> ftState = getFTState();
|
||||||
|
if ( !ftState ) {
|
||||||
|
CFRelease( descriptors );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for ( CFIndex i = 0; i < count; ++i ) {
|
for ( CFIndex i = 0; i < count; ++i ) {
|
||||||
CFStringRef familyNameRef = (CFStringRef)CFArrayGetValueAtIndex( descriptors, i );
|
CFStringRef familyNameRef = (CFStringRef)CFArrayGetValueAtIndex( descriptors, i );
|
||||||
@@ -792,8 +826,12 @@ void SystemFontResolver::populateFontList() const {
|
|||||||
CTFontDescriptorRef familyDesc = CTFontDescriptorCreateWithAttributes( queryDict );
|
CTFontDescriptorRef familyDesc = CTFontDescriptorCreateWithAttributes( queryDict );
|
||||||
CFRelease( queryDict );
|
CFRelease( queryDict );
|
||||||
|
|
||||||
CFSetRef mandatoryAttrs = CFSetCreate(
|
// mandatoryAttributes describes which query attributes must match; it is not a list of
|
||||||
kCFAllocatorDefault, (const void**)&kCTFontURLAttribute, 1, &kCFTypeSetCallBacks );
|
// attributes to return. Requiring the URL (which is absent from the query) lets CoreText
|
||||||
|
// substitute Helvetica for families such as Verdana.
|
||||||
|
CFSetRef mandatoryAttrs =
|
||||||
|
CFSetCreate( kCFAllocatorDefault, (const void**)&kCTFontFamilyNameAttribute, 1,
|
||||||
|
&kCFTypeSetCallBacks );
|
||||||
|
|
||||||
CFArrayRef matchingDescs =
|
CFArrayRef matchingDescs =
|
||||||
CTFontDescriptorCreateMatchingFontDescriptors( familyDesc, mandatoryAttrs );
|
CTFontDescriptorCreateMatchingFontDescriptors( familyDesc, mandatoryAttrs );
|
||||||
@@ -822,6 +860,20 @@ void SystemFontResolver::populateFontList() const {
|
|||||||
if ( fontPath.empty() )
|
if ( fontPath.empty() )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
CFStringRef postScriptNameRef =
|
||||||
|
(CFStringRef)CTFontDescriptorCopyAttribute( desc, kCTFontNameAttribute );
|
||||||
|
std::string postScriptName = cfStringToStd( postScriptNameRef );
|
||||||
|
if ( postScriptNameRef )
|
||||||
|
CFRelease( postScriptNameRef );
|
||||||
|
|
||||||
|
CFStringRef matchedFamilyRef =
|
||||||
|
(CFStringRef)CTFontDescriptorCopyAttribute( desc, kCTFontFamilyNameAttribute );
|
||||||
|
std::string matchedFamily = cfStringToStd( matchedFamilyRef );
|
||||||
|
if ( matchedFamilyRef )
|
||||||
|
CFRelease( matchedFamilyRef );
|
||||||
|
if ( normalizeFamily( matchedFamily ) != normalizeFamily( familyName ) )
|
||||||
|
continue;
|
||||||
|
|
||||||
CFDictionaryRef traits =
|
CFDictionaryRef traits =
|
||||||
(CFDictionaryRef)CTFontDescriptorCopyAttribute( desc, kCTFontTraitsAttribute );
|
(CFDictionaryRef)CTFontDescriptorCopyAttribute( desc, kCTFontTraitsAttribute );
|
||||||
CGFloat weightVal = 0.0;
|
CGFloat weightVal = 0.0;
|
||||||
@@ -857,9 +909,12 @@ void SystemFontResolver::populateFontList() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
FontDesc fontDesc;
|
FontDesc fontDesc;
|
||||||
fontDesc.family = familyName;
|
fontDesc.family = matchedFamily;
|
||||||
fontDesc.path = fontPath;
|
fontDesc.path = fontPath;
|
||||||
fontDesc.faceIndex = static_cast<Uint32>( j );
|
// j is only the descriptor's position in matchingDescs and is unrelated to the face's
|
||||||
|
// index inside a TTC. Match CoreText's PostScript name against the actual file faces.
|
||||||
|
if ( !ftState->findFaceIndex( fontPath, postScriptName, fontDesc.faceIndex ) )
|
||||||
|
continue;
|
||||||
fontDesc.weight = ctWeightToFontWeight( weightVal );
|
fontDesc.weight = ctWeightToFontWeight( weightVal );
|
||||||
fontDesc.stretch = ctWidthToFontStretch( widthVal );
|
fontDesc.stretch = ctWidthToFontStretch( widthVal );
|
||||||
fontDesc.italic = ( slantVal > 0.0 );
|
fontDesc.italic = ( slantVal > 0.0 );
|
||||||
|
|||||||
@@ -128,6 +128,24 @@ UTEST( SystemFontResolver, findVerdana ) {
|
|||||||
query.weight = FontWeight::Normal;
|
query.weight = FontWeight::Normal;
|
||||||
query.italic = false;
|
query.italic = false;
|
||||||
FontDesc desc = resolver->resolve( query );
|
FontDesc desc = resolver->resolve( query );
|
||||||
|
#if EE_PLATFORM == EE_PLATFORM_MACOS
|
||||||
|
auto verdanaFaces = resolver->enumerateFamily( "Verdana" );
|
||||||
|
if ( !verdanaFaces.empty() ) {
|
||||||
|
EXPECT_FALSE( desc.path.empty() );
|
||||||
|
EXPECT_STDSTREQ( "Verdana", desc.family );
|
||||||
|
EXPECT_TRUE_MSG(
|
||||||
|
desc.path.find( "Verdana" ) != std::string::npos,
|
||||||
|
( "Verdana must not resolve through a substituted CoreText family: " + desc.path )
|
||||||
|
.c_str() );
|
||||||
|
for ( const FontDesc& face : verdanaFaces ) {
|
||||||
|
FontTrueTypePtr font = defaultResourceScope().getFontService().loadSystemFont( face );
|
||||||
|
EXPECT_TRUE_MSG(
|
||||||
|
font && font->loaded(),
|
||||||
|
( "CoreText face index must load the selected Verdana face: " + face.getFileKey() )
|
||||||
|
.c_str() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#if EE_PLATFORM == EE_PLATFORM_LINUX || EE_PLATFORM == EE_PLATFORM_BSD || \
|
#if EE_PLATFORM == EE_PLATFORM_LINUX || EE_PLATFORM == EE_PLATFORM_BSD || \
|
||||||
EE_PLATFORM == EE_PLATFORM_WIN || EE_PLATFORM == EE_PLATFORM_MACOS
|
EE_PLATFORM == EE_PLATFORM_WIN || EE_PLATFORM == EE_PLATFORM_MACOS
|
||||||
if ( resolver->enumerate().size() > 0 ) {
|
if ( resolver->enumerate().size() > 0 ) {
|
||||||
|
|||||||
Reference in New Issue
Block a user