From bc9e7b71e70cf6499c55a7852b2d42bc8553295d Mon Sep 17 00:00:00 2001 From: Stefan Csomor Date: Sat, 12 Sep 2020 19:29:26 +0200 Subject: [PATCH] macOS wxNativeFontInfo changes (#2045) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * adding native font descriptor serialization = v2 * remove common xml prefix from serialized string * Update src/osx/carbon/font.cpp Co-authored-by: VZ * Update src/osx/carbon/font.cpp Co-authored-by: VZ * static string via accessor * striping off xml preamble unconditionally if we use a different format in the future, we will have to increase our version number * applying italic directly to the font descriptor thus preserving attributes in the native font descriptor which we don’t store explicitly yet * Adding support for preserving font width Although we don’t express this in the public API yet, we try to preserve eg condensed, when changing the font width * Adding reference, bug fix double checked the font weight constants, Co-authored-by: VZ --- include/wx/fontutil.h | 2 + src/osx/carbon/font.cpp | 232 ++++++++++++++++++++++++++++------------ 2 files changed, 165 insertions(+), 69 deletions(-) diff --git a/include/wx/fontutil.h b/include/wx/fontutil.h index eb09e0bbce..09ad8c8ef3 100644 --- a/include/wx/fontutil.h +++ b/include/wx/fontutil.h @@ -173,6 +173,7 @@ public: static CGFloat GetCTWeight( CTFontRef font ); static CGFloat GetCTWeight( CTFontDescriptorRef font ); + static CGFloat GetCTwidth( CTFontDescriptorRef font ); static CGFloat GetCTSlant( CTFontDescriptorRef font ); CTFontDescriptorRef GetCTFontDescriptor() const; @@ -182,6 +183,7 @@ private: // attributes for regenerating a CTFontDescriptor, stay close to native values // for better roundtrip fidelity CGFloat m_ctWeight; + CGFloat m_ctWidth; wxFontStyle m_style; CGFloat m_ctSize; wxFontFamily m_family; diff --git a/src/osx/carbon/font.cpp b/src/osx/carbon/font.cpp index 1c7e767eda..a54f5d9201 100644 --- a/src/osx/carbon/font.cpp +++ b/src/osx/carbon/font.cpp @@ -170,15 +170,15 @@ namespace const int kCTWeightsCount = 12; static CGFloat gCTWeights[kCTWeightsCount] = { -1.000, // 0 - -0.800, // 100 - -0.600, // 200 - -0.400, // 300 - 0.000, // 400 - 0.230, // 500 - 0.300, // 600 - 0.400, // 700 - 0.560, // 800 - 0.620, // 900 + -0.800, // 100, NSFontWeightUltraLight + -0.600, // 200, NSFontWeightThin + -0.400, // 300, NSFontWeightLight + 0.000, // 400, NSFontWeightRegular + 0.230, // 500, NSFontWeightMedium + 0.300, // 600, NSFontWeightSemibold + 0.400, // 700, NSFontWeightBold + 0.560, // 800, NSFontWeightHeavy + 0.620, // 900, NSFontWeightBlack 0.750, // 1000 }; @@ -746,6 +746,7 @@ void wxNativeFontInfo::Init() m_encoding = wxFONTENCODING_UTF8; m_ctWeight = 0.0; + m_ctWidth = 0.0; m_style = wxFONTSTYLE_NORMAL; m_ctSize = 0.0; m_family = wxFONTFAMILY_DEFAULT; @@ -765,6 +766,7 @@ void wxNativeFontInfo::Init(const wxNativeFontInfo& info) m_encoding = info.m_encoding; m_ctWeight = info.m_ctWeight; + m_ctWidth = info.m_ctWidth; m_style = info.m_style; m_ctSize = info.m_ctSize; m_family = info.m_family; @@ -787,6 +789,7 @@ void wxNativeFontInfo::InitFromFontDescriptor(CTFontDescriptorRef desc) m_descriptor.reset(wxCFRetain(desc)); m_ctWeight = GetCTWeight(desc); + m_ctWidth = GetCTwidth(desc); m_style = GetCTSlant(desc) > 0.01 ? wxFONTSTYLE_ITALIC : wxFONTSTYLE_NORMAL; wxCFTypeRef(CTFontDescriptorCopyAttribute(desc, kCTFontSizeAttribute)).GetValue(m_ctSize, CGFloat(0.0)); @@ -861,6 +864,7 @@ void wxNativeFontInfo::CreateCTFontDescriptor() traits.SetValue(kCTFontSymbolicTrait, kCTFontItalicTrait); traits.SetValue(kCTFontWeightTrait,m_ctWeight); + traits.SetValue(kCTFontWidthTrait,m_ctWidth); attributes.SetValue(kCTFontTraitsAttribute,traits.get()); attributes.SetValue(kCTFontSizeAttribute, m_ctSize); @@ -925,6 +929,15 @@ CGFloat wxNativeFontInfo::GetCTWeight(CTFontDescriptorRef descr) return weight; } +CGFloat wxNativeFontInfo::GetCTwidth(CTFontDescriptorRef descr) +{ + CGFloat weight; + CFTypeRef fonttraitstype = CTFontDescriptorCopyAttribute(descr, kCTFontTraitsAttribute); + wxCFDictionaryRef traits((CFDictionaryRef)fonttraitstype); + traits.GetValue(kCTFontWidthTrait).GetValue(&weight, CGFloat(0.0)); + return weight; +} + CGFloat wxNativeFontInfo::GetCTSlant(CTFontDescriptorRef descr) { CGFloat slant; @@ -934,8 +947,18 @@ CGFloat wxNativeFontInfo::GetCTSlant(CTFontDescriptorRef descr) return slant; } +// recipe taken from +// https://developer.apple.com/library/archive/documentation/StringsTextFonts/Conceptual/CoreText_Programming/FontOperations/FontOperations.html + +// common prefix of plist serializiation, gets removed and readded + +static const wxString& GetPListPrefix() +{ + static const wxString s_plistPrefix = ""; + return s_plistPrefix; +} -// bool wxNativeFontInfo::FromString(const wxString& s) { double d; @@ -949,86 +972,151 @@ bool wxNativeFontInfo::FromString(const wxString& s) if ( !token.ToLong(&l) ) return false; version = l; - // - // Ignore the version for now - // - token = tokenizer.GetNextToken(); - if ( !token.ToCDouble(&d) ) - return false; - if ( d < 0 || d > FLT_MAX ) - return false; + if ( version == 0 || version == 1 ) + { + token = tokenizer.GetNextToken(); + if ( !token.ToCDouble(&d) ) + return false; + if ( d < 0 || d > FLT_MAX ) + return false; #ifdef __LP64__ - // CGFloat is just double in this case. - m_ctSize = d; + // CGFloat is just double in this case. + m_ctSize = d; #else // !__LP64__ - m_ctSize = static_cast(d); + m_ctSize = static_cast(d); #endif // __LP64__/!__LP64__ - token = tokenizer.GetNextToken(); - if ( !token.ToLong(&l) ) - return false; - m_family = (wxFontFamily)l; + token = tokenizer.GetNextToken(); + if ( !token.ToLong(&l) ) + return false; + m_family = (wxFontFamily)l; - token = tokenizer.GetNextToken(); - if ( !token.ToLong(&l) ) - return false; - m_style = (wxFontStyle)l; + token = tokenizer.GetNextToken(); + if ( !token.ToLong(&l) ) + return false; + m_style = (wxFontStyle)l; - token = tokenizer.GetNextToken(); - if ( !token.ToLong(&l) ) - return false; - m_ctWeight = WXWeightToCT(wxFont::ConvertFromLegacyWeightIfNecessary(l)); + token = tokenizer.GetNextToken(); + if ( !token.ToLong(&l) ) + return false; + m_ctWeight = WXWeightToCT(wxFont::ConvertFromLegacyWeightIfNecessary(l)); - token = tokenizer.GetNextToken(); - if ( !token.ToLong(&l) ) - return false; - m_underlined = l != 0; + token = tokenizer.GetNextToken(); + if ( !token.ToLong(&l) ) + return false; + m_underlined = l != 0; - if ( version == 0L ) - { - m_strikethrough = false; + if ( version == 0L ) + { + m_strikethrough = false; + } + else + { + token = tokenizer.GetNextToken(); + if ( !token.ToLong(&l) ) + return false; + m_strikethrough = l != 0; + } + + // this works correctly via fallback even if this is (backwards compatibility) a font family name + SetPostScriptName( tokenizer.GetNextToken() ); + + RealizeResource(); + +#ifndef __WXMAC__ + if( !m_familyName ) + return false; +#endif + + token = tokenizer.GetNextToken(); + if ( !token.ToLong(&l) ) + return false; + m_encoding = (wxFontEncoding)l; + return true; } - else + else if ( version == 2 ) { token = tokenizer.GetNextToken(); if ( !token.ToLong(&l) ) return false; - m_strikethrough = l != 0; + bool underlined = l != 0; + + token = tokenizer.GetNextToken(); + if ( !token.ToLong(&l) ) + return false; + bool strikethrough = l != 0; + + token = tokenizer.GetNextToken(); + if ( !token.ToLong(&l) ) + return false; + wxFontEncoding encoding = (wxFontEncoding)l; + + wxString xml = tokenizer.GetString(); + xml = GetPListPrefix()+xml; + wxCFStringRef plist(xml); + wxCFDataRef listData(CFStringCreateExternalRepresentation(kCFAllocatorDefault,plist,kCFStringEncodingUTF8,0)); + wxCFDictionaryRef attributes((CFDictionaryRef) CFPropertyListCreateWithData(kCFAllocatorDefault, listData, 0, NULL, NULL)); + CTFontDescriptorRef descriptor = NULL; + if (attributes != NULL) + descriptor = CTFontDescriptorCreateWithAttributes(attributes); + if (descriptor != NULL) + { + InitFromFontDescriptor(descriptor); + m_underlined = underlined; + m_strikethrough = strikethrough; + m_encoding = encoding; + return true; + } } - // this works correctly via fallback even if this is (backwards compatibility) a font family name - SetPostScriptName( tokenizer.GetNextToken() ); - - RealizeResource(); - -#ifndef __WXMAC__ - if( !m_familyName ) - return false; -#endif - - token = tokenizer.GetNextToken(); - if ( !token.ToLong(&l) ) - return false; - m_encoding = (wxFontEncoding)l; - - return true; + return false; } wxString wxNativeFontInfo::ToString() const { wxString s; - s.Printf(wxT("%d;%s;%d;%d;%d;%d;%d;%s;%d"), - 1, // version - wxString::FromCDouble(GetFractionalPointSize()), - GetFamily(), - (int)GetStyle(), - GetNumericWeight(), - GetUnderlined(), - GetStrikethrough(), - GetPostScriptName().GetData(), - (int)GetEncoding()); + // version 2 is a streamed property list of the font descriptor as recommended by Apple + // prefixed by the attributes that are non-native to the native font ref like underline, strikethrough etc. + wxCFDictionaryRef attributes(CTFontDescriptorCopyAttributes(GetCTFontDescriptor())); + + if (attributes != NULL) + { + CFPropertyListFormat format = kCFPropertyListXMLFormat_v1_0; + if (CFPropertyListIsValid(attributes, format)) + { + wxCFDataRef listData(CFPropertyListCreateData(kCFAllocatorDefault, attributes, format, 0, NULL)); + wxCFStringRef cfString( CFStringCreateFromExternalRepresentation( kCFAllocatorDefault, listData, kCFStringEncodingUTF8) ); + wxString xml = cfString.AsString(); + xml.Replace("\r",wxEmptyString,true); + xml.Replace("\t",wxEmptyString,true); + xml = xml.Mid(xml.Find("