-
Notifications
You must be signed in to change notification settings - Fork 808
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handle missing font files more gracefully #1486
Changes from all commits
04cd65c
ee98009
53503a9
01f91d0
583f639
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -408,9 +408,8 @@ HRESULT _DWriteCreateFontFaceWithName(CFStringRef name, IDWriteFontFace** outFon | |
// Eg: Bold, Condensed, Light, Italic | ||
std::shared_ptr<_DWriteFontProperties> properties = _DWriteGetFontPropertiesFromName(name); | ||
|
||
// TODO: #1250: Need to be able to load fonts from the app's bundle | ||
// Need to be able to load fonts from the app's bundle | ||
// For now return a default font to avoid crashes in case of missing fonts | ||
// When #1250 is completed, remove this | ||
if (!properties->familyName.get()) { | ||
name = CFSTR("Segoe UI"); | ||
properties = _DWriteGetFontPropertiesFromName(name); | ||
|
@@ -667,7 +666,7 @@ HRESULT STDMETHODCALLTYPE GetCurrentFontFile(_Out_ IDWriteFontFile** fontFile) { | |
RETURN_HR_IF(E_ILLEGAL_METHOD_CALL, m_location < 0 || m_location > CFArrayGetCount(m_fontDatas.get())); | ||
|
||
if (0 <= m_location && m_location < m_previouslyCreatedFiles->size()) { | ||
*fontFile = m_previouslyCreatedFiles->at(m_location).Get(); | ||
m_previouslyCreatedFiles->at(m_location).CopyTo(fontFile); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
:D |
||
} else { | ||
ComPtr<IDWriteFactory> dwriteFactory; | ||
RETURN_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), &dwriteFactory)); | ||
|
@@ -716,8 +715,9 @@ HRESULT RuntimeClassInitialize() { | |
return S_OK; | ||
} | ||
|
||
void AddDatas(CFArrayRef fontDatas, CFArrayRef* errors) { | ||
HRESULT AddDatas(CFArrayRef fontDatas, CFArrayRef* errors) { | ||
CFMutableArrayRef outErrors = nil; | ||
HRESULT ret = S_OK; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
nit: result |
||
if (errors) { | ||
outErrors = CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks); | ||
*errors = outErrors; | ||
|
@@ -729,19 +729,24 @@ void AddDatas(CFArrayRef fontDatas, CFArrayRef* errors) { | |
if (data) { | ||
if (CFSetContainsValue(m_fontDatasSet.get(), data)) { | ||
__AppendErrorIfExists(outErrors, kCTFontManagerErrorAlreadyRegistered); | ||
ret = S_FALSE; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why does this have to be S_FALSE? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is created with a list of font files, so should one fail we need to return that it failed but still un(register) whatever fonts were valid |
||
} else { | ||
CFArrayAppendValue(m_fontDatas.get(), data); | ||
CFSetAddValue(m_fontDatasSet.get(), data); | ||
__AppendNullptrIfExists(outErrors); | ||
} | ||
} else { | ||
__AppendErrorIfExists(outErrors, kCTFontManagerErrorInvalidFontData); | ||
ret = S_FALSE; | ||
} | ||
} | ||
|
||
return ret; | ||
} | ||
|
||
void RemoveDatas(CFArrayRef fontDatas, CFArrayRef* errors) { | ||
HRESULT RemoveDatas(CFArrayRef fontDatas, CFArrayRef* errors) { | ||
CFMutableArrayRef outErrors = nil; | ||
HRESULT ret = S_OK; | ||
if (errors) { | ||
outErrors = CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks); | ||
*errors = outErrors; | ||
|
@@ -759,11 +764,15 @@ void RemoveDatas(CFArrayRef fontDatas, CFArrayRef* errors) { | |
__AppendNullptrIfExists(outErrors); | ||
} else { | ||
__AppendErrorIfExists(outErrors, kCTFontManagerErrorNotRegistered); | ||
ret = S_FALSE; | ||
} | ||
} else { | ||
__AppendErrorIfExists(outErrors, kCTFontManagerErrorInvalidFontData); | ||
ret = S_FALSE; | ||
} | ||
} | ||
|
||
return ret; | ||
} | ||
|
||
HRESULT STDMETHODCALLTYPE CreateEnumeratorFromKey(_In_ IDWriteFactory* factory, | ||
|
@@ -802,7 +811,7 @@ static void __AppendNullptrIfExists(CFMutableArrayRef errors) { | |
}; | ||
|
||
// TLambda is a member function of our FontCollectionLoader which updates the internal state of the loader | ||
// TLambda :: (DWriteFontCollectionLoader -> CFArrayRef -> CFArrayRef*) -> void | ||
// TLambda :: (DWriteFontCollectionLoader -> CFArrayRef -> CFArrayRef*) -> HRESULT | ||
template <typename TLambda> | ||
static HRESULT __DWriteUpdateUserCreatedFontCollection(CFArrayRef datas, CFArrayRef* errors, TLambda&& func) { | ||
ComPtr<IDWriteFactory> dwriteFactory; | ||
|
@@ -820,21 +829,20 @@ static HRESULT __DWriteUpdateUserCreatedFontCollection(CFArrayRef datas, CFArray | |
RETURN_IF_FAILED(createdLoader); | ||
|
||
// Call member function to update loader's font data | ||
func(loader.Get(), datas, errors); | ||
// Still want to update font collection with whatever fonts succeeded, so return ret at end | ||
HRESULT ret = func(loader.Get(), datas, errors); | ||
|
||
// DWrite won't create a new font collection unless we provide a new key each time | ||
// So every time we modify the font collection increment the key | ||
static size_t collectionKey = 0; | ||
++collectionKey; | ||
|
||
std::lock_guard<std::mutex> guard(_userCreatedFontCollectionMutex); | ||
RETURN_IF_FAILED( | ||
dwriteFactory->CreateCustomFontCollection(loader.Get(), &(collectionKey), sizeof(collectionKey), &_userCreatedFontCollection)); | ||
dwriteFactory->CreateCustomFontCollection(loader.Get(), &(++collectionKey), sizeof(collectionKey), &_userCreatedFontCollection)); | ||
|
||
// Update s_userFontPropertiesMap with new values | ||
s_userFontPropertiesMap = __CreatePropertiesMapForFontCollection(_userCreatedFontCollection.Get()); | ||
|
||
return S_OK; | ||
return ret; | ||
} | ||
|
||
// Registers user defined fonts to a collection so they can be created later | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -59,12 +59,11 @@ static bool __CTFontManagerUpdateWithFonts(CFArrayRef fontURLs, CTFontManagerSco | |
woc::unique_cf<CFMutableArrayRef> fontDatas{ CFArrayCreateMutable(nullptr, count, &kCFTypeArrayCallBacks) }; | ||
for (size_t i = 0; i < count; ++i) { | ||
NSData* data = [NSData dataWithContentsOfURL:static_cast<NSURL*>(CFArrayGetValueAtIndex(fontURLs, i))]; | ||
if (data != nullptr) { | ||
CFArrayAppendValue(fontDatas.get(), (CFDataRef)data); | ||
} | ||
CFArrayAppendValue(fontDatas.get(), (CFDataRef)data); | ||
} | ||
|
||
return SUCCEEDED(func(fontDatas.get(), errors)); | ||
// S_FALSE represents partial failure so cannot use SUCCEEDED macro | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why use S_FALSE? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It made more sense to use S_FALSE than E_* that might mean a more fatal failure than what we're experiencing |
||
return func(fontDatas.get(), errors) == S_OK; | ||
} | ||
|
||
// Gets CFData from CGFontRef if available, which are passed into DWriteWrapper methods | ||
|
@@ -84,7 +83,9 @@ static bool __CTFontManagerUpdateWithGraphicsFont(CGFontRef font, CFErrorRef _Nu | |
|
||
woc::unique_cf<CFArrayRef> fontDatas{ CFArrayCreate(nullptr, (const void**)&data, 1, &kCFTypeArrayCallBacks) }; | ||
CFArrayRef errors = nil; | ||
bool ret = SUCCEEDED(func(fontDatas.get(), &errors)); | ||
|
||
// S_FALSE represents partial failure so cannot use SUCCEEDED macro | ||
bool ret = func(fontDatas.get(), &errors) == S_OK; | ||
if (error != nil && errors != nil && CFArrayGetCount(errors) > 0L) { | ||
*error = (CFErrorRef)CFRetain(CFArrayGetValueAtIndex(errors, 0)); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,7 +39,8 @@ | |
woc::unique_cf<CGFontRef> cgfont{ CGFontCreateWithFontName(fontName.get()) }; | ||
EXPECT_NE(nullptr, cgfont); | ||
|
||
StrongId<NSString> familyName = (NSString*)CTFontCopyFamilyName(font.get()); | ||
StrongId<NSString> familyName; | ||
familyName.attach((NSString*)CTFontCopyFamilyName(font.get())); | ||
EXPECT_OBJCEQ(@"WinObjC", familyName); | ||
|
||
EXPECT_TRUE(CTFontManagerUnregisterFontsForURL((__bridge CFURLRef)testFileURL, kCTFontManagerScopeSession, &error)); | ||
|
@@ -48,7 +49,7 @@ | |
woc::unique_cf<CTFontRef> failedFont{ CTFontCreateWithName(fontName.get(), 20, nullptr) }; | ||
EXPECT_NE(nullptr, failedFont); | ||
|
||
familyName = (NSString*)CTFontCopyFullName(failedFont.get()); | ||
familyName.attach((NSString*)CTFontCopyFullName(failedFont.get())); | ||
EXPECT_OBJCEQ(@"Segoe UI", familyName); | ||
} | ||
|
||
|
@@ -69,7 +70,8 @@ | |
woc::unique_cf<CGFontRef> cgfont{ CGFontCreateWithFontName(fontName.get()) }; | ||
EXPECT_NE(nullptr, cgfont); | ||
|
||
StrongId<NSString> familyName = (NSString*)CTFontCopyFullName(font.get()); | ||
StrongId<NSString> familyName; | ||
familyName.attach((NSString*)CTFontCopyFullName(font.get())); | ||
EXPECT_OBJCEQ(@"WinObjC Italic", familyName); | ||
|
||
EXPECT_TRUE(CTFontManagerUnregisterGraphicsFont(graphicsFont.get(), &error)); | ||
|
@@ -85,7 +87,86 @@ | |
TEST(CTFontManager, ShouldFailToUnregisterNonregisteredFonts) { | ||
NSURL* testFileURL = __GetURLFromPathRelativeToModuleDirectory(@"/data/WinObjC.ttf"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what happened here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should return false, this was an error |
||
CFErrorRef error = nullptr; | ||
EXPECT_TRUE(CTFontManagerUnregisterFontsForURL((__bridge CFURLRef)testFileURL, kCTFontManagerScopeSession, &error)); | ||
EXPECT_FALSE(CTFontManagerUnregisterFontsForURL((__bridge CFURLRef)testFileURL, kCTFontManagerScopeSession, &error)); | ||
EXPECT_NE(nullptr, error); | ||
EXPECT_EQ(kCTFontManagerErrorNotRegistered, CFErrorGetCode(error)); | ||
} | ||
|
||
TEST(CTFontManager, ShouldFailToRegisterNonexistentFont) { | ||
NSURL* testFileURL = __GetURLFromPathRelativeToModuleDirectory(@"/data/BlatantlyNonexistentFont.ttf"); | ||
CFErrorRef error = nullptr; | ||
EXPECT_FALSE(CTFontManagerRegisterFontsForURL((__bridge CFURLRef)testFileURL, kCTFontManagerScopeSession, &error)); | ||
EXPECT_NE(nullptr, error); | ||
EXPECT_EQ(kCTFontManagerErrorInvalidFontData, CFErrorGetCode(error)); | ||
|
||
EXPECT_FALSE(CTFontManagerUnregisterFontsForURL((__bridge CFURLRef)testFileURL, kCTFontManagerScopeSession, &error)); | ||
EXPECT_NE(nullptr, error); | ||
EXPECT_EQ(kCTFontManagerErrorInvalidFontData, CFErrorGetCode(error)); | ||
} | ||
|
||
TEST(CTFontManager, ShouldFailToRegisterSameFontTwice) { | ||
woc::unique_cf<CFStringRef> fontName{ CFSTR("WinObjC") }; | ||
NSURL* testFileURL = __GetURLFromPathRelativeToModuleDirectory(@"/data/WinObjC.ttf"); | ||
CFErrorRef error = nullptr; | ||
EXPECT_TRUE(CTFontManagerRegisterFontsForURL((__bridge CFURLRef)testFileURL, kCTFontManagerScopeSession, &error)); | ||
EXPECT_EQ(nullptr, error); | ||
|
||
woc::unique_cf<CTFontRef> font{ CTFontCreateWithName(fontName.get(), 20, nullptr) }; | ||
EXPECT_NE(nullptr, font); | ||
|
||
StrongId<NSString> familyName; | ||
familyName.attach((NSString*)CTFontCopyFamilyName(font.get())); | ||
EXPECT_OBJCEQ(@"WinObjC", familyName); | ||
|
||
EXPECT_FALSE(CTFontManagerRegisterFontsForURL((__bridge CFURLRef)testFileURL, kCTFontManagerScopeSession, &error)); | ||
EXPECT_NE(nullptr, error); | ||
EXPECT_EQ(kCTFontManagerErrorAlreadyRegistered, CFErrorGetCode(error)); | ||
|
||
EXPECT_TRUE(CTFontManagerUnregisterFontsForURL((__bridge CFURLRef)testFileURL, kCTFontManagerScopeSession, &error)); | ||
EXPECT_EQ(nullptr, error); | ||
|
||
woc::unique_cf<CTFontRef> failedFont{ CTFontCreateWithName(fontName.get(), 20, nullptr) }; | ||
EXPECT_NE(nullptr, failedFont); | ||
|
||
familyName.attach((NSString*)CTFontCopyFullName(failedFont.get())); | ||
EXPECT_OBJCEQ(@"Segoe UI", familyName); | ||
} | ||
|
||
TEST(CTFontManager, ShouldRegisterMultipleFonts) { | ||
woc::unique_cf<CFStringRef> firstFontName{ CFSTR("WinObjC") }; | ||
woc::unique_cf<CFStringRef> secondFontName{ CFSTR("WinObjC-Italic") }; | ||
NSArray* urls = @[ | ||
__GetURLFromPathRelativeToModuleDirectory(@"/data/WinObjC.ttf"), | ||
__GetURLFromPathRelativeToModuleDirectory(@"/data/WinObjC-Italic.ttf") | ||
]; | ||
CFArrayRef errors = nullptr; | ||
EXPECT_TRUE(CTFontManagerRegisterFontsForURLs((__bridge CFArrayRef)urls, kCTFontManagerScopeSession, &errors)); | ||
EXPECT_NE(nullptr, errors); | ||
EXPECT_EQ(nullptr, CFArrayGetValueAtIndex(errors, 0)); | ||
EXPECT_EQ(nullptr, CFArrayGetValueAtIndex(errors, 1)); | ||
CFRelease(errors); | ||
|
||
woc::unique_cf<CTFontRef> firstFont{ CTFontCreateWithName(firstFontName.get(), 20, nullptr) }; | ||
EXPECT_NE(nullptr, firstFont); | ||
StrongId<NSString> firstName; | ||
firstName.attach((NSString*)CTFontCopyFullName(firstFont.get())); | ||
EXPECT_OBJCEQ(@"WinObjC", firstName); | ||
|
||
woc::unique_cf<CTFontRef> secondFont{ CTFontCreateWithName(secondFontName.get(), 20, nullptr) }; | ||
EXPECT_NE(nullptr, secondFont); | ||
StrongId<NSString> secondName; | ||
secondName.attach((NSString*)CTFontCopyFullName(secondFont.get())); | ||
EXPECT_OBJCEQ(@"WinObjC Italic", secondName); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you have repeated code here and in few other tests that do the same thing. |
||
|
||
EXPECT_TRUE(CTFontManagerUnregisterFontsForURLs((__bridge CFArrayRef)urls, kCTFontManagerScopeSession, &errors)); | ||
EXPECT_NE(nullptr, errors); | ||
EXPECT_EQ(nullptr, CFArrayGetValueAtIndex(errors, 0)); | ||
EXPECT_EQ(nullptr, CFArrayGetValueAtIndex(errors, 1)); | ||
CFRelease(errors); | ||
|
||
woc::unique_cf<CTFontRef> failedFont{ CTFontCreateWithName(secondFontName.get(), 20, nullptr) }; | ||
EXPECT_NE(nullptr, failedFont); | ||
|
||
secondName.attach((NSString*)CTFontCopyFullName(failedFont.get())); | ||
EXPECT_OBJCEQ(@"Segoe UI", secondName); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. addition of registering/unregistering multiple fonts test would be useful. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1, this is a good point that I missed in the previous PR In reply to: 90536678 [](ancestors = 90536678) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a test to attempt to register the same font. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this and CGDataProviderCreateWithFilename are pretty much the same logic.
re-use/refactor. rather than introducing the same code path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will all be changed for #1450 #WontFix