Files
opennurbs/opennurbs_apple_nsfont.cpp
2024-08-22 01:43:04 -07:00

1211 lines
37 KiB
C++

//
// Copyright (c) 1993-2022 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
#include "opennurbs.h"
#if !defined(ON_COMPILING_OPENNURBS)
// This check is included in all opennurbs source .c and .cpp files to insure
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined
// and the opennurbs .h files alter what is declared and how it is declared.
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
#endif
#if defined(ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE)
#include "opennurbs_internal_glyph.h"
#include "opennurbs_apple_nsfont.h"
class ON_AppleCTFontInformation
{
// "fake class" (for now)
public:
ON_AppleCTFontInformation() = default;
~ON_AppleCTFontInformation() = default;
ON_AppleCTFontInformation(const ON_AppleCTFontInformation&) = default;
ON_AppleCTFontInformation& operator=(const ON_AppleCTFontInformation&) = default;
private:
ON__UINT_PTR m_nothing = 0;
};
ON_Font::ON_Font(
ON_Font::FontType font_type,
const class ON_AppleCTFontInformation& apple_font_information
)
: m_font_type(font_type)
{}
void ON_ManagedFonts::Internal_GetAppleInstalledCTFonts(
ON_SimpleArray<const ON_Font*>& platform_font_list
)
{
ON_AppleCTFontInformation currently_nothing;
CFDictionaryRef options = nullptr;
CTFontCollectionRef availableFontCollection = CTFontCollectionCreateFromAvailableFonts(options);
if (nullptr == availableFontCollection )
return;
CFArrayRef availableFontArray = CTFontCollectionCreateMatchingFontDescriptors(availableFontCollection);
CFRelease(availableFontCollection);
if (nullptr == availableFontArray)
return;
const CFIndex count = CFArrayGetCount(availableFontArray);
platform_font_list.Reserve((int)count);
for ( CFIndex idx = 0; idx < count; idx++)
{
CTFontDescriptorRef descriptor = (CTFontDescriptorRef)CFArrayGetValueAtIndex(availableFontArray, idx);
if (nullptr == descriptor)
continue;
const CGAffineTransform *matrix = nullptr;
CGFloat size = 1000;
CTFontRef font = CTFontCreateWithFontDescriptor(descriptor, size, matrix);
if (nullptr == font)
continue;
const unsigned int units_per_em = CTFontGetUnitsPerEm(font);
if ( units_per_em > 0 && (CGFloat)units_per_em != size )
{
CFRelease(font);
size = (CGFloat)units_per_em;
font = CTFontCreateWithFontDescriptor(descriptor, size, matrix);
if (nullptr == font)
continue;
}
// It is critical that m_font_type = ON_Font::FontType::InstalledFont
ON_Font* platform_font = new ON_Font(ON_Font::FontType::InstalledFont,currently_nothing);
if (false == platform_font->SetFromAppleCTFont(font, false))
{
CFRelease(font);
delete platform_font;
continue;
}
CFRelease(font);
platform_font->SetPointSize(0);
platform_font_list.Append(platform_font);
}
CFRelease(availableFontArray);
}
const ON_Font* ON_Font::GetManagedFontFromAppleCTFont(
CTFontRef apple_font,
bool bAnnotationFont
)
{
ON_Font font_characteristics;
if ( false == font_characteristics.SetFromAppleCTFont(apple_font,bAnnotationFont) )
return nullptr;
return font_characteristics.ManagedFont();
}
unsigned int ON_Font::AppleCTFontUnitsPerEm(CTFontRef apple_font)
{
const unsigned int units_per_em
= (nullptr != apple_font)
? CTFontGetUnitsPerEm(apple_font)
: 0;
return units_per_em;
}
CTFontRef ON_Font::AppleCTFontSetSize(
CTFontRef apple_font,
CGFloat size,
bool bReleaseOriginal
)
{
for(;;)
{
if (nullptr == apple_font)
break;
const unsigned int units_per_em = ON_Font::AppleCTFontUnitsPerEm(apple_font);
if ( units_per_em <= 0 )
break;
const CGFloat units_per_em_size = (CGFloat)units_per_em;
if ( !(units_per_em_size > 0.0) )
break;
const CGFloat size0 = CTFontGetSize(apple_font);
if ( fabs(size0 - units_per_em_size) <= 0.0001 )
break;
const CGAffineTransform *matrix = nullptr;
CTFontDescriptorRef attributes = nullptr;
CTFontRef apple_font1 = CTFontCreateCopyWithAttributes(apple_font, units_per_em_size, matrix, attributes);
if ( nullptr == apple_font1)
break;
const unsigned int units_per_em1 = CTFontGetUnitsPerEm(apple_font1);
if ( units_per_em1 != units_per_em)
{
CFRelease(apple_font1);
break;
}
if (bReleaseOriginal)
CFRelease(apple_font);
return apple_font1;
}
return apple_font;
}
ON_PANOSE1 ON_Font::AppleCTFontPANOSE1(CTFontRef apple_font)
{
ON_PANOSE1 panose1 = ON_PANOSE1::Zero;
CFDataRef os2Table = nullptr;
for(;;)
{
if (nullptr == apple_font)
break;
// The OS/2 table contains the PANOSE1 classification
os2Table = CTFontCopyTable(apple_font, kCTFontTableOS2, kCTFontTableOptionNoOptions);
if (nullptr == os2Table)
break;
// The PANOSE data is in bytes 32-42 of the table according to the TrueType and OpenType specs.
if (CFDataGetLength(os2Table) < 42)
{
// Truncated table?
break;
}
uint8_t panose[10] = {};
CFDataGetBytes(os2Table, (CFRange){ 32, 10 }, panose);
if (panose[0] > 5)
break; // invalid PANOSE1 family kind
panose1.SetTenBytes(panose);
break;
}
if (nullptr != os2Table)
CFRelease(os2Table);
return panose1;
}
bool ON_Font::SetFromAppleCTFont(CTFontRef apple_font, bool bAnnotationFont)
{
if (nullptr == apple_font)
return false;
if ( false == this->ModificationPermitted(OPENNURBS__FUNCTION__,__FILE__,__LINE__) )
return false;
*this = ON_Font::Unset;
const ON_wString postscript_name = ON_Font::AppleCTFontPostScriptName(apple_font);
const ON_wString family_name = ON_Font::AppleCTFontFamilyName(apple_font);
const ON_wString face_name = ON_Font::AppleCTFontFaceName(apple_font);
// Set Windows LOGFONT.lfFaceName to something not empty there is some hope this
// font might work in Rhino 6/7 for Windows too.
const ON_wString windows_logfont_name = ON_Font::FakeWindowsLogfontNameFromFamilyAndPostScriptNames(family_name, postscript_name);
const bool rc = postscript_name.IsNotEmpty() || family_name.IsNotEmpty();
if (rc)
{
m_loc_postscript_name = postscript_name;
m_en_postscript_name = postscript_name;
m_loc_family_name = family_name;
m_en_family_name = family_name;
m_loc_face_name = face_name;
m_en_face_name = face_name;
m_loc_windows_logfont_name = windows_logfont_name;
m_en_windows_logfont_name = windows_logfont_name;
// Can get font metrics from NSFontDescriptor
// https://developer.apple.com/library/content/documentation/TextFonts/Conceptual/CocoaTextArchitecture/FontHandling/FontHandling.html
// defaultLineHeight(for theFont: NSFont)
// fd.xHeight, fd.ascender, fd.descender, fd.capHeight, fd.defaultLineHeightForFont
// Set weight - used if this font needs sustution on another computer
// https://mcneel.myjetbrains.com/youtrack/issue/RH-37075
double apple_font_weight_trait = ON_UNSET_VALUE;
double apple_font_width_trait = ON_UNSET_VALUE;
double apple_font_slant_trait = ON_UNSET_VALUE;
ON_Font::Weight weight = ON_Font::Weight::Normal;
ON_Font::Style style = ON_Font::Style::Upright;
ON_Font::Stretch stretch = ON_Font::Stretch::Medium;
CTFontDescriptorRef descriptor = CTFontCopyFontDescriptor(apple_font);
if (nullptr != descriptor)
{
CFDictionaryRef dict = (CFDictionaryRef)CTFontDescriptorCopyAttribute(descriptor, kCTFontTraitsAttribute);
if (nullptr != dict)
{
const CFNumberRef appleSymbolicTrait = (CFNumberRef)CFDictionaryGetValue(dict, kCTFontSymbolicTrait);
const CFNumberRef appleWeight = (CFNumberRef)CFDictionaryGetValue(dict, kCTFontWeightTrait);
const CFNumberRef appleWidth = (CFNumberRef)CFDictionaryGetValue(dict, kCTFontWidthTrait);
const CFNumberRef appleSlant = (CFNumberRef)CFDictionaryGetValue(dict, kCTFontSlantTrait);
if (nullptr != appleSymbolicTrait)
{
int i=0;
if ( CFNumberGetValue(appleSymbolicTrait, kCFNumberIntType, &i) )
{
// Use the kCTFontSymbolicTrait key to access the symbolic traits value from the font traits dictionary.
// The value is returned as a CFNumberRef.
if (0 != (kCTFontTraitItalic & i))
style = ON_Font::Style::Italic;
if (0 != (kCTFontTraitBold & i))
weight = ON_Font::Weight::Bold;
if (0 != (kCTFontTraitCondensed & i))
stretch = ON_Font::Stretch::Condensed;
else if (0 != (kCTFontTraitExpanded & i))
stretch = ON_Font::Stretch::Expanded;
}
}
if (nullptr != appleWeight)
{
double x = apple_font_weight_trait;
if ( CFNumberGetValue(appleWeight, kCFNumberFloat64Type, &x) && -1.0 <= x && x <= 1.0)
{
// Use the kCTFontWeightTrait key to access the normalized weight trait from the font traits dictionary.
// The value returned is a CFNumberRef representing a float value between -1.0 and 1.0 for normalized weight.
// The value of 0.0 corresponds to the regular or medium font weight.
apple_font_weight_trait = x;
ON_Font::WeightFromAppleFontWeightTrait(apple_font_weight_trait);
}
}
if (nullptr != appleWidth)
{
double x = apple_font_width_trait;
if ( CFNumberGetValue(appleWidth, kCFNumberFloat64Type, &x) && -1.0 <= x && x <= 1.0 )
{
// Use the kCTFontWidthTrait key to access the normalized proportion trait from the font traits dictionary.
// This value corresponds to the relative inter-glyph spacing for a given font.
// The value returned is a CFNumberRef representing a float between -1.0 and 1.0.
// The value of 0.0 corresponds to regular glyph spacing while negative values represent condensed glyph spacing.
apple_font_width_trait = x;
if (-1.0 == apple_font_width_trait)
stretch = ON_Font::Stretch::Condensed;
else if (1.0 == apple_font_width_trait)
stretch = ON_Font::Stretch::Expanded;
}
}
if (nullptr != appleSlant)
{
double x = apple_font_weight_trait;
if ( CFNumberGetValue(appleSlant, kCFNumberFloat64Type, &x) && -1.0 <= x && x <= 1.0 )
{
// Use this key to access the normalized slant angle from the font traits dictionary.
// The value returned is a CFNumberRef representing a float value between -1.0 and 1.0 for normalized slant angle.
// The value or 0.0 corresponds to 0 degree clockwise rotation from the vertical and 1.0 corresponds to 30 degrees clockwise rotation.
apple_font_slant_trait = x;
if (1.0 == apple_font_slant_trait)
style = ON_Font::Style::Italic;
}
}
CFRelease(dict);
}
CFRelease(descriptor);
}
if (-1.0 <= apple_font_weight_trait && apple_font_weight_trait <= 1.0)
SetAppleFontWeightTrait(apple_font_weight_trait);
else
SetFontWeight(weight);
m_font_style = style;
m_font_stretch = stretch;
// Saving point size added January 2018.
const double point_size = (double)CTFontGetSize(apple_font);
m_point_size
= (false == bAnnotationFont && ON_Font::IsValidPointSize(point_size) && point_size < ((double)ON_Font::AnnotationFontApplePointSize))
? point_size
: 0.0; // indicates annotation size (units per em) will be used
m_logfont_charset = ON_Font::WindowsConstants::logfont_default_charset;
// do this again because some of the above calls can modify description
m_loc_postscript_name = postscript_name;
m_en_postscript_name = m_loc_postscript_name;
m_loc_family_name = family_name;
m_en_family_name = m_loc_family_name;
m_loc_face_name = face_name;
m_en_face_name = m_loc_face_name;
m_loc_windows_logfont_name = windows_logfont_name;
m_en_windows_logfont_name = m_loc_windows_logfont_name;
m_panose1 = ON_Font::AppleCTFontPANOSE1(apple_font);
SetFontOrigin(ON_Font::Origin::AppleFont);
if (-1.0 <= apple_font_weight_trait && apple_font_weight_trait <= 1.0)
m_apple_font_weight_trait = apple_font_weight_trait;
if (-1.0 <= apple_font_width_trait && apple_font_width_trait <= 1.0)
m_apple_font_width_trait = apple_font_width_trait;
else
m_apple_font_width_trait = ON_UNSET_VALUE;
}
return rc;
}
const ON_wString ON_Font::AppleCTFontPostScriptName(
CTFontRef apple_font
)
{
if (nullptr == apple_font)
return ON_wString::EmptyString;
CFStringRef applePostScriptName = CTFontCopyPostScriptName(apple_font);
ON_wString postscript_name(applePostScriptName);
if (nullptr != applePostScriptName)
CFRelease(applePostScriptName);
postscript_name.TrimLeftAndRight();
return postscript_name;
}
const ON_wString ON_Font::AppleCTFontFamilyName(
CTFontRef apple_font
)
{
CFStringRef appleFamilyName = CTFontCopyFamilyName(apple_font);
ON_wString family_name(appleFamilyName);
if (nullptr != appleFamilyName)
CFRelease(appleFamilyName);
family_name.TrimLeftAndRight();
return family_name;
}
const ON_wString ON_Font::AppleCTFontDisplayName(
CTFontRef apple_font
)
{
CFStringRef appleDisplayName = CTFontCopyDisplayName(apple_font);
ON_wString display_name(appleDisplayName);
if (nullptr != appleDisplayName)
CFRelease(appleDisplayName);
display_name.TrimLeftAndRight();
return display_name;
}
static const ON_wString Internal_FaceNameFromAppleDisplayAndFamilyName(
const ON_wString& appleFontDisplayName,
const ON_wString& appleFontFamilyName
)
{
if ( appleFontDisplayName.IsEmpty() || appleFontFamilyName.IsEmpty())
return ON_wString::EmptyString;
const wchar_t* family_and_face_str = static_cast<const wchar_t*>(appleFontDisplayName);
const wchar_t* family_str = static_cast<const wchar_t*>(appleFontFamilyName);
if (nullptr == family_and_face_str || nullptr == family_str)
return ON_wString::EmptyString;
// It is not always the case that the beginning of
// appleFontDisplayName exactly matches
// appleFontFamilyName.
// We need to ignore spaces and hyphens.
for (;;)
{
wchar_t a = *family_str;
while (ON_wString::Space == a || ON_wString::HyphenMinus == a)
a = *(++family_str);
wchar_t b = *family_and_face_str;
while (ON_wString::Space == b || ON_wString::HyphenMinus == b)
b = *(++family_and_face_str);
if (0 == a || 0 == b)
break;
a = ON_wString::MapCharacterOrdinal(ON_StringMapOrdinalType::MinimumOrdinal,a);
b = ON_wString::MapCharacterOrdinal(ON_StringMapOrdinalType::MinimumOrdinal,b);
if (a != b)
break;
family_str++;
family_and_face_str++;
}
for(wchar_t c = *family_and_face_str; ON_wString::Space == c || ON_wString::HyphenMinus == c; c = *family_and_face_str )
++family_and_face_str;
ON_wString face_name(family_and_face_str);
face_name.TrimRight();
if ( face_name.IsEmpty())
{
// This is what Apple MacOS uses in FontBook and other font selection UI.
// https://developer.apple.com/documentation/appkit/nsfontmanager/1462316-availablemembersoffontfamily?language=objc
// A more complicated approach that yields the same results is:
// https://developer.apple.com/documentation/coretext/1511240-ctfontcopyname?language=objc
// using kCTFontSubFamilyNameKey This retrieves the TrueType "subfamily" font name
// TrueType names [1]=family [2]=subfamily (which we call "face"), [6] = postscript.
// Reference: Section 6 of https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=IWS-Chapter08
//
// None of the approaches above is localized, so a hard coded English word works as
// well as using the Apple SDK.
face_name = L"Regular";
}
return face_name;
}
const ON_wString ON_Font::AppleCTFontFaceName(
CTFontRef apple_font
)
{
for(;;)
{
if (nullptr == apple_font)
break;
for(;;)
{
CFStringRef appleStyleName = CTFontCopyName(apple_font,kCTFontStyleNameKey);
if (nullptr == appleStyleName)
break;
ON_wString style_name(appleStyleName);
CFRelease(appleStyleName);
style_name.TrimRight();
if ( style_name.IsEmpty() )
break;
return style_name;
}
// Fallback - In Sep 12, 2018 tests of 603 faces, the no longer needed with the swtich to CTFont
// Leaving this here to handle unknown buggy fonts.
const ON_wString family_and_face_name = ON_Font::AppleCTFontDisplayName(apple_font);
const ON_wString family_name = ON_Font::AppleCTFontFamilyName(apple_font);
const ON_wString hack_style_name = Internal_FaceNameFromAppleDisplayAndFamilyName(family_and_face_name,family_name);
return hack_style_name;
}
return ON_wString::EmptyString;
}
CTFontRef ON_Font::AppleCTFont(bool& bIsSubstituteFont) const
{
// Using PointSize() added January 2018.
bIsSubstituteFont = false;
const double pointSize
= ON_Font::IsValidPointSize(m_point_size)
? m_point_size
: 0.0;
return ON_Font::AppleCTFont(pointSize, bIsSubstituteFont);
}
CTFontRef ON_Font::AppleCTFont(
const wchar_t* postscript_name,
double pointSize,
bool& bIsSubstituteFont
)
{
bIsSubstituteFont = false;
ON_wString local_name(postscript_name);
local_name.TrimLeftAndRight();
if (local_name.IsEmpty())
return nullptr;
const bool bHavePointSize = ( pointSize > 0.0 );
const CGFloat size = (CGFloat)(bHavePointSize ? pointSize : 1000.0 );
const CGAffineTransform *matrix = nullptr;
CFStringRef appleName = local_name.ToAppleCFString();
if (nullptr == appleName)
return nullptr;
CTFontRef appleFont = CTFontCreateWithName(appleName, size, matrix);
CFRelease(appleName);
if (nullptr == appleFont)
return nullptr;
if (false == bHavePointSize)
appleFont = ON_Font::AppleCTFontSetSize(appleFont, 0.0, true);
if (nullptr != appleFont)
{
const ON_wString ctfont_postscript_name = ON_Font::AppleCTFontPostScriptName(appleFont);
if (false == ON_wString::EqualOrdinal(ctfont_postscript_name, local_name, true))
bIsSubstituteFont = true;
}
return appleFont;
}
CTFontRef ON_Font::AppleCTFont(double pointSize, bool& bIsSubstituteFont) const
{
if (this->IsManagedSubstitutedFont())
{
// March 2021 Dale Lear
// Fixes RH-62974 on Mac platform
const ON_Font* sub =this->SubstituteFont();
if (nullptr != sub &&sub->IsInstalledFont())
{
if (false == sub->IsManagedSubstitutedFont())
{
CTFontRef subref = sub->AppleCTFont( pointSize, bIsSubstituteFont);
bIsSubstituteFont = true;
return subref;
}
}
}
const bool bHavePointSize = ( pointSize > 0.0 );
const CGFloat size = (CGFloat)(bHavePointSize ? pointSize : 1000.0 );
CTFontRef appleCTFont = nullptr;
ON_wString appleCTFontPostscriptName;
bIsSubstituteFont = false;
const ON_Font* installed_font = nullptr;
bool bInstalledFontIsSubstitute = false;
ON_wString tested_names[7];
for(int name_dex = 0; name_dex < 7; name_dex++)
{
ON_wString name;
if ( 2 == name_dex)
{
installed_font = InstalledFont(true);
if (nullptr == installed_font)
{
bInstalledFontIsSubstitute = true;
installed_font = &ON_Font::Default;
}
}
switch (name_dex)
{
case 0:
name = m_loc_postscript_name;
break;
case 1:
name = m_en_postscript_name;
break;
case 2:
name = installed_font->m_loc_postscript_name;
break;
case 3:
name = installed_font->m_en_postscript_name;
break;
case 4:
name = installed_font->m_loc_family_name;
break;
case 5:
name = installed_font->m_en_family_name;
break;
case 6:
name = installed_font->Description();
break;
default:
break;
}
if ( name.IsNotEmpty() )
{
for ( int i = 0; i < name_dex; i++ )
{
if ( ON_wString::EqualOrdinal(name,tested_names[i],true) )
{
name = ON_wString::EmptyString;
break;
}
}
}
if ( name.IsEmpty() )
continue;
tested_names[name_dex] = name;
bool bCandidateIsSubstitute = false;
CTFontRef candidate = ON_Font::AppleCTFont(name, size, bCandidateIsSubstitute);
if (nullptr == candidate)
continue;
const ON_wString candidate_name = ON_Font::AppleCTFontPostScriptName(candidate);
if (nullptr == appleCTFont || ON_wString::EqualOrdinal(name,candidate_name,true))
{
if (nullptr != appleCTFont)
CFRelease(appleCTFont);
appleCTFont = candidate;
appleCTFontPostscriptName = candidate_name;
bIsSubstituteFont = bCandidateIsSubstitute || bInstalledFontIsSubstitute;
if (ON_wString::EqualOrdinal(name, appleCTFontPostscriptName, true))
break;
}
else
CFRelease(candidate);
}
if ( nullptr != appleCTFont && false == bHavePointSize)
{
// make sure size = font_units_per_em
appleCTFont = ON_Font::AppleCTFontSetSize(appleCTFont, 0.0, true);
}
return appleCTFont;
}
static unsigned int Internal_AppleUnitsPerEm( CTFontRef appleFont, double* pointSizeToUPM )
{
unsigned int font_design_units_per_M = CTFontGetUnitsPerEm(appleFont);
if ( font_design_units_per_M <= 0 )
{
ON_ERROR("CTFontGetUnitsPerEm(appleFont) returned zero.");
font_design_units_per_M = 1000;
}
if (nullptr != pointSizeToUPM)
{
const double point_size = (double)CTFontGetSize(appleFont);
const double UPM = font_design_units_per_M;
// This scaling approach recovers correct values in font design units
// when compared with values from DWrite and FreeType.
*pointSizeToUPM = (point_size > 0.0 && fabs(point_size-UPM) > 0.001 ) ? (UPM/point_size) : 1.0;
}
return font_design_units_per_M;
}
void ON_AppleFontGetFontMetrics(
const ON_Font* font,
ON_FontMetrics& font_metrics
)
{
for (;;)
{
if (nullptr == font)
break;
font = font->ManagedFont();
if (nullptr == font)
break;
bool bIsSubstituteFont = false;
CTFontRef appleFont = font->AppleCTFont(bIsSubstituteFont);
if (nullptr == appleFont)
break;
double pointSizeToUPM = 1.0;
const unsigned int font_design_units_per_M = Internal_AppleUnitsPerEm(appleFont,&pointSizeToUPM);
const double UPM = font_design_units_per_M;
const double leading = pointSizeToUPM*((double)CTFontGetLeading(appleFont));
const double ascent = pointSizeToUPM * ((double)CTFontGetAscent(appleFont));
const CGFloat ct_descent_sign = -1;
const double descent = pointSizeToUPM * ((double)(ct_descent_sign*CTFontGetDescent(appleFont)));
const double line_space1 = ascent - descent + leading;
const double line_space2
= (1.0 != pointSizeToUPM)
? (pointSizeToUPM*((double)(CTFontGetAscent(appleFont)-(ct_descent_sign*CTFontGetDescent(appleFont))+CTFontGetLeading(appleFont))))
: line_space1;
const double line_space = (line_space2>=line_space1) ? line_space2 : line_space1;
const double ascent_of_x = pointSizeToUPM*((double)CTFontGetXHeight(appleFont));
const double ascent_of_capital = pointSizeToUPM*((double)CTFontGetCapHeight(appleFont));
const double underscore_position = pointSizeToUPM*((double)CTFontGetUnderlinePosition(appleFont));
const double underscore_thickness = pointSizeToUPM*((double)CTFontGetUnderlineThickness(appleFont));
CFRelease(appleFont);
font_metrics = ON_FontMetrics::Unset;
font_metrics.SetHeights(
ascent,
descent,
UPM,
line_space
);
if (false == font_metrics.AscentDescentAndUPMAreValid())
break;
font_metrics.SetAscentOfx(ascent_of_x);
font_metrics.SetAscentOfCapital(ascent_of_capital);
font_metrics.SetUnderscore(underscore_position,underscore_thickness);
// Have to fake strikeout settings - not in NSFont API?
//int strikeout_position = ...;
//int strikeout_thickness = ...;
double h = (ascent_of_capital > 0) ? ascent_of_capital : ascent;
if (h > 0.0)
{
font_metrics.SetStrikeout(
(0.5*h),
(0.5*underscore_thickness)
);
}
if (font_metrics.AscentDescentAndUPMAreValid())
return;
break;
}
font_metrics = ON_FontMetrics::Unset;
return;
}
static int Internal_IntFloor(double x)
{
double f = floor(x);
double c = ceil(x);
if ( (1.0 == (c-f)) && ((c-x) <= 1.0e-4 ))
return (int)c;
return (int)f;
}
static int Internal_IntCeil(double x)
{
double f = floor(x);
double c = ceil(x);
if ( (1.0 == (c-f)) && ((x-f) <= 1.0e-4 ))
return (int)f;
return (int)c;
}
bool ON_AppleFontGetGlyphMetrics(
CTFontRef appleFont,
unsigned int glyphIndex,
class ON_TextBox& glyph_metrics
)
{
glyph_metrics = ON_TextBox::Unset;
for(;;)
{
if (0 == glyphIndex)
break;
if (nullptr == appleFont)
break;
const CGGlyph glyphs[2] = {(CGGlyph)glyphIndex,0};
if ( ((unsigned int)glyphs[0]) != glyphIndex )
break; // CGGlyph is a short and glyphIndex is too large.
const CFIndex count = 1;
CGRect boundingRects[2];
CGRect glyphRect = CTFontGetBoundingRectsForGlyphs(appleFont, kCTFontOrientationHorizontal, glyphs, boundingRects, count);
if ( boundingRects[0].size.height != 0.0f || boundingRects[0].size.width != 0.0f )
glyphRect = boundingRects[0];
CGSize advances[2];
double glyphAdvanceX = CTFontGetAdvancesForGlyphs(appleFont, kCTFontOrientationHorizontal, glyphs, advances, count);
if (advances[0].width != 0.0f)
glyphAdvanceX = advances[0].width;
double glyphAdvanceY = CTFontGetAdvancesForGlyphs(appleFont, kCTFontOrientationVertical, glyphs, advances, count);
if (advances[0].width != 0.0f)
glyphAdvanceY = advances[0].width; // width is correct - the vertical advance is returned in width.
//CGSize translations[2];
//CTFontGetVerticalTranslationsForGlyphs(appleFont, glyphs, translations, count);
double pointSizeToUPM = 1.0;
const unsigned int font_design_units_per_M = Internal_AppleUnitsPerEm(appleFont,&pointSizeToUPM);
if (0 == font_design_units_per_M)
pointSizeToUPM = 1.0;
glyph_metrics.m_bbmin.i = Internal_IntFloor(pointSizeToUPM*glyphRect.origin.x);
glyph_metrics.m_bbmin.j = Internal_IntFloor(pointSizeToUPM*glyphRect.origin.y);
glyph_metrics.m_bbmax.i = Internal_IntCeil(pointSizeToUPM*(glyphRect.origin.x + glyphRect.size.width));
glyph_metrics.m_bbmax.j = Internal_IntCeil(pointSizeToUPM*(glyphRect.origin.y + glyphRect.size.height));
glyph_metrics.m_advance.i = Internal_IntCeil(pointSizeToUPM*glyphAdvanceX);
glyph_metrics.m_advance.j = Internal_IntCeil(pointSizeToUPM*glyphAdvanceY);
return true;
}
return false;
}
class ON_AppleGlyphOutlineAccumlator : public ON_OutlineAccumulator
{
public:
ON_AppleGlyphOutlineAccumlator() = default;
~ON_AppleGlyphOutlineAccumlator() = default;
static void Internal_PathApplierFunction(
void *info,
const CGPathElement *element
);
private:
static const ON_2fPoint Internal_Point(CGPoint p);
private:
ON_AppleGlyphOutlineAccumlator(const ON_AppleGlyphOutlineAccumlator&) = delete;
ON_AppleGlyphOutlineAccumlator& operator=(const ON_AppleGlyphOutlineAccumlator&) = delete;
};
const ON_2fPoint ON_AppleGlyphOutlineAccumlator::Internal_Point(CGPoint p)
{
return ON_2fPoint((float)p.x, (float)p.y);
}
void ON_AppleGlyphOutlineAccumlator::Internal_PathApplierFunction(
void *info,
const CGPathElement *element
)
{
if (nullptr == info || nullptr == element)
return;
ON_AppleGlyphOutlineAccumlator* acc = (ON_AppleGlyphOutlineAccumlator*)info;
switch (element->type)
{
case kCGPathElementMoveToPoint:
if (nullptr != element->points)
{
acc->BeginFigure(
ON_OutlineFigurePoint::Type::BeginFigureUnknown,
ON_AppleGlyphOutlineAccumlator::Internal_Point(element->points[0])
);
}
break;
case kCGPathElementAddLineToPoint:
if (nullptr != element->points)
{
acc->AppendLine(
ON_AppleGlyphOutlineAccumlator::Internal_Point(element->points[0])
);
}
break;
case kCGPathElementAddQuadCurveToPoint:
if (nullptr != element->points)
{
acc->AppendQuadraticBezier(
ON_AppleGlyphOutlineAccumlator::Internal_Point(element->points[0]),
ON_AppleGlyphOutlineAccumlator::Internal_Point(element->points[1])
);
}
break;
case kCGPathElementAddCurveToPoint: // cubic bezier
if (nullptr != element->points)
{
acc->AppendCubicBezier(
ON_AppleGlyphOutlineAccumlator::Internal_Point(element->points[0]),
ON_AppleGlyphOutlineAccumlator::Internal_Point(element->points[1]),
ON_AppleGlyphOutlineAccumlator::Internal_Point(element->points[2])
);
}
break;
case kCGPathElementCloseSubpath:
acc->EndFigure(
ON_OutlineFigurePoint::Type::EndFigureClosed
);
break;
default:
break;
};
}
bool ON_AppleFontGetGlyphOutline(
CTFontRef appleFont,
unsigned int glyphIndex,
ON_OutlineFigure::Type figure_type,
class ON_Outline& outline
)
{
outline = ON_Outline::Unset;
if (nullptr == appleFont)
return false;
if ( glyphIndex <= 0 )
return false;
const CGGlyph appleGlyphIndex = (CGGlyph)glyphIndex;
if (((unsigned int)appleGlyphIndex) != glyphIndex)
{
// glyphIndex too large - currently CGGlyph is an unsigned short.
return false;
}
CGPathRef applePath = CTFontCreatePathForGlyph(appleFont, appleGlyphIndex, nullptr);
// NOTE WELL: applePath = nullptr for space, tab and so on.
// We still need to get the advance for these code points.
// NO // if (nullptr == applePath)
// NO // return false;
unsigned int font_design_units_per_M = CTFontGetUnitsPerEm(appleFont);
const CGFloat point_size = CTFontGetSize(appleFont);
if ( false == (point_size== (CGFloat)font_design_units_per_M))
{
ON_WARNING("CTFontGetSize() != CTFontGetUnitsPerEm()");
font_design_units_per_M =(unsigned int)ceil(point_size);
}
ON_AppleGlyphOutlineAccumlator acc;
acc.BeginGlyphOutline(font_design_units_per_M, figure_type, &outline);
if ( nullptr != applePath)
{
CGPathApply(applePath, &acc, ON_AppleGlyphOutlineAccumlator::Internal_PathApplierFunction);
CGPathRelease(applePath);
}
acc.EndOutline();
// Add Glyph metrics to outline
ON_TextBox glyph_metrics_in_font_design_units;
bool bHaveGlyphMetrics = ON_AppleFontGetGlyphMetrics(appleFont, appleGlyphIndex, glyph_metrics_in_font_design_units);
if ( bHaveGlyphMetrics )
outline.SetGlyphMetrics(glyph_metrics_in_font_design_units);
else
outline.SetGlyphMetrics(ON_TextBox::Unset);
return true;
}
unsigned int ON_AppleFontGlyphIndex(
CTFontRef appleFont,
unsigned int unicode_code_point
)
{
unsigned int glyphIndex = 0;
for (;;)
{
if (false == ON_IsValidUnicodeCodePoint(unicode_code_point))
break;
ON__UINT16 utf16[3] = {};
const int utf16_count = ON_EncodeUTF16(unicode_code_point, utf16);
if (utf16_count < 1 || utf16_count > 2)
break;
// UniChar is used for Apple UTF-16 endcodings
const UniChar apple_utf16[3] = { (UniChar)utf16[0], (UniChar)utf16[1], (UniChar)0 };
// glyphs[] includes an element for the NULL terminator and an extra element
// in case CTFontGetGlyphsForCharacters is buggy when handling surrogate pairs.
CGGlyph glyphs[3] = {};
if (false == CTFontGetGlyphsForCharacters(appleFont, apple_utf16, glyphs, (NSInteger)utf16_count))
break;
if (0 == glyphs[0])
break;
if (0 != glyphs[1] || 0 != glyphs[2])
break;
glyphIndex = (unsigned int)glyphs[0];
break;
}
return glyphIndex;
}
unsigned int ON_AppleFontGetGlyphMetrics(
const ON_FontGlyph* glyph,
ON_TextBox& glyph_metrics
)
{
glyph_metrics = ON_TextBox::Unset;
if (nullptr == glyph)
return 0;
if (false == glyph->CodePointIsSet())
return 0;
const ON_Font* font = glyph->Font();
if (nullptr == font)
return 0;
bool bIsSubstituteFont = false;
CTFontRef appleFont = font->AppleCTFont(bIsSubstituteFont);
if (nullptr == appleFont)
return 0;
const unsigned int glyph_index = ON_AppleFontGlyphIndex(appleFont, glyph->CodePoint());
if (0 == glyph_index)
{
CFRelease(appleFont);
return 0;
}
ON_AppleFontGetGlyphMetrics(appleFont, glyph_index, glyph_metrics);
CFRelease(appleFont);
return glyph_index;
}
bool ON_AppleFontGetGlyphOutline(
const ON_FontGlyph* glyph,
ON_OutlineFigure::Type figure_type,
class ON_Outline& outline
)
{
outline = ON_Outline::Unset;
if (nullptr == glyph)
return false;
if (false == glyph->CodePointIsSet())
return false;
const unsigned int glyphIndex = glyph->FontGlyphIndex();
if (0==glyphIndex)
return false;
const ON_Font* font = glyph->Font();
if (nullptr == font)
return false;
if (ON_OutlineFigure::Type::Unset == figure_type)
{
ON_OutlineFigure::Type font_figure_type = font->OutlineFigureType();
if (ON_OutlineFigure::Type::Unset != font_figure_type)
{
figure_type = font_figure_type;
}
}
#if defined(OPENNURBS_FREETYPE_SUPPORT)
if (
ON_OutlineFigure::Type::SingleStroke == figure_type
// NO // ON_OutlineFigure::Type::DoubleStroke == font_figure_type
)
{
// Apple's Mac OS CTFontCreatePathForGlyph() ignores subfigures that
// are lines. These are common in single stroke fonts. Typical results
// with singel stroke files are things like the R is missing it's right leg,
// h is missing is left side line, and so on.
// Freetype has many bugs, but it can parse some common single stroke files.
// Freetype is basically a file reading utility that can parse outline
// information in older formats of font files. It fails on newer formats
// and it also commonly fails to correctly map UNICODE code points to
// glyph indices. Freetype should only be used as a last resort.
const bool freetype_rc = ON_FreeTypeGetGlyphOutline(
glyph,
glyphIndex,
figure_type,
outline
);
if (freetype_rc)
return true;
}
#endif
bool bIsSubstituteFont = false;
CTFontRef appleFont = font->AppleCTFont(bIsSubstituteFont);
if (nullptr == appleFont)
return false;
const bool rc = ON_AppleFontGetGlyphOutline(
appleFont,
glyphIndex,
figure_type,
outline
);
CFRelease(appleFont);
return rc;
}
void ON_Font::DumpCTFont(
CTFontRef apple_font,
ON_TextLog& text_log
)
{
if (nullptr == apple_font)
{
text_log.Print("CTFont = nullptr\n");
return;
}
text_log.Print("CTFont\n");
text_log.PushIndent();
const ON_wString postscript_name = ON_Font::AppleCTFontPostScriptName(apple_font);
text_log.Print(L"CTFont PostScriptName: \"%ls\"\n",static_cast<const wchar_t*>(postscript_name));
const ON_wString display_name = ON_Font::AppleCTFontDisplayName(apple_font);
text_log.Print(L"CTFont DisplayName: \"%ls\"\n",static_cast<const wchar_t*>(display_name));
const ON_wString family_name = ON_Font::AppleCTFontFamilyName(apple_font);
text_log.Print(L"CTFont FamilyName: \"%ls\"\n",static_cast<const wchar_t*>(family_name));
// Apple NSFont and MacOS do not have "face names" as a font attribute.
// This is the "typeface" name shown in Apple FontBook
const ON_wString fake_face_name = ON_Font::AppleCTFontFaceName(apple_font);
text_log.Print(L"CTFont FontBook typeface: \"%ls\"\n",static_cast<const wchar_t*>(fake_face_name));
const unsigned int UPM = ON_Font::AppleCTFontUnitsPerEm(apple_font);
text_log.Print("CTFont UnitsPerEm: %u\n",UPM);
const double point_size = (double)CTFontGetSize(apple_font);
text_log.Print("CTFont Size: %g points\n",point_size);
#if 0
text_log.Print(L"CTFont Names:\n");
text_log.PushIndent();
CFStringRef keys[] =
{
kCTFontCopyrightNameKey,
kCTFontFamilyNameKey,
kCTFontSubFamilyNameKey,
kCTFontStyleNameKey,
kCTFontUniqueNameKey, // Apple docs say this name is not actually unique
kCTFontFullNameKey,
kCTFontVersionNameKey,
kCTFontPostScriptNameKey,
kCTFontTrademarkNameKey,
kCTFontManufacturerNameKey,
kCTFontDesignerNameKey,
kCTFontDescriptionNameKey,
kCTFontVendorURLNameKey,
kCTFontDesignerURLNameKey,
kCTFontLicenseNameKey,
kCTFontLicenseURLNameKey,
kCTFontSampleTextNameKey,
kCTFontPostScriptCIDNameKey
};
const size_t key_count = sizeof(keys)/sizeof(keys[0]);
text_log.Print(L"CTFont Names:\n");
text_log.PushIndent();
for (size_t i = 0; i < key_count; i++)
{
CFStringRef appleName = CTFontCopyName(apple_font,keys[i]);
const ON_wString key(keys[i]);
const ON_wString name(appleName);
if (nullptr != appleName)
CFRelease(appleName);
if ( name.IsNotEmpty() )
{
text_log.Print(
L"\"%ls\" = CTFontCopyName(...,\"%ls\")\n",
static_cast<const wchar_t*>(name),
static_cast<const wchar_t*>(key)
);
}
}
text_log.PopIndent();
#endif
text_log.PopIndent();
}
#endif