mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-01 18:50:16 +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;
|
||||
}
|
||||
|
||||
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() {
|
||||
Lock lock( mutex );
|
||||
probeCache.clear();
|
||||
@@ -777,6 +806,11 @@ void SystemFontResolver::populateFontList() const {
|
||||
return;
|
||||
|
||||
CFIndex count = CFArrayGetCount( descriptors );
|
||||
std::shared_ptr<FreeTypeState> ftState = getFTState();
|
||||
if ( !ftState ) {
|
||||
CFRelease( descriptors );
|
||||
return;
|
||||
}
|
||||
|
||||
for ( CFIndex i = 0; i < count; ++i ) {
|
||||
CFStringRef familyNameRef = (CFStringRef)CFArrayGetValueAtIndex( descriptors, i );
|
||||
@@ -792,8 +826,12 @@ void SystemFontResolver::populateFontList() const {
|
||||
CTFontDescriptorRef familyDesc = CTFontDescriptorCreateWithAttributes( queryDict );
|
||||
CFRelease( queryDict );
|
||||
|
||||
CFSetRef mandatoryAttrs = CFSetCreate(
|
||||
kCFAllocatorDefault, (const void**)&kCTFontURLAttribute, 1, &kCFTypeSetCallBacks );
|
||||
// mandatoryAttributes describes which query attributes must match; it is not a list of
|
||||
// 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 =
|
||||
CTFontDescriptorCreateMatchingFontDescriptors( familyDesc, mandatoryAttrs );
|
||||
@@ -822,6 +860,20 @@ void SystemFontResolver::populateFontList() const {
|
||||
if ( fontPath.empty() )
|
||||
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)CTFontDescriptorCopyAttribute( desc, kCTFontTraitsAttribute );
|
||||
CGFloat weightVal = 0.0;
|
||||
@@ -857,9 +909,12 @@ void SystemFontResolver::populateFontList() const {
|
||||
}
|
||||
|
||||
FontDesc fontDesc;
|
||||
fontDesc.family = familyName;
|
||||
fontDesc.family = matchedFamily;
|
||||
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.stretch = ctWidthToFontStretch( widthVal );
|
||||
fontDesc.italic = ( slantVal > 0.0 );
|
||||
|
||||
@@ -128,6 +128,24 @@ UTEST( SystemFontResolver, findVerdana ) {
|
||||
query.weight = FontWeight::Normal;
|
||||
query.italic = false;
|
||||
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 || \
|
||||
EE_PLATFORM == EE_PLATFORM_WIN || EE_PLATFORM == EE_PLATFORM_MACOS
|
||||
if ( resolver->enumerate().size() > 0 ) {
|
||||
|
||||
Reference in New Issue
Block a user