Skip to content

Commit

Permalink
Implement CTFontCopyLocalizedName and CTFontCopyAvailableTables (#2058)
Browse files Browse the repository at this point in the history
Fixes #2023

* Implement CTFont CopyLocalizedName and CopyAvailableTables

* Default params

* Spelling is hard

* Comment cleanup

* Remove leftover __debugbreak

* CR feedback

* Consistent nil handling
  • Loading branch information
aballway authored Mar 2, 2017
1 parent bf09dcc commit 5c54820
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 34 deletions.
22 changes: 14 additions & 8 deletions Frameworks/CoreGraphics/DWriteWrapper.mm
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,14 @@ HRESULT UpdateCollection(const ComPtr<IDWriteFactory>& dwriteFactory, const ComP
*
* @return CFString object.
*/
CFStringRef _CFStringFromLocalizedString(IDWriteLocalizedStrings* localizedString) {
CFStringRef _CFStringFromLocalizedString(IDWriteLocalizedStrings* localizedString, CFStringRef* actualLanguage) {
if (localizedString == NULL) {
TraceError(TAG, L"The input parameter is invalid!");
return nil;
}

wchar_t localeNameBuf[LOCALE_NAME_MAX_LENGTH];
const wchar_t* localeName = __GetUserDefaultLocaleName(localeNameBuf, LOCALE_NAME_MAX_LENGTH);
wchar_t localeBuffer[LOCALE_NAME_MAX_LENGTH];
const wchar_t* localeName = __GetUserDefaultLocaleName(localeBuffer, LOCALE_NAME_MAX_LENGTH);

uint32_t index = 0;
BOOL exists = FALSE;
Expand All @@ -131,6 +131,11 @@ CFStringRef _CFStringFromLocalizedString(IDWriteLocalizedStrings* localizedStrin
index = 0;
}

if (actualLanguage) {
RETURN_NULL_IF_FAILED(localizedString->GetLocaleName(index, localeBuffer, LOCALE_NAME_MAX_LENGTH));
*actualLanguage = CFStringCreateWithCharacters(nullptr, (const UniChar*)localeBuffer, wcslen(localeBuffer));
}

// Get the string length.
uint32_t length = 0;
RETURN_NULL_IF_FAILED(localizedString->GetStringLength(index, &length));
Expand Down Expand Up @@ -274,8 +279,8 @@ HRESULT _DWriteCreateTextFormatWithFontNameAndSize(CFStringRef optionalFontName,
userFontInfo = __GetUserFontCollectionHelper()->GetFontPropertiesFromUppercaseFontName(upperFontName);
}

wchar_t localeNameBuf[LOCALE_NAME_MAX_LENGTH];
const wchar_t* localeName = __GetUserDefaultLocaleName(localeNameBuf, LOCALE_NAME_MAX_LENGTH);
wchar_t localeBuffer[LOCALE_NAME_MAX_LENGTH];
const wchar_t* localeName = __GetUserDefaultLocaleName(localeBuffer, LOCALE_NAME_MAX_LENGTH);

if (info) {
RETURN_IF_FAILED(
Expand Down Expand Up @@ -312,7 +317,8 @@ HRESULT _DWriteCreateTextFormatWithFontNameAndSize(CFStringRef optionalFontName,
* Gets an informational string as a CFString from a DWrite font face.
*/
CFStringRef _DWriteFontCopyInformationalString(const ComPtr<IDWriteFontFace>& fontFace,
DWRITE_INFORMATIONAL_STRING_ID informationalStringId) {
DWRITE_INFORMATIONAL_STRING_ID informationalStringId,
CFStringRef* actualLanguage) {
RETURN_NULL_IF(!fontFace);

ComPtr<IDWriteFontFace3> dwriteFontFace3;
Expand All @@ -322,7 +328,7 @@ CFStringRef _DWriteFontCopyInformationalString(const ComPtr<IDWriteFontFace>& fo
BOOL exists;

RETURN_NULL_IF_FAILED(dwriteFontFace3->GetInformationalStrings(informationalStringId, &name, &exists));
return exists ? static_cast<CFStringRef>(CFRetain(_CFStringFromLocalizedString(name.Get()))) : nullptr;
return exists ? static_cast<CFStringRef>(CFRetain(_CFStringFromLocalizedString(name.Get(), actualLanguage))) : nullptr;
}

/**
Expand All @@ -334,7 +340,7 @@ CFDataRef _DWriteFontCopyTable(const ComPtr<IDWriteFontFace>& fontFace, uint32_t
void* tableContext;
BOOL exists;

RETURN_NULL_IF_FAILED(fontFace->TryGetFontTable(tag, &tableData, &tableSize, &tableContext, &exists));
RETURN_NULL_IF_FAILED(fontFace->TryGetFontTable(_CTToDWriteFontTableTag(tag), &tableData, &tableSize, &tableContext, &exists));
RETURN_NULL_IF(!exists);

// Copy the font table's binary data to a CFDataRef
Expand Down
24 changes: 12 additions & 12 deletions Frameworks/CoreText/CTFont.mm
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//******************************************************************************
//
// Copyright (c) 2016 Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft. All rights reserved.
//
// This code is licensed under the MIT License (MIT).
//
Expand Down Expand Up @@ -493,17 +493,16 @@ CFStringRef CTFontCopyDisplayName(CTFontRef font) {
@Status Interoperable
*/
CFStringRef CTFontCopyName(CTFontRef font, CFStringRef nameKey) {
RETURN_NULL_IF(!font);
return _DWriteFontCopyName(font->_dwriteFontFace, nameKey);
return CTFontCopyLocalizedName(font, nameKey, nullptr);
}

/**
@Status Stub
@Status Interoperable
@Notes
*/
CFStringRef CTFontCopyLocalizedName(CTFontRef font, CFStringRef nameKey, CFStringRef _Nullable* actualLanguage) {
UNIMPLEMENTED();
return StubReturn();
RETURN_NULL_IF(!font);
return _DWriteFontCopyName(font->_dwriteFontFace, nameKey, actualLanguage);
}

/**
Expand Down Expand Up @@ -901,19 +900,20 @@ CTFontRef CTFontCreateWithGraphicsFont(CGFontRef cgFont, CGFloat size, const CGA
return (CTFontRef)ret;
}
/**
@Status Stub
@Notes
@Status Caveat
@Notes options is not supported
*/
CFArrayRef CTFontCopyAvailableTables(CTFontRef font, CTFontTableOptions options) {
UNIMPLEMENTED();
return StubReturn();
RETURN_NULL_IF(!font);
return _DWriteCopyAvailableFontTables(font->_dwriteFontFace);
}

/**
@Status Interoperable
@Notes options is not supported, and is deprecated anyway
@Status Caveat
@Notes options is not supported
*/
CFDataRef CTFontCopyTable(CTFontRef font, CTFontTableTag table, CTFontTableOptions options) {
RETURN_NULL_IF(!font);
return _DWriteFontCopyTable(font->_dwriteFontFace, table);
}

Expand Down
50 changes: 44 additions & 6 deletions Frameworks/CoreText/DWriteWrapper_CTFont.mm
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ static CFDictionaryRef _DWriteFontCreateTraitsDict(const ComPtr<IDWriteFontFace>
/**
* Gets a name/informational string from a DWrite font face corresponding to a CTFont constant
*/
CFStringRef _DWriteFontCopyName(const ComPtr<IDWriteFontFace>& fontFace, CFStringRef nameKey) {
CFStringRef _DWriteFontCopyName(const ComPtr<IDWriteFontFace>& fontFace, CFStringRef nameKey, CFStringRef* actualLanguage) {
if (nameKey == nullptr || fontFace == nullptr) {
return nullptr;
}
Expand All @@ -301,13 +301,14 @@ CFStringRef _DWriteFontCopyName(const ComPtr<IDWriteFontFace>& fontFace, CFStrin
RETURN_NULL_IF_FAILED(fontFace.As(&dwriteFontFace3));
ComPtr<IDWriteLocalizedStrings> name;
RETURN_NULL_IF_FAILED(dwriteFontFace3->GetFamilyNames(&name));
return static_cast<CFStringRef>(CFRetain(_CFStringFromLocalizedString(name.Get())));
return static_cast<CFStringRef>(CFRetain(_CFStringFromLocalizedString(name.Get(), actualLanguage)));

} else if (CFEqual(nameKey, kCTFontSubFamilyNameKey)) {
// Similar to above, WIN32_SUBFAMILY_NAMES is limited to four fonts per family,
// but PREFERRED_SUBFAMILY_NAMES is only sometimes present (if it differs from WIN32_SUBFAMILY_NAMES)
// Try PREFERRED first
CFStringRef ret = _DWriteFontCopyInformationalString(fontFace, DWRITE_INFORMATIONAL_STRING_PREFERRED_SUBFAMILY_NAMES);
CFStringRef ret =
_DWriteFontCopyInformationalString(fontFace, DWRITE_INFORMATIONAL_STRING_PREFERRED_SUBFAMILY_NAMES, actualLanguage);
if (ret) {
return ret;
}
Expand All @@ -319,14 +320,15 @@ CFStringRef _DWriteFontCopyName(const ComPtr<IDWriteFontFace>& fontFace, CFStrin
RETURN_NULL_IF_FAILED(fontFace.As(&dwriteFontFace3));
ComPtr<IDWriteLocalizedStrings> name;
RETURN_NULL_IF_FAILED(dwriteFontFace3->GetFaceNames(&name));
return static_cast<CFStringRef>(CFRetain(_CFStringFromLocalizedString(name.Get())));
return static_cast<CFStringRef>(CFRetain(_CFStringFromLocalizedString(name.Get(), actualLanguage)));

} else if (CFEqual(nameKey, kCTFontUniqueNameKey)) {
return CFStringCreateWithFormat(kCFAllocatorDefault,
nullptr,
CFSTR("%@ %@"),
// Only want to assign to actualLanguage once
CFAutorelease(_DWriteFontCopyName(fontFace, kCTFontFullNameKey)),
CFAutorelease(_DWriteFontCopyName(fontFace, kCTFontStyleNameKey)));
CFAutorelease(_DWriteFontCopyName(fontFace, kCTFontStyleNameKey, actualLanguage)));

} else if (CFEqual(nameKey, kCTFontFullNameKey)) {
informationalStringId = DWRITE_INFORMATIONAL_STRING_FULL_NAME;
Expand Down Expand Up @@ -358,5 +360,41 @@ CFStringRef _DWriteFontCopyName(const ComPtr<IDWriteFontFace>& fontFace, CFStrin
informationalStringId = DWRITE_INFORMATIONAL_STRING_NONE;
}

return _DWriteFontCopyInformationalString(fontFace, informationalStringId);
return _DWriteFontCopyInformationalString(fontFace, informationalStringId, actualLanguage);
}

static constexpr CTFontTableTag sc_tags[] = { kCTFontTableBASE, kCTFontTableCFF, kCTFontTableDSIG, kCTFontTableEBDT, kCTFontTableEBLC,
kCTFontTableEBSC, kCTFontTableGDEF, kCTFontTableGPOS, kCTFontTableGSUB, kCTFontTableJSTF,
kCTFontTableLTSH, kCTFontTableOS2, kCTFontTablePCLT, kCTFontTableVDMX, kCTFontTableVORG,
kCTFontTableZapf, kCTFontTableAcnt, kCTFontTableAvar, kCTFontTableBdat, kCTFontTableBhed,
kCTFontTableBloc, kCTFontTableBsln, kCTFontTableCmap, kCTFontTableCvar, kCTFontTableCvt,
kCTFontTableFdsc, kCTFontTableFeat, kCTFontTableFmtx, kCTFontTableFpgm, kCTFontTableFvar,
kCTFontTableGasp, kCTFontTableGlyf, kCTFontTableGvar, kCTFontTableHdmx, kCTFontTableHead,
kCTFontTableHhea, kCTFontTableHmtx, kCTFontTableHsty, kCTFontTableJust, kCTFontTableKern,
kCTFontTableKerx, kCTFontTableLcar, kCTFontTableLoca, kCTFontTableMaxp, kCTFontTableMort,
kCTFontTableMorx, kCTFontTableName, kCTFontTableOpbd, kCTFontTablePost, kCTFontTablePrep,
kCTFontTableProp, kCTFontTableSbit, kCTFontTableSbix, kCTFontTableTrak, kCTFontTableVhea,
kCTFontTableVmtx };

CFArrayRef _DWriteCopyAvailableFontTables(const ComPtr<IDWriteFontFace>& fontFace) {
auto ret = woc::MakeAutoCF<CFMutableArrayRef>(CFArrayCreateMutable(nullptr, 0, nullptr));
for (CTFontTableTag tag : sc_tags) {
const void* tableData;
uint32_t tableSize;
void* tableContext;
BOOL exists;

RETURN_NULL_IF_FAILED(fontFace->TryGetFontTable(_CTToDWriteFontTableTag(tag), &tableData, &tableSize, &tableContext, &exists));
if (exists) {
// Returned array is expected to have unboxed CTFontTableTag values
CFArrayAppendValue(ret, (const void*)tag);
}

// As part of the contact for TryGetFontTable, tableContext must always be released via ReleaseFontTable
if (tableContext) {
fontFace->ReleaseFontTable(tableContext);
}
}

return ret.detach();
}
8 changes: 6 additions & 2 deletions Frameworks/CoreText/DWriteWrapper_CoreText.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,14 @@ CTFontSymbolicTraits _CTFontSymbolicTraitsFromCFNumber(CFNumberRef num);
HRESULT _DWriteCreateFontFaceWithFontDescriptor(CTFontDescriptorRef fontDescriptor, IDWriteFontFace** fontFace);

CFDictionaryRef _DWriteFontCreateTraitsDict(const Microsoft::WRL::ComPtr<IDWriteFontFace>& fontFace);
CFStringRef _DWriteFontCopyName(const Microsoft::WRL::ComPtr<IDWriteFontFace>& fontFace, CFStringRef nameKey);
CFStringRef _DWriteFontCopyName(const Microsoft::WRL::ComPtr<IDWriteFontFace>& fontFace,
CFStringRef nameKey,
CFStringRef* actualLanguage = nullptr);

// DWriteWrapper functions relating to CGPath
CGPathRef _DWriteFontCreatePathForGlyph(const Microsoft::WRL::ComPtr<IDWriteFontFace>& fontFace,
CGFloat pointSize,
CGGlyph glyph,
const CGAffineTransform* transform);
const CGAffineTransform* transform);

CFArrayRef _DWriteCopyAvailableFontTables(const Microsoft::WRL::ComPtr<IDWriteFontFace>& fontFace);
12 changes: 9 additions & 3 deletions Frameworks/include/CoreGraphics/DWriteWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ static inline CFStringRef _CFStringCreateUppercaseCopy(CFStringRef string) {
return ret;
}

COREGRAPHICS_EXPORT CFStringRef _CFStringFromLocalizedString(IDWriteLocalizedStrings* localizedString);
COREGRAPHICS_EXPORT CFStringRef _CFStringFromLocalizedString(IDWriteLocalizedStrings* localizedString, CFStringRef* language = nullptr);

struct _DWriteFontProperties {
DWRITE_FONT_WEIGHT weight = DWRITE_FONT_WEIGHT_NORMAL;
Expand Down Expand Up @@ -65,7 +65,8 @@ COREGRAPHICS_EXPORT HRESULT _DWriteCreateTextFormatWithFontNameAndSize(CFStringR

// DWriteFont getters that convert to a CF/CG object or struct
COREGRAPHICS_EXPORT CFStringRef _DWriteFontCopyInformationalString(const Microsoft::WRL::ComPtr<IDWriteFontFace>& fontFace,
DWRITE_INFORMATIONAL_STRING_ID informationalStringId);
DWRITE_INFORMATIONAL_STRING_ID informationalStringId,
CFStringRef* language = nullptr);
COREGRAPHICS_EXPORT CFDataRef _DWriteFontCopyTable(const Microsoft::WRL::ComPtr<IDWriteFontFace>& fontFace, uint32_t tag);
COREGRAPHICS_EXPORT CGFloat _DWriteFontGetSlantDegrees(const Microsoft::WRL::ComPtr<IDWriteFontFace>& fontFace);
COREGRAPHICS_EXPORT CGRect _DWriteFontGetBoundingBox(const Microsoft::WRL::ComPtr<IDWriteFontFace>& fontFace);
Expand All @@ -75,4 +76,9 @@ COREGRAPHICS_EXPORT HRESULT _DWriteFontGetBoundingBoxesForGlyphs(
// DWrite functions relating to font binary data
COREGRAPHICS_EXPORT HRESULT _DWriteRegisterFontsWithDatas(CFArrayRef fontDatas, CFArrayRef* errors);
COREGRAPHICS_EXPORT HRESULT _DWriteUnregisterFontsWithDatas(CFArrayRef fontDatas, CFArrayRef* errors);
COREGRAPHICS_EXPORT HRESULT _DWriteCreateFontFaceWithData(CGDataProviderRef data, IDWriteFontFace** outFontFace);
COREGRAPHICS_EXPORT HRESULT _DWriteCreateFontFaceWithData(CGDataProviderRef data, IDWriteFontFace** outFontFace);

inline uint32_t _CTToDWriteFontTableTag(uint32_t tag) {
// CT has the opposite byte order of DWrite, so we need 'BASE' -> 'ESAB'
return ((tag & 0xff) << 24) | ((tag & 0xff00) << 8) | ((tag & 0xff0000) >> 8) | ((tag & 0xff000000) >> 24);
}
4 changes: 2 additions & 2 deletions include/CoreText/CTFont.h
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ CORETEXT_EXPORT CFStringRef CTFontCopyFamilyName(CTFontRef font);
CORETEXT_EXPORT CFStringRef CTFontCopyFullName(CTFontRef font);
CORETEXT_EXPORT CFStringRef CTFontCopyDisplayName(CTFontRef font);
CORETEXT_EXPORT CFStringRef CTFontCopyName(CTFontRef font, CFStringRef nameKey);
CORETEXT_EXPORT CFStringRef CTFontCopyLocalizedName(CTFontRef font, CFStringRef nameKey, CFStringRef _Nullable* actualLanguage) STUB_METHOD;
CORETEXT_EXPORT CFStringRef CTFontCopyLocalizedName(CTFontRef font, CFStringRef nameKey, CFStringRef _Nullable* actualLanguage);
CORETEXT_EXPORT CFCharacterSetRef CTFontCopyCharacterSet(CTFontRef font) STUB_METHOD;
CORETEXT_EXPORT CFStringEncoding CTFontGetStringEncoding(CTFontRef font) STUB_METHOD;
CORETEXT_EXPORT CFArrayRef CTFontCopySupportedLanguages(CTFontRef font) STUB_METHOD;
Expand Down Expand Up @@ -250,6 +250,6 @@ CORETEXT_EXPORT CTFontRef CTFontCreateWithGraphicsFont(CGFontRef graphicsFont,
CGFloat size,
const CGAffineTransform* matrix,
CTFontDescriptorRef attributes);
CORETEXT_EXPORT CFArrayRef CTFontCopyAvailableTables(CTFontRef font, CTFontTableOptions options) STUB_METHOD;
CORETEXT_EXPORT CFArrayRef CTFontCopyAvailableTables(CTFontRef font, CTFontTableOptions options);
CORETEXT_EXPORT CFDataRef CTFontCopyTable(CTFontRef font, CTFontTableTag table, CTFontTableOptions options);
CORETEXT_EXPORT CFTypeID CTFontGetTypeID();
43 changes: 42 additions & 1 deletion tests/unittests/CoreText/CTFontTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -740,4 +740,45 @@ virtual void TearDown() {
});
CGPathApply(pathWithFontSizeAndTransforms, &comparePathContext, comparePathToExpected);
ASSERT_EQ(expectedElements.size(), comparePathContext.count);
}
}

static const NSString* sc_localeName = @"en-us";
TEST(CTFont, CopyLocalizedName) {
auto fontName = woc::MakeAutoCF<CFStringRef>(CFSTR("Metadata Test"));
StrongId<NSURL> testFileURL = __GetURLFromPathRelativeToModuleDirectory(@"/data/MetadataTest-Regular.ttf");
CFErrorRef error = nullptr;
EXPECT_TRUE(CTFontManagerRegisterFontsForURL((__bridge CFURLRef)testFileURL.get(), kCTFontManagerScopeSession, &error));
EXPECT_EQ(nullptr, error);

auto font = woc::MakeAutoCF<CTFontRef>(CTFontCreateWithName(fontName, 20, nullptr));
ASSERT_NE(nullptr, font);

// Actual language out-param should be optional
auto copyrightNameWithoutLang = woc::MakeAutoCF<CFStringRef>(CTFontCopyLocalizedName(font, kCTFontCopyrightNameKey, nullptr));
EXPECT_OBJCEQ(c_copyrightName, (__bridge NSString*)copyrightNameWithoutLang.get());

CFStringRef actualLanguage = nullptr;
auto copyrightName = woc::MakeAutoCF<CFStringRef>(CTFontCopyLocalizedName(font, kCTFontCopyrightNameKey, &actualLanguage));
EXPECT_OBJCEQ(c_copyrightName, (__bridge NSString*)copyrightName.get());
ASSERT_NE(nil, actualLanguage);
ASSERT_EQ(5, CFStringGetLength(actualLanguage));
EXPECT_OBJCEQ(sc_localeName, (__bridge NSString*)actualLanguage);

EXPECT_TRUE(CTFontManagerUnregisterFontsForURL((__bridge CFURLRef)testFileURL.get(), kCTFontManagerScopeSession, &error));
EXPECT_EQ(nullptr, error);
}

TEST(CTFont, CopyAvailableTables) {
auto font = woc::MakeAutoCF<CTFontRef>(CTFontCreateWithName(CFSTR("Arial"), 20, nullptr));
auto availableTables = woc::MakeAutoCF<CFArrayRef>(CTFontCopyAvailableTables(font, kCTFontTableOptionNoOptions));
ASSERT_NE(nil, availableTables);
CFIndex count = CFArrayGetCount(availableTables);
EXPECT_LT(0, count);

// There are 55 supported font tables
EXPECT_GE(55, count);

// Don't want to make test too precise so that it may fail should fonts change, but 'cmap' is a required font table
// So it should be safe to always test that this value is available
EXPECT_TRUE(CFArrayContainsValue(availableTables, { 0, count }, (const void*)kCTFontTableCmap));
}

0 comments on commit 5c54820

Please sign in to comment.