4
votes

I’m using the -[NSFontDescriptor matchingFontDescriptorsWithMandatoryKeys:] method in order to find if a font is already installed in the system but I get very strange results.

Here is a small program to test if a given font matches an installed font:

#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>

void logFontDescriptor(NSString *message, NSFontDescriptor *fontDescriptor, NSArray *attributeKeys)
{
    printf("%s %s\n", [message UTF8String], [fontDescriptor.postscriptName UTF8String]);
    for (NSString *attributeKey in attributeKeys)
    {
        printf("    %30s -> %s\n", [[attributeKey description] UTF8String], [[[fontDescriptor objectForKey:attributeKey] description] UTF8String]);
    }
    printf("\n");
}

int main(int argc, const char * argv[])
{
    @autoreleasepool
    {
        if (argc != 2)
            return EXIT_FAILURE;

        NSArray *mandatoryKeys = @[ NSFontNameAttribute, NSFontFamilyAttribute, NSFontFaceAttribute ];

        NSURL *fontURL = [NSURL fileURLWithPath:@(argv[1])];
        NSArray *fontDescriptors = CFBridgingRelease(CTFontManagerCreateFontDescriptorsFromURL((__bridge CFURLRef)fontURL));
        for (NSFontDescriptor *fontDescriptor in fontDescriptors)
        {
            logFontDescriptor(@"***", fontDescriptor, mandatoryKeys);

            NSArray *matchingFontDescriptors = [fontDescriptor matchingFontDescriptorsWithMandatoryKeys:[NSSet setWithArray:mandatoryKeys]];
            for (NSFontDescriptor *matchingFontDescriptor in matchingFontDescriptors)
            {
                logFontDescriptor(@"  MATCHING", matchingFontDescriptor, mandatoryKeys);
            }
        }
    }
    return EXIT_SUCCESS;
}

In order to reproduce the strange behavior, download the Ubuntu Font Family and copy only the Ubuntu-B.ttf (Ubuntu Bold) file into your ~/Library/Fonts directory. Then run this test program with Ubuntu-R.ttf (Ubuntu Regular):

./font_descriptor_matching ~/Downloads/ubuntu-font-family-0.80/Ubuntu-R.ttf

Now, you have the Ubuntu Bold font installed on your system and you are asking what font descriptors are matching Ubuntu Regular and here is the result (OS X 10.9.1):

*** Ubuntu
               NSFontNameAttribute -> Ubuntu
             NSFontFamilyAttribute -> Ubuntu
               NSFontFaceAttribute -> Regular

  MATCHING Ubuntu-Bold
               NSFontNameAttribute -> Ubuntu-Bold
             NSFontFamilyAttribute -> Ubuntu
               NSFontFaceAttribute -> Bold

Asking Ubuntu Regular what font matches its font name (NSFontNameAttribute), font family (NSFontFamilyAttribute) and font face (NSFontFaceAttribute) yields that the Ubuntu Bold font matches. As you can see, the only attribute that matches is the font family. Both the font name and font face are different yet the matchingFontDescriptorsWithMandatoryKeys: method says that these fonts match.

Am I misunderstanding what the matchingFontDescriptorsWithMandatoryKeys: method does or is it a bug?

Additional Information:

  • If both the Ubuntu Bold and the Ubuntu Regular fonts are installed then the matching works as expected.

  • This issue does not exist with all fonts.

1
I played with this a bit, it kind of looks like you’ve found a bug, but it’s an odd bug—I mean, how often are you matching against the traits from an uninstalled font? I'm assuming that if Ubuntu-R.ttf is installed too, it works as expected? Or is that not true?Wil Shipley
My app installs fonts so I want to make sure not to install a duplicate font. That’s why I’m matching against an uninstalled font. I updated the question with additional information and indeed if both fonts are installed, matching works as expected.0xced
Sure sounds like you found a bug. Report a Radar!Wil Shipley

1 Answers

1
votes

In order to workaround this odd behavior, I used this method which suppresses non matching font descriptors by explicitly testing all mandatory keys.

static NSArray * ReallyMatchingFontDescriptorsWithMandatoryKeys(NSFontDescriptor *fontDescriptor, NSSet *mandatoryKeys)
{
    NSArray *matchingFontDescriptors = [fontDescriptor matchingFontDescriptorsWithMandatoryKeys:mandatoryKeys];
    NSMutableArray *reallyMatchingFontDescriptors = [matchingFontDescriptors mutableCopy];
    for (NSFontDescriptor *matchingFontDescriptor in matchingFontDescriptors)
    {
        BOOL reallyMatching = YES;
        for (NSString *mandatoryKey in mandatoryKeys)
        {
            if (![[fontDescriptor objectForKey:mandatoryKey] isEqual:[matchingFontDescriptor objectForKey:mandatoryKey]])
                reallyMatching = NO;
        }
        if (!reallyMatching)
            [reallyMatchingFontDescriptors removeObjectIdenticalTo:matchingFontDescriptor];
    }
    return [reallyMatchingFontDescriptors copy];
}