// // 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 . // //////////////////////////////////////////////////////////////// #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 #include "opennurbs_internal_glyph.h" #include "opennurbs_win_dwrite.h" #include "opennurbs_apple_nsfont.h" // Do not put this class in a public header file or SDK. // It will be modified at unexpected times in unexpected ways. Any code outside of this file // that uses the implementation will crash unexpectedly. class ON_FontListImpl { public: ON_FontListImpl() = default; ~ON_FontListImpl() = default; private: ON_FontListImpl(const ON_FontListImpl&) = delete; ON_FontListImpl& operator=(const ON_FontListImpl&) = delete; public: // List of fonts sorted by FontCharacteristics hash // (Recently added fonts may be in m_unsorted[]) mutable ON_SimpleArray< const ON_Font* > m_by_font_characteristics_hash; // List of fonts sorted by PostScript name // (Recently added fonts may be in m_unsorted[]) mutable ON_SimpleArray< const ON_Font* > m_by_postscript_name; // List of fonts sorted by Windows LOGFONT.lfFaceName name // (Recently added fonts may be in m_unsorted[]) mutable ON_SimpleArray< const ON_Font* > m_by_windows_logfont_name; // List of fonts sorted by Family name, then FaceName // (Recently added fonts may be in m_unsorted[]) mutable ON_SimpleArray< const ON_Font* > m_by_family_name; // List of fonts sorted by English PostScript name // Only fonts with m_en_postscript_name != m_loc_postscript_name are included here // (Recently added fonts may be in m_unsorted[]) mutable ON_SimpleArray< const ON_Font* > m_by_english_postscript_name; // List of fonts sorted by English Windows LOGFONT.lfFaceName name // Only fonts with m_en_windows_logfont_name != m_loc_windows_logfont_name are included here // (Recently added fonts may be in m_unsorted[]) mutable ON_SimpleArray< const ON_Font* > m_by_english_windows_logfont_name; // List of fonts sorted by English Family name, then English FaceName // Only fonts with m_en_family_name != m_loc_family_name are included here // (Recently added fonts may be in m_unsorted[]) mutable ON_SimpleArray< const ON_Font* > m_by_english_family_name; mutable ON_SimpleArray< const ON_Font* > m_by_quartet_name; }; ON_PANOSE1::FamilyKind ON_PANOSE1::FamilyKindFromUnsigned( unsigned int unsigned_panose_family_kind ) { switch (unsigned_panose_family_kind) { ON_ENUM_FROM_UNSIGNED_CASE(ON_PANOSE1::FamilyKind::Any); ON_ENUM_FROM_UNSIGNED_CASE(ON_PANOSE1::FamilyKind::NoFit); ON_ENUM_FROM_UNSIGNED_CASE(ON_PANOSE1::FamilyKind::LatinText); ON_ENUM_FROM_UNSIGNED_CASE(ON_PANOSE1::FamilyKind::LatinScript); ON_ENUM_FROM_UNSIGNED_CASE(ON_PANOSE1::FamilyKind::LatinDecorative); ON_ENUM_FROM_UNSIGNED_CASE(ON_PANOSE1::FamilyKind::LatinSymbol); default: break; } ON_ERROR("unsigned_pannos1_classification is not valid"); return ON_PANOSE1::FamilyKind::Any; } const wchar_t* ON_PANOSE1::FamilyKindToWideString( ON_PANOSE1::FamilyKind family_kind ) { const wchar_t* s; switch (family_kind) { case ON_PANOSE1::FamilyKind::Any: s = L"Any"; break; case ON_PANOSE1::FamilyKind::NoFit: s = L"No fit"; break; case ON_PANOSE1::FamilyKind::LatinText: s = L"Latin Text"; break; case ON_PANOSE1::FamilyKind::LatinScript: s = L"Latin Script"; break; case ON_PANOSE1::FamilyKind::LatinDecorative: s = L"Latin Decorative"; break; case ON_PANOSE1::FamilyKind::LatinSymbol: s = L"Latin Symbol"; break; default: s = L""; break; } return s; } bool ON_PANOSE1::IsZero() const { const ON__UINT8* p = TenBytes(); for (int i = 0; i < 10; i++) { if (0 != p[i]) return false; } return true; } bool ON_PANOSE1::IsZeroOrOne() const { const ON__UINT8* p = TenBytes(); for (int i = 0; i < 10; i++) { if (p[i] > 1) return false; } return true; } bool ON_PANOSE1::IsSet() const { return IsZeroOrOne() ? false : true; } ON_PANOSE1::FamilyKind ON_PANOSE1::PANOSE1FamilyKind() const { return m_family_kind; } const ON__UINT8* ON_PANOSE1::TenBytes() const { return (const ON__UINT8*)(&m_family_kind); } void ON_PANOSE1::SetTenBytes(const ON__UINT8* panose1_ten_bytes) { SetNineBytes(ON_PANOSE1::FamilyKindFromUnsigned(panose1_ten_bytes[0]), panose1_ten_bytes + 1); } void ON_PANOSE1::SetNineBytes( ON_PANOSE1::FamilyKind family_kind, const ON__UINT8* panose1_properties_bytes ) { const ON__UINT8 b0 = static_cast(family_kind); if ( b0 > 5) { *this = ON_PANOSE1::Zero; } else { m_family_kind = family_kind; m_prop1 = panose1_properties_bytes[0]; m_prop2 = panose1_properties_bytes[1]; m_prop3 = panose1_properties_bytes[2]; m_prop4 = panose1_properties_bytes[3]; m_prop5 = panose1_properties_bytes[4]; m_prop6 = panose1_properties_bytes[5]; m_prop7 = panose1_properties_bytes[6]; m_prop8 = panose1_properties_bytes[7]; m_prop9 = panose1_properties_bytes[8]; } } void ON_PANOSE1::Dump( class ON_TextLog& text_log ) const { if (IsZero()) { text_log.Print(L"PANOSE1::Zero\n"); } else { ON_wString family = FamilyKindToWideString(m_family_kind); if (family.IsEmpty()) family = ON_wString::FormatToString(L"%u", (unsigned int)m_family_kind); text_log.Print(L"PANOSE1: %ls (%u, %u, %u, %u, %u, %u, %u, %u, %u)\n", static_cast(family), (unsigned int)m_prop1, (unsigned int)m_prop2, (unsigned int)m_prop3, (unsigned int)m_prop4, (unsigned int)m_prop5, (unsigned int)m_prop6, (unsigned int)m_prop7, (unsigned int)m_prop8, (unsigned int)m_prop9 ); } } bool ON_PANOSE1::Write( class ON_BinaryArchive& archive ) const { if (false == archive.BeginWrite3dmAnonymousChunk(1)) return false; bool rc = false; for(;;) { if (!archive.WriteByte(1, &m_family_kind)) break; if (!archive.WriteByte(1, &m_prop1)) break; if (!archive.WriteByte(1, &m_prop2)) break; if (!archive.WriteByte(1, &m_prop3)) break; if (!archive.WriteByte(1, &m_prop4)) break; if (!archive.WriteByte(1, &m_prop5)) break; if (!archive.WriteByte(1, &m_prop6)) break; if (!archive.WriteByte(1, &m_prop7)) break; if (!archive.WriteByte(1, &m_prop8)) break; if (!archive.WriteByte(1, &m_prop9)) break; rc = true; break; } if (!archive.EndWrite3dmChunk()) rc = false; return rc; } bool ON_PANOSE1::Read( class ON_BinaryArchive& archive ) { int version = 0; if (false == archive.BeginRead3dmAnonymousChunk(&version)) return false; bool rc = false; for (;;) { if (version < 1) break; if (!archive.ReadByte(1, &m_family_kind)) break; if (!archive.ReadByte(1, &m_prop1)) break; if (!archive.ReadByte(1, &m_prop2)) break; if (!archive.ReadByte(1, &m_prop3)) break; if (!archive.ReadByte(1, &m_prop4)) break; if (!archive.ReadByte(1, &m_prop5)) break; if (!archive.ReadByte(1, &m_prop6)) break; if (!archive.ReadByte(1, &m_prop7)) break; if (!archive.ReadByte(1, &m_prop8)) break; if (!archive.ReadByte(1, &m_prop9)) break; if (version <= 1) { rc = true; break; } rc = true; break; } if (!archive.EndRead3dmChunk()) rc = false; return rc; } int ON_FontMetrics::Ascent() const { return m_ascent; } int ON_FontMetrics::Descent() const { return m_descent; } int ON_FontMetrics::LineSpace() const { return m_line_space; } int ON_FontMetrics::UPM() const { return m_UPM; } int ON_FontMetrics::AscentOfI() const { return AscentOfCapital(); } int ON_FontMetrics::AscentOfCapital() const { return (int)m_ascent_of_capital; } int ON_FontMetrics::AscentOfx() const { return (int)m_ascent_of_x; } double ON_FontMetrics::GlyphScale(double text_height) const { // Please consult Dale Lear and the ON_FontGlyph // bounding box, outline, and advance calculation code // before making any modifications to this function. const double y = (double)AscentOfCapital(); return (text_height > 0.0 && text_height < ON_UNSET_POSITIVE_FLOAT && y > 0.0 && y < ON_UNSET_POSITIVE_FLOAT ) ? (text_height / y) : 1.0; } int ON_FontMetrics::StrikeoutThickness() const { return m_strikeout_thickness; } int ON_FontMetrics::StrikeoutPosition() const { return m_strikeout_position; } int ON_FontMetrics::UnderscoreThickness() const { return m_underscore_thickness; } int ON_FontMetrics::UnderscorePosition() const { return m_underscore_position; } bool ON_FontMetrics::AscentDescentAndUPMAreValid() const { const int max_valid = 0xFFFF; const int min_valid = -max_valid; // basic validation if (m_UPM <= 0 || m_UPM >= max_valid) return false; if (0 == m_ascent && 0 == m_descent) return false; if (m_ascent <= min_valid || m_ascent >= max_valid) return false; if (m_descent <= min_valid || m_descent >= max_valid) return false; if (m_ascent <= m_descent) return false; return true; } bool ON_FontMetrics::HeightsAreValid() const { // basic validation if (false == AscentDescentAndUPMAreValid() ) return false; // sanity check if (m_line_space < m_ascent - m_descent) return false; if (((int)m_ascent_of_capital) > m_ascent) return false; if (((int)m_ascent_of_x) > m_ascent) return false; return true; } bool ON_FontMetrics::IsSetAndValid() const { return (m_ascent_of_capital > 0 && HeightsAreValid()); } void ON_FontMetrics::SetHeights( int ascent, int descent, int UPM, int line_space ) { if (ON_UNSET_INT_INDEX < descent && descent < ascent && ascent < -ON_UNSET_INT_INDEX) { m_ascent = ascent; m_descent = descent; } else { m_ascent = 0; m_descent = 0; } m_UPM = (UPM > 0 && UPM < -ON_UNSET_INT_INDEX) ? UPM : 0; m_line_space = (line_space > 0 && line_space < -ON_UNSET_INT_INDEX)? line_space : 0; if ( m_line_space > 0 && m_ascent > 0 && m_descent <= m_ascent && m_line_space < m_ascent - m_descent && m_line_space >= m_ascent - m_descent - 1 ) { // Probably sloppy rounding - just make it work m_line_space = m_ascent - m_descent; } } void ON_FontMetrics::SetAscentOfI( int ascent_of_capital ) { SetAscentOfCapital(ascent_of_capital); } void ON_FontMetrics::SetAscentOfCapital( int ascent_of_capital ) { m_ascent_of_capital = (ascent_of_capital > 0 && ascent_of_capital <= 0xFFFF) ? ((unsigned short)ascent_of_capital) : 0; } void ON_FontMetrics::SetAscentOfx( int ascent_of_x ) { m_ascent_of_x = (ascent_of_x > 0 && ascent_of_x <= 0xFFFF) ? ((unsigned short)ascent_of_x) : 0; } void ON_FontMetrics::SetStrikeout( int strikeout_position, int strikeout_thickness ) { m_strikeout_position = strikeout_position; m_strikeout_thickness = strikeout_thickness; } void ON_FontMetrics::SetUnderscore( int underscore_position, int underscore_thickness ) { m_underscore_position = underscore_position; m_underscore_thickness = underscore_thickness; } static int Internal_FontMetricCeil( double x ) { const double m = (double)0xFFFFFF; if (x >= -m && x <= m) { double i = ceil(x); return (int)(((i - x) > 0.9375) ? (i - 1.0) : i); } return 0; } static int Internal_FontMetricFloor( double x ) { const double m = (double)0xFFFFFF; if (x >= -m && x <= m) { double i = floor(x); return (int)(((x - i) > 0.9375) ? (i + 1.0) : i); } return 0; } static int Internal_FontMetricNearest( double x ) { const double m = (double)0xFFFFFF; if (x >= -m && x <= m) { double i = floor(x); return (int)(((x - i) > 0.5) ? (i + 1.0) : i); } return 0; } void ON_FontMetrics::SetHeights( double ascent, double descent, double UPM, double line_space ) { int iascent = Internal_FontMetricCeil(ascent); int idescent = Internal_FontMetricFloor(descent); int iUPM = Internal_FontMetricCeil(UPM); int iline_space = Internal_FontMetricCeil(line_space); if ( iascent > 0 && idescent <= iascent && iline_space < iascent - idescent && line_space >= (ascent - descent - 1.0) ) { iline_space = iascent - idescent; } SetHeights(iascent, idescent, iUPM, iline_space); } void ON_FontMetrics::SetAscentOfCapital( double ascent_of_capital ) { int iascent_of_capital = Internal_FontMetricCeil(ascent_of_capital); //// Dale Lear Feb 2021 - Huh? The contition is never true unless m_ascent - 1 overflows to a positive number. ////if (m_ascent < 0 && iascent_of_capital > m_ascent && iascent_of_capital <= m_ascent - 1) //// iascent_of_capital = m_ascent; SetAscentOfCapital(iascent_of_capital); } void ON_FontMetrics::SetAscentOfx( double ascent_of_x ) { int iascent_of_x = Internal_FontMetricCeil(ascent_of_x); //// Dale Lear Feb 2021 - Huh? The contition is never true unless m_ascent - 1 overflows to a positive number. ////if (m_ascent < 0 && iascent_of_x > m_ascent && iascent_of_x <= m_ascent - 1) //// iascent_of_x = m_ascent; SetAscentOfx(iascent_of_x); } void ON_FontMetrics::SetStrikeout( double strikeout_position, double strikeout_thickness ) { int istrikeout_thickness = (strikeout_thickness > 0.0) ? Internal_FontMetricCeil(strikeout_thickness) : 0; if (0 == istrikeout_thickness && strikeout_thickness > 0.0) istrikeout_thickness = 1; SetStrikeout(Internal_FontMetricNearest(strikeout_position), istrikeout_thickness); } void ON_FontMetrics::SetUnderscore( double underscore_position, double underscore_thickness ) { int iunderscore_thickness = (underscore_thickness > 0.0) ? Internal_FontMetricCeil(underscore_thickness) : 0; if (0 == iunderscore_thickness && underscore_thickness > 0.0) iunderscore_thickness = 1; SetUnderscore(Internal_FontMetricFloor(underscore_position), iunderscore_thickness); } static int Internal_ScaleInt(double scale, int i) { return (int)((i >= 0) ? ceil(scale*i) : floor(scale*i)); } const ON_FontMetrics ON_FontMetrics::Scale( const ON_FontMetrics& font_metrics, double scale ) { ON_FontMetrics scaled_font_metrics = font_metrics; if (scale > 0.0 && 1.0 != scale) { scaled_font_metrics.m_UPM = Internal_ScaleInt(scale, scaled_font_metrics.m_UPM); scaled_font_metrics.m_ascent = Internal_ScaleInt(scale, scaled_font_metrics.m_ascent); scaled_font_metrics.m_descent = Internal_ScaleInt(scale, scaled_font_metrics.m_descent); scaled_font_metrics.SetAscentOfCapital(Internal_ScaleInt(scale, scaled_font_metrics.m_ascent_of_capital)); scaled_font_metrics.SetAscentOfx(Internal_ScaleInt(scale, scaled_font_metrics.m_ascent_of_x)); if (font_metrics.m_line_space == Internal_ScaleInt(ON_FontMetrics::DefaultLineFeedRatio,font_metrics.AscentOfCapital())) scaled_font_metrics.m_line_space = Internal_ScaleInt(ON_FontMetrics::DefaultLineFeedRatio,scaled_font_metrics.AscentOfCapital()); else scaled_font_metrics.m_line_space = Internal_ScaleInt(scale, scaled_font_metrics.m_line_space); scaled_font_metrics.m_strikeout_thickness = Internal_ScaleInt(scale, scaled_font_metrics.m_strikeout_thickness); scaled_font_metrics.m_strikeout_position = Internal_ScaleInt(scale, scaled_font_metrics.m_strikeout_position); scaled_font_metrics.m_underscore_thickness = Internal_ScaleInt(scale, scaled_font_metrics.m_underscore_thickness); scaled_font_metrics.m_underscore_position = Internal_ScaleInt(scale, scaled_font_metrics.m_underscore_position); } return scaled_font_metrics; } const ON_FontMetrics ON_FontMetrics::Normalize( const ON_FontMetrics& font_metrics ) { if (font_metrics.m_UPM == ON_Font::AnnotationFontCellHeight) return font_metrics; if (font_metrics.m_UPM <= 0) return ON_FontMetrics::Unset; const double scale = ((double)ON_Font::AnnotationFontCellHeight / ((double)font_metrics.m_UPM)); return ON_FontMetrics::Scale(font_metrics, scale); } ON_ManagedFonts::ON_ManagedFonts(ON__UINT_PTR zero) : m_default_font_ptr(zero) , m_managed_fonts(true) , m_installed_fonts(false) {} ON_ManagedFonts::~ON_ManagedFonts() { ON_SimpleArray managed_fonts(m_installed_fonts.m_by_index); managed_fonts.Append(m_managed_fonts.m_by_index.Count(),m_managed_fonts.m_by_index.Array()); m_installed_fonts.Internal_EmptyLists(); m_managed_fonts.Internal_EmptyLists(); // last created first deleted - it shouldn't really matter. for(int i = managed_fonts.Count()-1; i >= 0; i--) { ON_Font* managed_font = const_cast(managed_fonts[i]); if (nullptr == managed_font) continue; if (m_default_font_ptr == (ON__UINT_PTR)managed_font) { // ON_Font::Default is constructed after ON_ManagedFonts::List // and destructed before the destruction of ON_ManagedFonts::List. // This continue prevents calling delete on continue; } if (1 == managed_font->m_runtime_serial_number) { // ON_Font::Default.m_runtime_serial_number = 1 and it is the only instance of a font // with m_runtime_serial_number = 1. // However, the managed_font pointer points to ON_Font::Default, which was destroyed a few miliseconds ago. // See opennurbs_statics.cpp and observe that construction order is // ..., ON_ManagedFonts::List, ON_Font::Unset, ON_Font::Default, ... // and destruction order is // ..., ON_Font::Default, ON_Font::Unset, ON_ManagedFonts::List, ... ON_ERROR("The m_default_font_ptr test above should have detected ON_Font::Default."); continue; } // The reset needs to happen for all fonts, including ON_Font::Default // Otherwise we get unpredictable crashes when closing because the // order in which ON_Font::Default, ON_GlyphMap::GlyphPool and ON_ManagedFonts::List // is not predictable. managed_font->m_font_glyph_cache.reset(); delete managed_font; } } class ON_FontGlyphCache { public: static ON_FontGlyphCache* New(); void SetFontMetrics( const ON_FontMetrics& font_unit_metrics ); #if defined(ON_OS_WINDOWS_GDI) // primary installed font used to get glyphs // When the current (Windows) device has a font that // matches the ON_Font, this is matching installed font. struct IDWriteFont* m_dwrite_font = 0; #endif // See ON_Font.FontMetrics() documentation for a discussion // of normalized and unnormalized metrics and scales. // Both scale values are stored to reduce rounding errors. double m_font_unit_to_normalized_scale = 1.0; double m_normalized_to_font_unit_scale = 1.0; // Font metrics in the units from the system font defintion. // UPM = font design cell height (often 1000 for PostScript, 2014 for TrueType, ...) ON_FontMetrics m_font_unit_metrics; // Font metrics normalized so UPM = ON_Font::Constants::AnnotationFontCellHeight ON_FontMetrics m_normalized_metrics; // Array of glyphs with sizes and display info std::shared_ptr m_glyphmap; }; ON_FontGlyphCache* ON_FontGlyphCache::New() { ON_FontGlyphCache* font_cache = new ON_FontGlyphCache(); font_cache->m_glyphmap = std::make_shared(); return font_cache; } void ON_FontGlyphCache::SetFontMetrics( const ON_FontMetrics& font_unit_metrics ) { m_font_unit_metrics = font_unit_metrics; m_normalized_to_font_unit_scale = font_unit_metrics.UPM() > 0 ? (((double)font_unit_metrics.UPM()) / ((double)ON_Font::Constants::AnnotationFontCellHeight)) : 0.0; m_font_unit_to_normalized_scale = m_normalized_to_font_unit_scale > 0.0 ? (((double)ON_Font::Constants::AnnotationFontCellHeight) / ((double)font_unit_metrics.UPM())) : 0.0; m_normalized_metrics = (m_font_unit_to_normalized_scale > 0.0 && 1.0 != m_font_unit_to_normalized_scale) ? ON_FontMetrics::Scale(m_font_unit_metrics, m_font_unit_to_normalized_scale) : m_font_unit_metrics; } static const ON_Font* Internal_BinarySearchForManagedFontSerialNumber( unsigned int key, const ON_Font*const* base, size_t nel ) { if (nel > 0 && nullptr != base ) { size_t i; const ON_Font* font; unsigned int d; // The end tests are not necessary, but they // seem to provide overall speed improvement // for the types of searches that call this // function. font = base[0]; d = font->RuntimeSerialNumber(); if ( key < d ) return nullptr; if ( key == d ) return font; font = base[nel-1]; d = font->RuntimeSerialNumber(); if ( key > d ) return nullptr; if ( key == d ) return font; while ( nel > 0 ) { i = nel/2; font = base[i]; d = font->RuntimeSerialNumber(); if ( key < d ) { nel = i; } else if ( key > d ) { i++; base += i; nel -= i; } else { return font; } } } return nullptr; } const ON_Font* ON_ManagedFonts::GetFromSerialNumber( unsigned int managed_font_serial_number ) { if (0 == m_managed_fonts.Count()) { // Put ON_Font::Default as the first entry in this list. Internal_AddManagedFont(&ON_Font::Default, nullptr); } if (managed_font_serial_number < 1) { ON_ERROR("managed_font_serial_number parameter must be >= 1"); return nullptr; } if ( managed_font_serial_number == ON_Font::Default.RuntimeSerialNumber() ) return &ON_Font::Default; const ON_Font* const * managed_fonts = m_managed_fonts.m_by_index.Array(); const unsigned int font_count = m_managed_fonts.m_by_index.UnsignedCount(); if (managed_font_serial_number <= font_count && managed_font_serial_number == managed_fonts[managed_font_serial_number - 1]->RuntimeSerialNumber()) { // This test should always find the managed font as long as the current numbering scheme is used. return managed_fonts[managed_font_serial_number - 1]; } return (font_count > 0) ? Internal_BinarySearchForManagedFontSerialNumber(managed_font_serial_number, managed_fonts, font_count) : nullptr; } const ON_Font* ON_ManagedFonts::GetFromFontCharacteristics( const ON_Font& font_characteristics, bool bCreateIfNotFound ) { if ( 0 == m_managed_fonts.Count() ) { // Put ON_Font::Default as the first entry in this list. Internal_AddManagedFont( &ON_Font::Default, nullptr ); } if (font_characteristics.IsManagedFont()) return &font_characteristics; const bool bIsUnderlined = font_characteristics.IsUnderlined(); const bool bIsStrikethrough = font_characteristics.IsStrikethrough(); const double point_size = ON_Font::IsValidPointSize(font_characteristics.PointSize()) ? font_characteristics.PointSize() : 0.0; const ON_Font* set_font_characteristics = &font_characteristics; std::unique_ptr< ON_Font > fup; if (false == font_characteristics.IsInstalledFont()) { bool bHaveName = font_characteristics.PostScriptName().IsNotEmpty() || font_characteristics.WindowsLogfontName().IsNotEmpty() || font_characteristics.FamilyName().IsNotEmpty(); if ( false == bHaveName || font_characteristics.HasUnsetProperties(false, false) ) { fup = std::make_unique< ON_Font >(font_characteristics); ON_Font* temporary_font = fup.get(); if (nullptr != temporary_font) { if (false == bHaveName) { *temporary_font = ON_Font::Default; } else { temporary_font->SetUnsetProperties(ON_Font::Default, true); } temporary_font->SetUnderlined(bIsUnderlined); temporary_font->SetStrikethrough(bIsStrikethrough); temporary_font->SetPointSize(point_size); set_font_characteristics = temporary_font; } } if (false == set_font_characteristics->IsValid()) return &ON_Font::Default; } ON_Font::NameLocale name_locale = ON_Font::NameLocale::LocalizedFirst; for (;;) { // quick test for default font that occurs often enough to warrent the special checking if (bIsUnderlined) break; if (bIsStrikethrough) break; if (0.0 != point_size && point_size != ON_Font::Default.m_point_size) break; if (false == ON_Font::EqualWeightStretchStyle(&ON_Font::Default, set_font_characteristics, false)) break; // there are multiple weight-stretch-style settings for the same family name and same LOGFONT.lfFaceName. ON_wString name = set_font_characteristics->PostScriptName(name_locale); if (name.IsNotEmpty()) { if (ON_wString::EqualOrdinal(name, ON_Font::Default.PostScriptName(name_locale), true)) { return &ON_Font::Default; } break; // Different PostScript names } name = set_font_characteristics->WindowsLogfontName(name_locale); if (name.IsNotEmpty()) { if (ON_wString::EqualOrdinal(name, ON_Font::Default.WindowsLogfontName(name_locale), true)) { return &ON_Font::Default; } break; // Different LOGFONT settings } name = set_font_characteristics->FamilyName(name_locale); if (name.IsNotEmpty()) { if (ON_wString::EqualOrdinal(name, ON_Font::Default.FamilyName(name_locale), true)) { name = set_font_characteristics->FaceName(name_locale); if ( name.IsEmpty() || ON_wString::EqualOrdinal(name, ON_Font::Default.FaceName(name_locale), true) ) { return &ON_Font::Default; } } break; // Different faces } break; } const ON_Font* managed_font = m_managed_fonts.FromFontProperties( set_font_characteristics, true, true, bIsUnderlined, bIsStrikethrough, point_size ); if (nullptr == managed_font && (bIsUnderlined || bIsStrikethrough)) { ON_Font undecorated(*set_font_characteristics); undecorated.SetUnderlined(false); undecorated.SetStrikethrough(false); managed_font = m_managed_fonts.FromFontProperties( &undecorated, true, true, false, // ignore bIsUnderlined false, // ignore bIsStrikethrough point_size ); } unsigned int managed_font_wss_dev = ON_Font::WeightStretchStyleDeviation( set_font_characteristics, managed_font ); // Lowell - Added additional check for matching underline and strikethrough settings RH-60947 unsigned int managed_font_us_dev = ON_Font::UnderlinedStrikethroughDeviation( set_font_characteristics, managed_font ); if ( nullptr != managed_font && 0 == managed_font_wss_dev && 0 == managed_font_us_dev && point_size == managed_font->PointSize() ) { return managed_font; } for(;;) { if (set_font_characteristics->IsInstalledFont()) break; const ON_Font* installed_font = m_installed_fonts.FromFontProperties( set_font_characteristics, true, true ); if (nullptr == installed_font) break; set_font_characteristics = installed_font; if (bIsUnderlined || bIsStrikethrough || point_size > 0.0) { // Need a copy to set underlined/strikethrough/point_size fup = std::make_unique< ON_Font >(*installed_font); ON_Font* temporary_font = fup.get(); if (nullptr != temporary_font) { temporary_font->SetUnderlined(bIsUnderlined); temporary_font->SetStrikethrough(bIsStrikethrough); temporary_font->SetPointSize(point_size); set_font_characteristics = temporary_font; } } // Look again now that we have settings known to be correct. managed_font = m_managed_fonts.FromFontProperties( set_font_characteristics, true, true, bIsUnderlined, bIsStrikethrough, point_size ); managed_font_wss_dev = ON_Font::WeightStretchStyleDeviation( set_font_characteristics->FontWeight(), set_font_characteristics->FontStretch(), set_font_characteristics->FontStyle(), managed_font ); // RH - 60947 managed_font_us_dev = ON_Font::UnderlinedStrikethroughDeviation( set_font_characteristics->IsUnderlined(), set_font_characteristics->IsStrikethrough(), managed_font ); if (nullptr != managed_font && 0 == managed_font_wss_dev && 0 == managed_font_us_dev ) return managed_font; break; } if (false == bCreateIfNotFound) return nullptr; // The managed font constructor does not copy user data, m_font_index, m_font_id, m_gonna_change_font_cache ON_MemoryAllocationTracking disable_tracking(false); const ON_FontMetrics* font_metrics = nullptr; const ON_FontGlyphCache* font_glyph_cache = set_font_characteristics->m_font_glyph_cache.get(); if (nullptr != font_glyph_cache) font_metrics = &font_glyph_cache->m_font_unit_metrics; managed_font = Internal_AddManagedFont( new ON_Font( ON_Font::FontType::ManagedFont, *set_font_characteristics), font_metrics ); return managed_font; } static void Internal_AddManagedFontSingleRefCountString( const ON_wString& s ) { const ON_wString dirty(s); ON_wString& clean = const_cast(s); clean.Destroy(); clean = static_cast(dirty); } const ON_Font* ON_ManagedFonts::Internal_AddManagedFont( const ON_Font* managed_font, const ON_FontMetrics* managed_font_metrics_in_font_design_units ) { // All memory allocated for managed fonts is permanent app workspace memory. ON_MemoryAllocationTracking disable_tracking(false); if (0 == m_default_font_ptr && 1 == managed_font->m_runtime_serial_number) { // ON_Font::Default is constructed after ON_ManagedFonts::List and // destroyed before ON_ManagedFonts::List. Therefore ~ON_ManagedFonts() // has a pointer to ON_Font::Default that must be ignored during // destruction. m_default_font_ptr = (ON__UINT_PTR)managed_font; } const ON_Font* installed_font = InstalledFonts().FromFontProperties(managed_font, true, true); if (nullptr != installed_font) { // This managed font matches an installed font. ON_Font::Internal_SetManagedFontInstalledFont(managed_font, installed_font, false); } else { // Feb 22, 2021 RH-62974 // This managed font is not installed on this device. // After prototyping several approaches permitting variaous types // of user configured font substitution, Lowell and Dale Lear // concluded that using the default font was the best option. // If this is a problem for lots of users, then we need a way // for users to configure the choice of devault font. // Current settings: // Windows: Arial // Apple: Helvetic Neue // managed_font is a missing font. // We have to substitue the correct quartet member (regular/bold/italic/bold-italic). // Since managed_font references a font that is not installed on this device, // IsItalicInQuartet() and IsBoldInQuartet() will probably fall through to // the fallback best guess sections of those functions. const bool bBoldInQuartet = managed_font->IsBoldInQuartet(); const bool bItalicInQuartet = managed_font->IsItalicInQuartet(); if (ON_FontFaceQuartet::Member::Unset == managed_font->m_quartet_member) managed_font->m_quartet_member = ON_FontFaceQuartet::MemberFromBoldAndItalic(bBoldInQuartet, bItalicInQuartet); const ON_FontFaceQuartet default_quartet = ON_Font::Default.InstalledFontQuartet(); // only look at installed quartet! installed_font = default_quartet.ClosestFace(bBoldInQuartet, bItalicInQuartet); if (nullptr == installed_font) installed_font = &ON_Font::Default; ON_Font::Internal_SetManagedFontInstalledFont(managed_font, installed_font, true); } ON_FontGlyphCache* font_cache = managed_font->m_font_glyph_cache.get(); if (nullptr == font_cache) { ///////////////////// // // Put the cached glyph information here so we only have one set for each font // font_cache = ON_FontGlyphCache::New(); managed_font->m_font_glyph_cache = std::shared_ptr(font_cache); } if (nullptr != font_cache) { if (false == font_cache->m_font_unit_metrics.HeightsAreValid()) { ON_FontMetrics font_metrics_in_font_design_units; if ( nullptr != managed_font_metrics_in_font_design_units && managed_font_metrics_in_font_design_units->HeightsAreValid() ) { font_metrics_in_font_design_units = *managed_font_metrics_in_font_design_units; } else { ON_ManagedFonts::GetFontMetricsInFontDesignUnits( managed_font, font_metrics_in_font_design_units ); } font_cache->SetFontMetrics(font_metrics_in_font_design_units); } } // Insulate strings in managed fonts from outside damage. Internal_AddManagedFontSingleRefCountString(managed_font->m_locale_name); Internal_AddManagedFontSingleRefCountString(managed_font->m_loc_postscript_name); Internal_AddManagedFontSingleRefCountString(managed_font->m_en_postscript_name); Internal_AddManagedFontSingleRefCountString(managed_font->m_loc_family_name); Internal_AddManagedFontSingleRefCountString(managed_font->m_en_family_name); Internal_AddManagedFontSingleRefCountString(managed_font->m_loc_face_name); Internal_AddManagedFontSingleRefCountString(managed_font->m_en_face_name); Internal_AddManagedFontSingleRefCountString(managed_font->m_loc_windows_logfont_name); Internal_AddManagedFontSingleRefCountString(managed_font->m_loc_windows_logfont_name); m_managed_fonts.AddFont(managed_font,false); return managed_font; } #define ON_MANAGED_FONT_CHECK(falure_return_value) {if (0 != m_managed_font) (ON_ERROR("Cannot modify managed fonts."); return (falure_return_value);}} // // END list of managed ON_Fonts // ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// // // BEGIN list of platform ON_Fonts // #if defined(ON_RUNTIME_WIN) ////static bool ON_Internal_WindowsGetGlyphMetricsEx( //// LOGFONT& logfont, //// int logfont_height, //// //MAT2 mat2, //// ON__UINT32 unicode_code_point, //// class ON_TextBox& font_unit_glyph_box ////) ////{ //// font_unit_glyph_box = ON_TextBox::Unset; //// //// if (logfont_height <= 0) //// logfont_height = ON_Font::Constants::AnnotationFontCellHeight; //// //// if (false == ON_IsValidUnicodeCodePoint(unicode_code_point) ) //// return false; //// //// if (0 == logfont.lfFaceName[0] ) //// return false; //// //// wchar_t w[8] = { 0 }; //// const size_t w_capacity = (sizeof(w) / sizeof(w[0])) - 1; //// const int w_count = ON_EncodeWideChar(unicode_code_point, w_capacity, w); //// if ( 0 == w[0] || w_count <= 0 || w_count > (int)w_capacity ) //// return false; //// //// HDC font_hdc = nullptr; //// HFONT hfont = nullptr; //// HGDIOBJ hfont0 = nullptr; //// //// for (;;) //// { //// font_hdc = ON_Font::CreateWindowsLogfontDeviceContext(); //// if (nullptr == font_hdc) //// break; //// //// LOGFONT lf = logfont; //// lf.lfHeight = logfont_height; //// hfont = ::CreateFontIndirect(&lf); //// if (nullptr == hfont) //// break; //// hfont0 = ::SelectObject(font_hdc, hfont); //// //// ON__UINT16 glyphindex[sizeof(w)/sizeof(w[0])] = { 0 }; //// const DWORD gicount = ::GetGlyphIndices(font_hdc, w, w_count, glyphindex, GGI_MARK_NONEXISTING_GLYPHS); //// if (GDI_ERROR == gicount) //// break; //// if (0xffff == glyphindex[0] || 0xffff == glyphindex[1]) //// { //// // March 2017 Dale Lear //// // https://mcneel.myjetbrains.com/youtrack/issue/RH-38377 //// // //// // The GGI_MARK_NONEXISTING_GLYPHS flag causes GetGlyphIndices() //// // to set glyphindex[] values to 0xFFFF for missing glyphs. //// // //// // GetGlyphIndices() is not capable of getting this glyph in the hdc font. //// // This often happes for surrogate pair encodings, even when the //// // glyph does exist in the font. //// // //// // GetGlyphIndices cannot find glyph in the font //// // Often a surrogate pair and we probably need to be using //// // Windows newer Uniscribe API instead of the tired old //// // GetGlyphIndices + GetGlyphOutlineW stuff. //// break; //// } //// //// MAT2 mat2; //// memset(&mat2, 0, sizeof(mat2)); //// mat2.eM11.fract = 0; //// mat2.eM11.value = 1; //// mat2.eM12.fract = 0; //// mat2.eM12.value = 0; //// mat2.eM21.fract = 0; //// mat2.eM21.value = 0; //// mat2.eM22.fract = 0; //// mat2.eM22.value = 1; //// //// GLYPHMETRICS glm; //// memset(&glm, 0, sizeof(glm)); //// const DWORD cb_size = ::GetGlyphOutlineW(font_hdc, glyphindex[0], GGO_NATIVE | GGO_GLYPH_INDEX, &glm, 0, nullptr, &mat2); //// if (GDI_ERROR == cb_size) //// break; //// //// font_unit_glyph_box.m_advance.i = glm.gmCellIncX; //// font_unit_glyph_box.m_advance.j = glm.gmCellIncY; //// if (cb_size == 0) //// { //// // Non-printing char - nothing to draw //// font_unit_glyph_box.m_bbmin.i = 0; //// font_unit_glyph_box.m_bbmin.j = 0; //// font_unit_glyph_box.m_bbmax.i = font_unit_glyph_box.m_advance.i; //// font_unit_glyph_box.m_bbmax.j = font_unit_glyph_box.m_advance.j; //// } //// else //// { //// font_unit_glyph_box.m_bbmin.i = glm.gmptGlyphOrigin.x; //// font_unit_glyph_box.m_bbmin.j = glm.gmptGlyphOrigin.y - (int)glm.gmBlackBoxY; //// font_unit_glyph_box.m_bbmax.i = (int)glm.gmBlackBoxX + glm.gmptGlyphOrigin.x; //// font_unit_glyph_box.m_bbmax.j = glm.gmptGlyphOrigin.y; //// } //// //// break; //// } //// //// if (nullptr != font_hdc) //// { //// if (nullptr != hfont) //// { //// ::SelectObject(font_hdc, hfont0); //// ::DeleteObject(hfont); //// } //// ON_Font::DeleteWindowsLogfontDeviceContext(font_hdc); //// } //// //// return font_unit_glyph_box.IsSet(); ////} class ON_InternalLogFontAndTextMetric { public: ON_InternalLogFontAndTextMetric() = default; ~ON_InternalLogFontAndTextMetric() = default; ON_InternalLogFontAndTextMetric(const ON_InternalLogFontAndTextMetric&) = default; ON_InternalLogFontAndTextMetric& operator=(const ON_InternalLogFontAndTextMetric&) = default; static const ON_InternalLogFontAndTextMetric Unset; public: ON_SHA1_Hash m_font_hash = ON_SHA1_Hash::ZeroDigest; LOGFONT m_lfX; TEXTMETRIC m_tmX; }; ON_InternalLogFontAndTextMetric Internal_InitUnset() { ON_InternalLogFontAndTextMetric lftm; memset(&lftm, 0, sizeof(lftm)); return lftm; } const ON_InternalLogFontAndTextMetric ON_InternalLogFontAndTextMetric::Unset(Internal_InitUnset()); ////class ON_Font_Windows_LFTM_UserData : public ON_UserData ////{ ////public: //// static void Set( //// const LOGFONT& lf, //// const TEXTMETRIC& tm, //// ON_Font* font //// ) //// { //// if (nullptr == font || 0 == lf.lfFaceName[0]) //// return; //// ON_UserData* ud = font->GetUserData(ON_Font_Windows_LFTM_UserData::Id); //// ON_Font_Windows_LFTM_UserData* lftm = ON_Font_Windows_LFTM_UserData::Cast(ud); //// if (nullptr == lftm) //// { //// if (nullptr != ud) //// delete ud; //// ud = nullptr; //// lftm = new ON_Font_Windows_LFTM_UserData(); //// } //// lftm->m_font_hash = font->FontCharacteristicsHash(); //// lftm->m_lf = lf; //// lftm->m_lftm = tm; //// if (nullptr == ud) //// { //// if (false == font->AttachUserData(lftm)) //// delete lftm; //// } //// return; //// } //// //// static ON_InternalLogFontAndTextMetric Get( //// const ON_Font* font //// ); //// //// ON_Font_Windows_LFTM_UserData() //// { //// m_userdata_uuid = ON_Font_Windows_LFTM_UserData::Id; //// m_userdata_copycount = 1; //// } //// ~ON_Font_Windows_LFTM_UserData() = default; //// ON_Font_Windows_LFTM_UserData(const ON_Font_Windows_LFTM_UserData&) = default; //// ON_Font_Windows_LFTM_UserData& operator=(const ON_Font_Windows_LFTM_UserData&) = default; //// //// ON_OBJECT_DECLARE(ON_Font_Windows_LFTM_UserData); //// ////private: ////// {452ADA1A-49F0-4FAF-8E14-73DABE21FC5C} //// static const ON_UUID Id; //// ////private: //// ON_SHA1_Hash m_font_hash = ON_SHA1_Hash::ZeroDigest; //// ON_InternalLogFontAndTextMetric m_lftm = ON_InternalLogFontAndTextMetric::Unset; ////}; //// ////// {452ADA1A-49F0-4FAF-8E14-73DABE21FC5C} ////const ON_UUID ON_Font_Windows_LFTM_UserData::Id = { 0x452ada1a, 0x49f0, 0x4faf, { 0x8e, 0x14, 0x73, 0xda, 0xbe, 0x21, 0xfc, 0x5c } }; ////ON_OBJECT_IMPLEMENT(ON_Font_Windows_LFTM_UserData, ON_UserData, "452ADA1A-49F0-4FAF-8E14-73DABE21FC5C"); //// ////void ON_Font_Windows_LFTM_UserData::Set( //// const LOGFONT& lf, //// const TEXTMETRIC& tf, //// ON_Font* font ////) ////{ ////} //// ////ON_InternalLogFontAndTextMetric ON_Font_Windows_LFTM_UserData::Get( //// const ON_Font* font ////) ////{ ////} static int CALLBACK Internal_GetPlatformWindowsLogfontFontList_CallbackProc( const LOGFONT *lf, const TEXTMETRIC *ignored_tm, DWORD font_type, LPARAM lParam ) { for (;;) { if (nullptr == lf ) break; if (font_type != TRUETYPE_FONTTYPE) break; if (0 == lParam) break; if (L'@' == lf->lfFaceName[0]) break; ((ON_SimpleArray*)lParam)->AppendNew() = *lf; break; } return 1; } static void Internal_CleanLOGFONT( LOGFONT& lf, bool bZero ) { lf.lfHeight = 0; lf.lfWidth = 0; lf.lfEscapement = 0; lf.lfOrientation = 0; if (bZero) { lf.lfWeight = 0; lf.lfItalic = 0; lf.lfUnderline = 0; lf.lfStrikeOut = 0; lf.lfCharSet = DEFAULT_CHARSET; lf.lfOutPrecision = 0; lf.lfClipPrecision = 0; lf.lfQuality = 0; lf.lfPitchAndFamily = 0; } else { // do not change lfWeight if (0 != lf.lfItalic) lf.lfItalic = 1; if (0 != lf.lfUnderline) lf.lfUnderline = 1; if (0 != lf.lfStrikeOut) lf.lfStrikeOut = 1; if (SYMBOL_CHARSET != lf.lfCharSet) lf.lfCharSet = DEFAULT_CHARSET; lf.lfOutPrecision = ON_Font::WindowsConstants::logfont_out_precis; lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; lf.lfQuality = ON_Font::WindowsConstants::logfont_quality; lf.lfPitchAndFamily = ON_Font::WindowsConstants::logfont_pitch_and_family; } } static int Internal_CompareLogfont(const LOGFONT* lhs, const LOGFONT* rhs) { if (lhs == rhs) return 0; if (nullptr == lhs) return 1; if (nullptr == rhs) return -1; // compare font family name const size_t face_name_capacity = sizeof(lhs->lfFaceName) / sizeof(lhs->lfFaceName[0]); int rc = ON_wString::CompareOrdinal( lhs->lfFaceName, ON_wString::Length(lhs->lfFaceName,face_name_capacity), rhs->lfFaceName, ON_wString::Length(rhs->lfFaceName,face_name_capacity), true ); if (0 != rc) return rc; // Compare style (upright, italic, ...) const int lhs_italic = (0 != lhs->lfItalic) ? 1 : 0; const int rhs_italic = (0 != rhs->lfItalic) ? 1 : 0; rc = lhs_italic - rhs_italic; if (0 != rc) return rc; // compare weight (normal, bold, ...) if (lhs->lfWeight < rhs->lfWeight) return -1; if (lhs->lfWeight > rhs->lfWeight) return 1; // compare charset last - SYMBOL_CHARSET require special treatment const int lhs_charset = (SYMBOL_CHARSET == lhs->lfCharSet) ? 1 : 0; const int rhs_charset = (SYMBOL_CHARSET == rhs->lfCharSet) ? 1 : 0; rc = lhs_charset - rhs_charset; if (0 != rc) return rc; return 0; } unsigned int ON_Font::GetInstalledWindowsLogFonts( ON_SimpleArray& logfont_list ) { logfont_list.SetCount(0); // https://en.wikipedia.org/wiki/List_of_typefaces_included_with_Microsoft_Windows HDC font_hdc = ON_Font::CreateWindowsLogfontDeviceContext(); if (nullptr == font_hdc) return 0; const DWORD flags = 0; // To enumerate all installed Windows LOGFONT values // Get list of font family names LOGFONT lf; memset(&lf, 0, sizeof(lf)); lf.lfCharSet = DEFAULT_CHARSET; ON_SimpleArray font_family_list(1000); ::EnumFontFamiliesEx( font_hdc, &lf, Internal_GetPlatformWindowsLogfontFontList_CallbackProc, (LPARAM)(&font_family_list), flags ); // remove duplicates for (int i = 0; i < font_family_list.Count(); i++) { Internal_CleanLOGFONT(font_family_list[i], true); } font_family_list.QuickSort(Internal_CompareLogfont); memset(&lf, 0, sizeof(lf)); int font_family_count = 0; for (int i = 0; i < font_family_list.Count(); i++) { if (0 != Internal_CompareLogfont(&lf, &font_family_list[i])) { lf = font_family_list[i]; font_family_list[font_family_count++] = lf; } } font_family_list.SetCount(font_family_count); // Get complete list of fonts - family and face (..., Arial(Normal , Bold, Italic, Bold Italic, ...), ... logfont_list.Reserve(3 * font_family_count); for (int i = 0; i < font_family_count; i++) { // Append a list of all faces in this font family lf = font_family_list[i]; if (0 == lf.lfFaceName[0]) continue; int count0 = logfont_list.Count(); bool bAddFamilyLOGFONT = true; lf.lfCharSet = DEFAULT_CHARSET; ::EnumFontFamiliesEx( font_hdc, &lf, Internal_GetPlatformWindowsLogfontFontList_CallbackProc, (LPARAM)(&logfont_list), flags ); // remove duplicates const int count1 = logfont_list.Count(); for (int j = count0; j < count1; j++) { Internal_CleanLOGFONT(logfont_list[j], false); } ON_qsort((void*)(logfont_list.Array() + count0), count1 - count0, sizeof(LOGFONT), (int (*)(const void*,const void*))Internal_CompareLogfont); memset(&lf, 0, sizeof(lf)); for (int j = count0; j < count1; j++) { LOGFONT lfj = logfont_list[j]; if (0 == Internal_CompareLogfont(&lf, &lfj)) continue; // Add logfont_list[j] to the culled list fonts. lf = logfont_list[j]; logfont_list[count0++] = lf; bAddFamilyLOGFONT = false; } if (bAddFamilyLOGFONT) { logfont_list[count0++] = font_family_list[i]; } logfont_list.SetCount(count0); } ON_Font::DeleteWindowsLogfontDeviceContext(font_hdc); font_hdc = nullptr; return logfont_list.UnsignedCount(); } void ON_ManagedFonts::Internal_GetWindowsInstalledFonts( ON_SimpleArray& platform_font_list ) { // As of May 2016, Rhino 6 requires WIndows 7 SP2 and DWrite works better. // ON_Font::GetInstalledWindowsLogFonts() uses the 2 pass EnumFontFamiliesEx() technique. // It has at least 5 failures // It fails to mark "HoloLens MDL Assets" as a symbol font (lfCharSet = SYMBOL_CHARSET) // It fails to mark "Segoe MDL Assets" as a symbol font (lfCharSet = SYMBOL_CHARSET) // It fails to include "Source Sans Pro Black" // It fails to include simulated fonts. // It fails to get PostScript font names. ////ON_SimpleArray logfont_list; ////ON_Font::GetInstalledWindowsLogFonts(logfont_list); ////for (int i = 0; i < logfont_list.Count(); i++) ////{ //// LOGFONT lf = logfont_list[i]; //// lf.lfHeight = 0; //// ON_Font* font = new ON_Font(); //// if (false == font->SetFromWindowsLogFont( 0, nullptr, lf ) ) //// { //// delete font; //// continue; //// } //// platform_font_list.Append(font); ////} platform_font_list.SetCount(0); ON_SimpleArray dwrite_font_list; // freetype cannot support simulated fonts. If freetype is not used, we can include simulated fonts. const bool bIncludeSimulatedFontFaces = true; const bool bKeepDWriteFont = true; ON_Font::GetInstalledWindowsDWriteFonts( L"GetUserDefaultLocaleName", bIncludeSimulatedFontFaces, true, dwrite_font_list ); platform_font_list.Reserve(dwrite_font_list.Count()); ON_SHA1_Hash prev_installed_font_hash = ON_SHA1_Hash::ZeroDigest; for (int i = 0; i < dwrite_font_list.Count(); i++) { //const bool bSimulated // = dwrite_font_list[i].m_bSimulatedBold // || dwrite_font_list[i].m_bSimulatedOblique // || dwrite_font_list[i].m_bSimulatedOther; //if (bSimulated) //{ // if (false == bIncludeSimulatedFontFaces) // continue; // if (ON_Font::IsSingleStrokeFontName(dwrite_font_list[i].m_postscript_name)) // continue; //} struct IDWriteFont* dwrite_font = dwrite_font_list[i].m_dwrite_font; if (nullptr == dwrite_font) continue; for (;;) { ON_Font* installed_font = new ON_Font(ON_Font::FontType::InstalledFont, dwrite_font_list[i]); if ( installed_font->WindowsLogfontName().IsEmpty() && installed_font->PostScriptName().IsEmpty() && installed_font->FamilyName().IsEmpty() && installed_font->QuartetName().IsEmpty() ) { // No reliable way to identify this font. delete installed_font; break; } platform_font_list.Append(installed_font); break; } dwrite_font_list[i].m_dwrite_font = nullptr; dwrite_font->Release(); } } #endif ON_FontFaceQuartet::ON_FontFaceQuartet( const wchar_t* quartet_name, const ON_Font* regular, const ON_Font* bold, const ON_Font* italic, const ON_Font* bold_italic ) : m_quartet_name(quartet_name) , m_regular(regular) , m_bold(bold) , m_italic(italic) , m_bold_italic(bold_italic) { m_quartet_name.TrimLeftAndRight(); } int ON_FontFaceQuartet::CompareQuartetName( const ON_FontFaceQuartet* lhs, const ON_FontFaceQuartet* rhs ) { if (lhs == rhs) return 0; if (nullptr == lhs) return 1; if (nullptr == rhs) return -1; return ON_wString::CompareOrdinal(lhs->m_quartet_name, rhs->m_quartet_name, true); } ON::RichTextStyle ON::RichTextStyleFromUnsigned(unsigned int u) { switch (u) { ON_ENUM_FROM_UNSIGNED_CASE(ON::RichTextStyle::Unset); ON_ENUM_FROM_UNSIGNED_CASE(ON::RichTextStyle::Windows10SDK); ON_ENUM_FROM_UNSIGNED_CASE(ON::RichTextStyle::AppleOSXSDK); }; ON_ERROR("Invalid ON::RichTextStyle value."); return ON::RichTextStyle::Unset; } ON::RichTextStyle ON::RichTextStyleFromCurrentPlatform() { return #if defined(ON_RUNTIME_WIN) ON::RichTextStyle::Windows10SDK #elif defined(ON_RUNTIME_APPLE) ON::RichTextStyle::AppleOSXSDK #else ON::RichTextStyle::Unset #endif ; } const ON_wString ON_FontFaceQuartet::RichTextSample( ON::RichTextStyle rich_text_style ) const { const wchar_t* quartet_name = static_cast(m_quartet_name); if (nullptr == quartet_name) return ON_wString::EmptyString; if ( nullptr == m_regular && nullptr == m_bold && nullptr == m_italic && nullptr == m_bold_italic) return ON_wString::EmptyString; const ON_wString regular_ps = (nullptr != m_regular) ? m_regular->PostScriptName() : ON_wString::EmptyString; const ON_wString bold_ps = (nullptr != m_bold) ? m_bold->PostScriptName() : ON_wString::EmptyString; const ON_wString italic_ps = (nullptr != m_italic) ? m_italic->PostScriptName() : ON_wString::EmptyString; const ON_wString bold_italic_ps = (nullptr != m_bold_italic) ? m_bold_italic->PostScriptName() : ON_wString::EmptyString; const ON_wString not_available(L"Not available."); const ON_wString regular = regular_ps.IsNotEmpty() ? regular_ps : not_available; const ON_wString bold = bold_ps.IsNotEmpty() ? bold_ps : not_available; const ON_wString italic = italic_ps.IsNotEmpty() ? italic_ps : not_available; const ON_wString bold_italic = bold_italic_ps.IsNotEmpty() ? bold_italic_ps : not_available; ON_wString sample; switch (rich_text_style) { case ON::RichTextStyle::Unset: break; case ON::RichTextStyle::Windows10SDK: // font table uses Windows LOGFONT name and \b \i to select faces sample = ON_wString::FormatToString(L"{\\rtf1\\deff0{\\fonttbl{\\f0 %ls;}}\\fs40", quartet_name); sample += ON_wString::FormatToString(L"{\\f0 Windows 10 LOGFONT Quartet: %ls}{\\par}", quartet_name); if (nullptr != m_regular) sample += ON_wString::FormatToString(L"{\\f0 Regular: %ls}{\\par}", static_cast(regular_ps)); if (nullptr != m_bold) sample += ON_wString::FormatToString(L"{\\f0\\b Bold: %ls}{\\par}", static_cast(bold_ps)); if (nullptr != m_italic) sample += ON_wString::FormatToString(L"{\\f0\\i Italic: %ls}{\\par}", static_cast(italic_ps)); if (nullptr != m_bold_italic) sample += ON_wString::FormatToString(L"{\\f0\\b\\i Bold Italic: %ls}{\\par}", static_cast(bold_italic_ps)); sample += ON_wString(L"\\par}"); break; case ON::RichTextStyle::AppleOSXSDK: // font table uses unique PostScript names for each face if (regular_ps.IsNotEmpty() || bold_ps.IsNotEmpty() || italic_ps.IsNotEmpty() || bold_italic_ps.IsNotEmpty()) { sample = ON_wString::FormatToString(L"{\\rtf1\\deff0{\\fonttbl"); int fdex = 0; if (regular_ps.IsNotEmpty()) sample += ON_wString::FormatToString(L"{\\f%d %ls;}", fdex++, static_cast(regular_ps)); if (bold_ps.IsNotEmpty()) sample += ON_wString::FormatToString(L"{\\f%d %ls;}", fdex++, static_cast(bold_ps)); if (italic_ps.IsNotEmpty()) sample += ON_wString::FormatToString(L"{\\f%d %ls;}", fdex++, static_cast(italic_ps)); if (bold_italic_ps.IsNotEmpty()) sample += ON_wString::FormatToString(L"{\\f%d %ls;}", fdex++, static_cast(bold_italic_ps)); sample += ON_wString(L"}\\fs40"); sample += ON_wString::FormatToString(L"{\\f0 Apple OS X Fake Quartet: %ls}{\\par}", quartet_name); fdex = 0; if (nullptr != m_regular) sample += ON_wString::FormatToString(L"{\\f%d Regular: %ls}{\\par}", fdex++, static_cast(regular_ps)); if (nullptr != m_bold) sample += ON_wString::FormatToString(L"{\\f%d\\b Bold: %ls}{\\par}", fdex++, static_cast(bold_ps)); if (nullptr != m_italic) sample += ON_wString::FormatToString(L"{\\f%d\\i Italic: %ls}{\\par}", fdex++, static_cast(italic_ps)); if (nullptr != m_bold_italic) sample += ON_wString::FormatToString(L"{\\f%d\\b\\i Bold Italic: %ls}{\\par}", fdex++, static_cast(bold_italic_ps)); sample += ON_wString(L"\\par}"); } break; default: break; } return sample; } bool ON_FontFaceQuartet::HasRegularFace() const { return (nullptr != RegularFace()); } bool ON_FontFaceQuartet::HasBoldFace() const { return (nullptr != BoldFace()); } bool ON_FontFaceQuartet::HasItalicFace() const { return (nullptr != ItalicFace()); } bool ON_FontFaceQuartet::HasBoldItalicFace() const { return (nullptr != BoldItalicFace()); } bool ON_FontFaceQuartet::HasAllFaces() const { return HasRegularFace() && HasBoldFace() && HasItalicFace() && HasBoldItalicFace(); } bool ON_FontFaceQuartet::IsEmpty() const { return IsNotEmpty() ? false : true; } bool ON_FontFaceQuartet::IsNotEmpty() const { return (HasRegularFace() || HasBoldFace() || HasItalicFace() || HasBoldItalicFace()); } const ON_wString ON_FontFaceQuartet::QuartetName() const { return m_quartet_name; } const ON_Font* ON_FontFaceQuartet::RegularFace() const { return m_regular; } const ON_Font* ON_FontFaceQuartet::BoldFace() const { return m_bold; } const ON_Font* ON_FontFaceQuartet::ItalicFace() const { return m_italic; } const ON_Font* ON_FontFaceQuartet::BoldItalicFace() const { return m_bold_italic; } ON_FontFaceQuartet::Member ON_FontFaceQuartet::QuartetMember( const ON_Font* font ) const { if (nullptr == font || m_quartet_name.IsEmpty()) return ON_FontFaceQuartet::Member::Unset; if ( false == m_quartet_name.EqualOrdinal(font->QuartetName(ON_Font::NameLocale::Localized), true) && false == m_quartet_name.EqualOrdinal(font->QuartetName(ON_Font::NameLocale::English), true) ) return ON_FontFaceQuartet::Member::Unset; ON_FontFaceQuartet::Member m[2] = { ON_FontFaceQuartet::Member ::Unset,ON_FontFaceQuartet::Member::Unset}; if ( font->IsItalicOrOblique() ) { m[0] = ON_FontFaceQuartet::Member::Italic; m[1] = ON_FontFaceQuartet::Member::BoldItalic; } else { m[0] = ON_FontFaceQuartet::Member::Regular; m[1] = ON_FontFaceQuartet::Member::Bold; } const ON_Font* q[2] = { Face(m[0]),Face(m[1]) }; if (font == q[0]) return m[0]; if (font == q[1]) return m[1]; ON_wString s[4]; for (int i = 0; i < 2; i++) { if (nullptr == q[i]) continue; #if defined(ON_RUNTIME_WIN) // style, weight, and LOGFONT name is the most reliable way to uniquely identify quartet members on Windows. // We've already matched style. if (q[i]->FontWeight() != font->FontWeight()) continue; s[0] = q[i]->WindowsLogfontName(ON_Font::NameLocale::Localized); s[1] = q[i]->WindowsLogfontName(ON_Font::NameLocale::English); s[2] = font->WindowsLogfontName(ON_Font::NameLocale::Localized); s[3] = font->WindowsLogfontName(ON_Font::NameLocale::English); #else // style and PostScript name is the most reliable way to uniquely identify quartet members on Mac OS. // We've already matched style. s[0] = q[i]->PostScriptName(ON_Font::NameLocale::Localized); s[1] = q[i]->PostScriptName(ON_Font::NameLocale::English); s[2] = font->PostScriptName(ON_Font::NameLocale::Localized); s[3] = font->PostScriptName(ON_Font::NameLocale::English); #endif if (s[0].IsNotEmpty() && (s[0].EqualOrdinal(s[2], true) || s[0].EqualOrdinal(s[3], true))) return m[i]; if (s[1].IsNotEmpty() && (s[1].EqualOrdinal(s[2], true) || s[1].EqualOrdinal(s[3], true))) return m[i]; } return ON_FontFaceQuartet::Member::Unset; } const ON_Font* ON_FontFaceQuartet::Face( ON_FontFaceQuartet::Member member ) const { switch (member) { case ON_FontFaceQuartet::Member::Regular: return m_regular; case ON_FontFaceQuartet::Member::Bold: return m_bold; case ON_FontFaceQuartet::Member::Italic: return m_italic; case ON_FontFaceQuartet::Member::BoldItalic: return m_bold_italic; case ON_FontFaceQuartet::Member::Unset: break; } return nullptr; } const ON_Font* ON_FontFaceQuartet::ClosestFace( ON_FontFaceQuartet::Member member ) const { bool bPreferBold = false; bool bPreferItalic = false; switch (member) { case ON_FontFaceQuartet::Member::Regular: break; case ON_FontFaceQuartet::Member::Bold: bPreferBold = true; break; case ON_FontFaceQuartet::Member::Italic: bPreferItalic = true; break; case ON_FontFaceQuartet::Member::BoldItalic: bPreferItalic = true; bPreferBold = true; break; default: return nullptr; } return ClosestFace(bPreferBold,bPreferItalic); } const ON_Font* ON_FontFaceQuartet::Face( bool bBold, bool bItalic ) const { return (bItalic) ? (bBold ? BoldItalicFace() : ItalicFace()) : (bBold ? BoldFace() : RegularFace()); } unsigned int ON_FontFaceQuartet::FaceCount() const { unsigned count = 0; const ON_Font* f[4] = { m_regular,m_bold,m_italic,m_bold_italic }; for (unsigned i = 0; i < 4U; ++i) { if (nullptr != f[i]) ++count; } return count; } unsigned int ON_FontFaceQuartet::NotInstalledFaceCount() const { unsigned count = 0; const ON_Font* f[4] = { m_regular,m_bold,m_italic,m_bold_italic }; for (unsigned i = 0; i < 4U; ++i) { if (nullptr != f[i] && f[i]->IsManagedSubstitutedFont()) ++count; } return count; } unsigned int ON_FontFaceQuartet::SimulatedFaceCount() const { unsigned count = 0; const ON_Font* f[4] = { m_regular,m_bold,m_italic,m_bold_italic }; for (unsigned i = 0; i < 4U; ++i) { if (nullptr != f[i] && f[i]->IsSimulated()) ++count; } return count; } const ON_Font* ON_FontFaceQuartet::ClosestFace( bool bPreferedBold, bool bPreferedItalic ) const { const ON_Font* f = Face(bPreferedBold, bPreferedItalic); if (nullptr != f) return f; if (IsEmpty()) return nullptr; if (nullptr == m_bold && nullptr == m_bold_italic) bPreferedBold = false; else if (nullptr == m_regular && nullptr == m_italic) bPreferedBold = true; if (nullptr == m_italic && nullptr == m_bold_italic) bPreferedItalic = false; else if (nullptr == m_regular && nullptr == m_bold) bPreferedItalic = true; f = Face(bPreferedBold, bPreferedItalic); if (nullptr != f) return f; if (nullptr != m_regular) return m_regular; if (nullptr != m_bold) return m_bold; if (nullptr != m_italic) return m_italic; return m_bold_italic; } const ON_wString ON_FontFaceQuartet::MemberToString( ON_FontFaceQuartet::Member member ) { switch (member) { case ON_FontFaceQuartet::Member::Unset: return ON_wString(L"Unset"); break; case ON_FontFaceQuartet::Member::Regular: return ON_wString(L"Regular"); break; case ON_FontFaceQuartet::Member::Bold: return ON_wString(L"Bold"); break; case ON_FontFaceQuartet::Member::Italic: return ON_wString(L"Italic"); break; case ON_FontFaceQuartet::Member::BoldItalic: return ON_wString(L"Bold-Italic"); break; } return ON_wString::EmptyString; } ON_FontFaceQuartet::Member ON_FontFaceQuartet::MemberFromUnsigned( unsigned int member_as_unsigned ) { switch (member_as_unsigned) { ON_ENUM_FROM_UNSIGNED_CASE(ON_FontFaceQuartet::Member::Unset); ON_ENUM_FROM_UNSIGNED_CASE(ON_FontFaceQuartet::Member::Regular); ON_ENUM_FROM_UNSIGNED_CASE(ON_FontFaceQuartet::Member::Bold); ON_ENUM_FROM_UNSIGNED_CASE(ON_FontFaceQuartet::Member::Italic); ON_ENUM_FROM_UNSIGNED_CASE(ON_FontFaceQuartet::Member::BoldItalic); } return ON_FontFaceQuartet::Member::Unset; } ON_FontFaceQuartet::Member ON_FontFaceQuartet::MemberFromBoldAndItalic( bool bMemberIsBold, bool bMemberIsItalic ) { return bMemberIsBold ? (bMemberIsItalic ? ON_FontFaceQuartet::Member::BoldItalic : ON_FontFaceQuartet::Member::Bold) : (bMemberIsItalic ? ON_FontFaceQuartet::Member::Italic : ON_FontFaceQuartet::Member::Regular) ; } unsigned ON_FontFaceQuartet::BoldItalicDeviation( ON_FontFaceQuartet::Member desired_member, ON_FontFaceQuartet::Member available_member ) { if (desired_member == available_member) return 0; unsigned distance = 0; if (ON_FontFaceQuartet::Member::Unset == desired_member) { distance += 4; desired_member = ON_FontFaceQuartet::Member::Regular; } if (ON_FontFaceQuartet::Member::Unset == available_member) { distance += 4; available_member = ON_FontFaceQuartet::Member::Regular; } const bool bDesiredBold = (ON_FontFaceQuartet::Member::Bold == desired_member || ON_FontFaceQuartet::Member::BoldItalic == desired_member); const bool bDesiredItalic = (ON_FontFaceQuartet::Member::Italic == desired_member || ON_FontFaceQuartet::Member::BoldItalic == desired_member); const bool bAvailableBold = (ON_FontFaceQuartet::Member::Bold == available_member || ON_FontFaceQuartet::Member::BoldItalic == available_member); const bool bAvailableItalic = (ON_FontFaceQuartet::Member::Italic == available_member || ON_FontFaceQuartet::Member::BoldItalic == available_member); if (bDesiredBold != bAvailableBold) distance += 1; if (bDesiredItalic != bAvailableItalic) distance += 2; return distance; } const ON_wString ON_Font::WidthWeightSlantDescription(ON_Font::Stretch width, ON_Font::Weight weight, ON_Font::Style slant) { ON_wString wws; if (ON_Font::Stretch::Unset != width) { if (wws.IsNotEmpty()) wws += ON_wString(L"-"); wws += ON_Font::StretchToWideString(width); } if (ON_Font::Weight::Unset != weight) { if (wws.IsNotEmpty()) wws += ON_wString(L"-"); wws += ON_Font::WeightToWideString(weight); } if (ON_Font::Style::Unset != slant) { if (wws.IsNotEmpty()) wws += ON_wString(L"-"); wws += ON_Font::StyleToWideString(slant); } return wws; } const ON_wString ON_Font::WidthWeightSlantDescription() const { return ON_Font::WidthWeightSlantDescription(this->FontStretch(), this->FontWeight(), this->FontStyle()); } void ON_FontFaceQuartet::Dump(ON_TextLog& text_log) const { ON_wString quartet_name = this->QuartetName(); quartet_name.TrimLeftAndRight(); if (this->IsEmpty() && quartet_name.IsEmpty()) { text_log.Print(L"Empty Quartet\n"); return; } const ON_wString quartet_face[4] = { ON_FontFaceQuartet::MemberToString(ON_FontFaceQuartet::Member::Regular), ON_FontFaceQuartet::MemberToString(ON_FontFaceQuartet::Member::Bold), ON_FontFaceQuartet::MemberToString(ON_FontFaceQuartet::Member::Italic), ON_FontFaceQuartet::MemberToString(ON_FontFaceQuartet::Member::BoldItalic) }; const ON_Font* quartet_font[4] = { RegularFace(), BoldFace(), ItalicFace(), BoldItalicFace() }; const bool bFaceNotAvailable[4] = { nullptr == quartet_font[0], nullptr == quartet_font[1], nullptr == quartet_font[2], nullptr == quartet_font[3] }; const bool bFaceNotInstalled[4] = { nullptr != quartet_font[0] && quartet_font[0]->IsManagedSubstitutedFont(), nullptr != quartet_font[1] && quartet_font[1]->IsManagedSubstitutedFont(), nullptr != quartet_font[2] && quartet_font[2]->IsManagedSubstitutedFont(), nullptr != quartet_font[3] && quartet_font[3]->IsManagedSubstitutedFont() }; const bool bQuartetNotAvailable = bFaceNotAvailable[0] && bFaceNotAvailable[1] && bFaceNotAvailable[2] && bFaceNotAvailable[3] ; const bool bQuartetNotInstalled = (bFaceNotAvailable[0] || bFaceNotInstalled[0]) && (bFaceNotAvailable[1] || bFaceNotInstalled[1]) && (bFaceNotAvailable[2] || bFaceNotInstalled[2]) && (bFaceNotAvailable[3] || bFaceNotInstalled[3]) ; ON_wString quartet_decription(L"Quartet"); if (quartet_name.IsNotEmpty()) { quartet_decription += ON_wString(L" "); quartet_decription += quartet_name; } quartet_decription += ON_wString(L":"); if (bQuartetNotAvailable) { quartet_decription += ON_wString(L" "); } else if (bQuartetNotInstalled) { quartet_decription += ON_wString(L" (not installed)"); } text_log.PrintString(quartet_decription); text_log.PrintNewLine(); text_log.PushIndent(); for (int i = 0; i < 4; i++) { ON_wString description(quartet_face[i]); description += ON_wString(':'); const ON_Font* font = quartet_font[i]; if (nullptr == font) description += ON_wString(L" "); else { const ON_wString family_name = font->FamilyName(); if (family_name.IsNotEmpty()) { description += ON_wString(L" "); description += family_name; const ON_wString face_name = font->FaceName(); if (face_name.IsNotEmpty()) { description += ON_wString(L" "); description += face_name; } } #if defined(ON_RUNTIME_APPLE) const ON_wString postscript_name = font->PostScriptName(); if (postscript_name.IsNotEmpty()) { description += ON_wString(L" (PostScript name="); description += postscript_name; description += ON_wString(L")"); } #endif const ON_wString wws = font->WidthWeightSlantDescription(); if (wws.IsNotEmpty()) { description += ON_wString(L" "); description += wws; } if (font->IsManagedSubstitutedFont()) description += ON_wString(L" (not installed)"); else if (font->IsInstalledFont() && font->IsSimulated()) description += ON_wString(L" (simulated)"); } text_log.PrintString(description); text_log.PrintNewLine(); } text_log.PopIndent(); } #define ON_ManagedFonts_CompareFontPointer(lhs,rhs) \ if (lhs == rhs) return 0; if (nullptr == lhs) return 1; if (nullptr == rhs) return -1; \ const ON_Font* lhs_font = *lhs; const ON_Font* rhs_font = *rhs; \ if (lhs_font == rhs_font) return 0; if (nullptr == lhs_font) return 1; if (nullptr == rhs_font) return -1 int ON_ManagedFonts::CompareFontPointer(ON_Font const* const* lhs, ON_Font const* const* rhs) { ON_ManagedFonts_CompareFontPointer(lhs, rhs); return 0; } int ON_FontList::ComparePostScriptName(ON_Font const* const* lhs, ON_Font const* const* rhs) { ON_ManagedFonts_CompareFontPointer(lhs, rhs); return ON_wString::CompareOrdinal( lhs_font->PostScriptName(ON_Font::NameLocale::LocalizedFirst), rhs_font->PostScriptName(ON_Font::NameLocale::LocalizedFirst), true ); } int ON_FontList::CompareFamilyName(ON_Font const* const* lhs, ON_Font const* const* rhs) { ON_ManagedFonts_CompareFontPointer(lhs, rhs); return ON_wString::CompareOrdinal( lhs_font->FamilyName(ON_Font::NameLocale::LocalizedFirst), rhs_font->FamilyName(ON_Font::NameLocale::LocalizedFirst), true ); } int ON_FontList::CompareFamilyAndFaceName(ON_Font const* const* lhs, ON_Font const* const* rhs) { ON_ManagedFonts_CompareFontPointer(lhs, rhs); int rc = ON_wString::CompareOrdinal( lhs_font->FamilyName(ON_Font::NameLocale::LocalizedFirst), rhs_font->FamilyName(ON_Font::NameLocale::LocalizedFirst), true ); if ( 0 == rc ) rc = ON_wString::CompareOrdinal( lhs_font->FaceName(ON_Font::NameLocale::LocalizedFirst), rhs_font->FaceName(ON_Font::NameLocale::LocalizedFirst), true ); return rc; } int ON_FontList::CompareFamilyAndWindowsLogfontName(ON_Font const* const* lhs, ON_Font const* const* rhs) { ON_ManagedFonts_CompareFontPointer(lhs, rhs); int rc = ON_wString::CompareOrdinal( lhs_font->FamilyName(ON_Font::NameLocale::LocalizedFirst), rhs_font->FamilyName(ON_Font::NameLocale::LocalizedFirst), true ); if (0 == rc) rc = ON_wString::CompareOrdinal( lhs_font->WindowsLogfontName(ON_Font::NameLocale::LocalizedFirst), rhs_font->WindowsLogfontName(ON_Font::NameLocale::LocalizedFirst), true ); return rc; } int ON_FontList::CompareWindowsLogfontName(ON_Font const* const* lhs, ON_Font const* const* rhs) { ON_ManagedFonts_CompareFontPointer(lhs, rhs); return ON_wString::CompareOrdinal( lhs_font->WindowsLogfontName(ON_Font::NameLocale::LocalizedFirst), rhs_font->WindowsLogfontName(ON_Font::NameLocale::LocalizedFirst), true ); } int ON_FontList::CompareEnglishPostScriptName(ON_Font const* const* lhs, ON_Font const* const* rhs) { ON_ManagedFonts_CompareFontPointer(lhs, rhs); return ON_wString::CompareOrdinal( lhs_font->PostScriptName(ON_Font::NameLocale::English), rhs_font->PostScriptName(ON_Font::NameLocale::English), true ); } int ON_FontList::CompareEnglishFamilyName(ON_Font const* const* lhs, ON_Font const* const* rhs) { ON_ManagedFonts_CompareFontPointer(lhs, rhs); return ON_wString::CompareOrdinal( lhs_font->FamilyName(ON_Font::NameLocale::English), rhs_font->FamilyName(ON_Font::NameLocale::English), true ); } int ON_FontList::CompareEnglishFamilyAndFaceName(ON_Font const* const* lhs, ON_Font const* const* rhs) { ON_ManagedFonts_CompareFontPointer(lhs, rhs); int rc = ON_wString::CompareOrdinal( lhs_font->FamilyName(ON_Font::NameLocale::English), rhs_font->FamilyName(ON_Font::NameLocale::English), true ); if ( 0 == rc ) rc = ON_wString::CompareOrdinal( lhs_font->FaceName(ON_Font::NameLocale::English), rhs_font->FaceName(ON_Font::NameLocale::English), true ); return rc; } int ON_FontList::CompareEnglishWindowsLogfontName(ON_Font const* const* lhs, ON_Font const* const* rhs) { ON_ManagedFonts_CompareFontPointer(lhs, rhs); return ON_wString::CompareOrdinal( lhs_font->WindowsLogfontName(ON_Font::NameLocale::LocalizedFirst), rhs_font->WindowsLogfontName(ON_Font::NameLocale::LocalizedFirst), true ); } int ON_FontList::CompareQuartetName(ON_Font const* const* lhs, ON_Font const* const* rhs) { ON_ManagedFonts_CompareFontPointer(lhs, rhs); return ON_wString::CompareOrdinal( lhs_font->QuartetName(), rhs_font->QuartetName(), true ); } int ON_FontList::CompareWeightStretchStyle( ON_Font const* const* lhs, ON_Font const* const* rhs ) { ON_ManagedFonts_CompareFontPointer(lhs, rhs); int rc; // Upright first, Italic 2nd, Oblique last // slope const int lhs_font_style = (int)static_cast(lhs_font->FontStyle()); const int rhs_font_style = (int)static_cast(rhs_font->FontStyle()); rc = (lhs_font_style - rhs_font_style); if (0 != rc) return rc; // width const int lhs_font_stretch = (int)static_cast(lhs_font->FontStretch()); const int rhs_font_stretch = (int)static_cast(rhs_font->FontStretch()); rc = (lhs_font_stretch - rhs_font_stretch); if (0 != rc) return rc; // weight const int lhs_font_weight = lhs_font->WindowsLogfontWeight(); const int rhs_font_weight = rhs_font->WindowsLogfontWeight(); rc = (lhs_font_weight - rhs_font_weight); if (0 != rc) return rc; return 0; } int ON_FontList::CompareStretch( ON_Font const* const* lhs, ON_Font const* const* rhs ) { ON_ManagedFonts_CompareFontPointer(lhs, rhs); const int lhs_font_stretch = (int)static_cast(lhs_font->FontStretch()); const int rhs_font_stretch = (int)static_cast(rhs_font->FontStretch()); const int rc = (lhs_font_stretch - rhs_font_stretch); return rc; } int ON_FontList::CompareUnderlinedStrikethroughPointSize( ON_Font const* const* lhs, ON_Font const* const* rhs ) { ON_ManagedFonts_CompareFontPointer(lhs, rhs); // underlined const int lhs_font_underlined = lhs_font->IsUnderlined() ? 1 : 0; const int rhs_font_underlined = rhs_font->IsUnderlined() ? 1 : 0; int rc = (lhs_font_underlined - rhs_font_underlined); if (0 != rc) return rc; // underlined const int lhs_font_strikethrough = lhs_font->IsStrikethrough() ? 1 : 0; const int rhs_font_strikethrough = rhs_font->IsStrikethrough() ? 1 : 0; rc = (lhs_font_strikethrough - rhs_font_strikethrough); if (0 != rc) return rc; const double lhs_font_point_size = lhs_font->PointSize(); const double rhs_font_point_size = rhs_font->PointSize(); if (lhs_font_point_size < rhs_font_point_size) return -1; if (lhs_font_point_size > rhs_font_point_size) return 1; return 0; } int ON_FontList::CompareFontCharacteristicsHash( ON_Font const* const* lhs, ON_Font const* const* rhs ) { ON_ManagedFonts_CompareFontPointer(lhs, rhs); return ON_SHA1_Hash::Compare(lhs_font->FontCharacteristicsHash(), rhs_font->FontCharacteristicsHash()); } // // END list of platform ON_Fonts // ////////////////////////////////////////////////////////////////////////// bool ON_Font::ModificationPermitted( const char* function_name, const char* file_name, int line_number ) const { if (this == &ON_Font::Default) ON_ErrorEx(file_name, line_number, function_name, "ON_Font::Default cannot be modified."); else if (this == &ON_Font::Unset) ON_ErrorEx(file_name, line_number, function_name, "ON_Font::Unset cannot be modified."); else if (IsManagedFont()) ON_ErrorEx(file_name, line_number, function_name, "Managed fonts cannot be modified."); else { // Modificaton of this font means the managed information it references // will not be valid. A reference to the correct cached information // will be generated when it is actually needed. m_font_glyph_cache.reset(); return true; } return false; } #define ON_FONT_MODIFICATION_PERMITTED this->ModificationPermitted(OPENNURBS__FUNCTION__,__FILE__,__LINE__) //// V6 files 4F0F51FB-35D0-4865-9998-6D2C6A99721D is the class id for ON_TextStyle ////ON_OBJECT_IMPLEMENT( ON_Font, ON_Object, "4F0F51FB-35D0-4865-9998-6D2C6A99721D" ); //ON_OBJECT_IMPLEMENT( ON_Font, ON_Object, "5F7476D1-798A-4953-B359-4A37699CD6F4" ); const ON_Font* ON_Font::ManagedFont() const { return IsManagedFont() ? this : ON_Font::GetManagedFont(*this, true); } const ON_FontGlyph* ON_Font::CodePointGlyph( ON__UINT32 unicode_codepoint ) const { const ON_Font* managed_font = ManagedFont(); if (nullptr == managed_font) return nullptr; bool bCreateIfMissing = true; bool bFindSubstitutes = true; return managed_font->Internal_ManagedCodePointGlyph(unicode_codepoint,bCreateIfMissing,bFindSubstitutes); } const ON_Font* ON_Font::GetManagedFont( const ON_Font& font_characteristics, bool bCreateIfNotFound ) { if (font_characteristics.IsManagedFont()) { // No need to look it up and return itself. return &font_characteristics; } return ON_ManagedFonts::List.GetFromFontCharacteristics(font_characteristics,bCreateIfNotFound); } const ON_Font* ON_Font::GetManagedFontFromSerialNumber( unsigned int managed_font_runtime_serial_number ) { return ON_ManagedFonts::List.GetFromSerialNumber(managed_font_runtime_serial_number); } unsigned int ON_Font::GetManagedFontList( ON_SimpleArray< const ON_Font* >& managed_fonts ) { const ON_SimpleArray< const ON_Font* >& a = ON_ManagedFonts::ManagedFonts().ByIndex(); if (&a != &managed_fonts) managed_fonts = a; return managed_fonts.UnsignedCount(); } unsigned int ON_Font::GetInstalledFontList( ON_SimpleArray< const ON_Font* >& installed_fonts ) { const ON_SimpleArray< const ON_Font* >& a = ON_ManagedFonts::InstalledFonts().ByFamilyName(); if (&a != &installed_fonts) installed_fonts = a; return installed_fonts.UnsignedCount(); } unsigned int ON_Font::GetInstalledFontFamily( const wchar_t* font_family_name, ON_SimpleArray< const ON_Font* >& installed_fonts ) { installed_fonts.SetCount(0); if (nullptr == font_family_name || 0 == font_family_name[0]) return 0; const ON_FontList& installed_font_list = ON_ManagedFonts::InstalledFonts(); if (0 == installed_font_list.FontListFromNames( nullptr, nullptr, font_family_name, nullptr, installed_fonts) ) installed_font_list.FontListFromNames(font_family_name, font_family_name, nullptr, nullptr, installed_fonts); return installed_fonts.UnsignedCount(); } static const wchar_t* Internal_NameOverLapSkipNoise( const wchar_t* s, bool bNoiseFilter ) { if (false == bNoiseFilter) return s; if (nullptr == s) return nullptr; while (0 != *s) { if (*s >= 'A' && *s <= 'Z') return s; if (*s >= 'a' && *s <= 'z') return s; if (*s >= 0x80) return s; s++; } return s; } static const ON_wString Internal_NameOverlapCleanName( const wchar_t* s, bool bNoiseFilter ) { if (nullptr == s || 0 == s[0]) return ON_wString::EmptyString; ON_wString clean_name; wchar_t buffer[128]; int buffer_count = 0; for (wchar_t c = *(s = Internal_NameOverLapSkipNoise(s,bNoiseFilter)); 0 != c; c = *(s = Internal_NameOverLapSkipNoise(++s,bNoiseFilter))) { buffer[buffer_count++] = ON_wString::MapCharacterOrdinal(ON_StringMapOrdinalType::MinimumOrdinal, c); if (127 == buffer_count) { buffer[buffer_count] = 0; clean_name += buffer; buffer_count = 0; } } buffer[buffer_count] = 0; clean_name += buffer; return clean_name; } static int Internal_NameOverLap( const ON_wString& clean_name, const wchar_t* s, bool bNoiseFilter ) { if (nullptr == s) return 0; const wchar_t* clean_s = static_cast(clean_name); int overlap_count = 0; for ( s = Internal_NameOverLapSkipNoise(s,bNoiseFilter); 0 != *clean_s && *clean_s == ON_wString::MapCharacterOrdinal(ON_StringMapOrdinalType::LowerOrdinal,*s); s = Internal_NameOverLapSkipNoise(s,bNoiseFilter) ) { overlap_count++; clean_s++; s++; } return overlap_count; } static bool Internal_EqualLogfontName( const ON_wString& clean_logfont_name, const ON_Font* candidate_font ) { if (nullptr == candidate_font) return false; if (clean_logfont_name.IsEmpty()) return false; const ON_wString installed_font_logfont_name = Internal_NameOverlapCleanName(candidate_font->WindowsLogfontName(ON_Font::NameLocale::LocalizedFirst),true); const bool bEqualLogfontName = ON_wString::EqualOrdinal(clean_logfont_name, installed_font_logfont_name, true); return bEqualLogfontName; } static bool Internal_EqualFamilyName( const ON_wString& clean_family_name, const ON_Font* installed_font, ON_wString& installed_font_family_name ) { if (nullptr == installed_font) return false; installed_font_family_name = Internal_NameOverlapCleanName(installed_font->FamilyName(),true); const bool bEqualFamileName = ON_wString::EqualOrdinal(clean_family_name, installed_font_family_name, true); return bEqualFamileName; } class Internal_FontDelta { // Internal_FontDelta is a simple minded attempt at measuring the difference between a target font and a candidate font. // It is probably a poor substitued for something fancier like PANOSE information. // Unfortunately, we generally do not have PANOSE information about the target font. public: Internal_FontDelta() = default; ~Internal_FontDelta() = default; Internal_FontDelta(const Internal_FontDelta&) = default; Internal_FontDelta& operator= (const Internal_FontDelta&) = default; // Initialize this to be a measure of the difference between target_font and candidate_font // The target_clean_*_name values take some time to compute, so they are pre-calculated and // passed in. There are generally multiple candidate fonts. Internal_FontDelta( const ON_Font& target_font, const ON_wString& target_clean_logfont_name, const ON_wString& target_clean_family_name, const ON_Font* candidate_font ) : m_candidate_font(candidate_font) { if (nullptr == m_candidate_font) { m_hash_delta = 6; return; } // When we have identical PostSript names, we assume they // are correct and assume font family, weight, stretch, and style // are exact matches. const ON_wString target_postscript_name = target_font.PostScriptName(); const bool bEqualPostScriptName = target_postscript_name.IsNotEmpty() && ON_wString::EqualOrdinal(target_postscript_name, m_candidate_font->PostScriptName(), true); const bool bEqualLogfontName = bEqualPostScriptName || ( ON_Font::Origin::WindowsFont == target_font.FontOrigin() && ON_Font::Origin::WindowsFont == candidate_font->FontOrigin() && target_clean_logfont_name.IsNotEmpty() && Internal_EqualLogfontName(target_clean_logfont_name, candidate_font) ); m_weight_delta = bEqualPostScriptName ? 0 : abs(((int)static_cast(target_font.FontWeight())) - ((int)static_cast(candidate_font->FontWeight()))); m_stretch_delta = bEqualPostScriptName ? 0 : abs(((int)static_cast(target_font.FontStretch())) - ((int)static_cast(candidate_font->FontStretch()))); m_style_delta = bEqualPostScriptName ? 0 : abs(((int)static_cast(target_font.FontStyle())) - ((int)static_cast(candidate_font->FontStyle()))); ON_wString candidate_clean_family_name; const bool bEqualFamilyName = bEqualLogfontName || Internal_EqualFamilyName(target_clean_family_name, candidate_font, candidate_clean_family_name); if (target_font.IsUnderlined() != candidate_font->IsUnderlined()) m_underline_and_strikethrough_delta++; if (target_font.IsStrikethrough() != candidate_font->IsStrikethrough()) m_underline_and_strikethrough_delta++; m_family_name_delta = 0; if ( bEqualFamilyName && 0 == m_weight_delta && 0 == m_stretch_delta && 0 == m_style_delta ) { // This is either an exact match (m_hash_delta = 0) or a very close match (m_hash_delta = 1). m_hash_delta = (0 == m_underline_and_strikethrough_delta && target_font.FontCharacteristicsHash() == candidate_font->FontCharacteristicsHash()) ? 0 : 1; return; } // If PANOSE information were available for target_font and candidate_font, it would be useful here. if ( bEqualLogfontName && 0 == m_stretch_delta && m_style_delta <= 1 ) { // There are at most 4 "faces" with the same WINDOWS LOGFONT.lfFaceName and the differences // among the four "faces" are almost always in weight (normal or bold) or style (upright or italic). // Fonts with larger differences in weight or style or any significant difference in stretch // typically have different values of LOGFONT.lfFaceName. // (Underlined and Strikethrough are not "faces"). m_hash_delta = 2; return; } if (bEqualFamilyName) { // A family, like "Arial", can have manu faces with a wide variety of weights, stretches, and styles. // The LOGFONTs with lfFaceName = "Arial", "Arial Narrow", "Arial Black", ... are all in the "Arial" family. m_hash_delta = 3; return; } // Differences below here (m_hash_delta >= 4) are generally visually significant. // Calculate font family name overlap int name_overlap = Internal_NameOverLap(target_clean_family_name, candidate_clean_family_name, true); m_family_name_delta = target_clean_family_name.Length() - name_overlap; if (0 == m_family_name_delta) { // clean_font_family_name.Length() = name_overlap means that // clean_font_family_name.Length() <= installed_font_family_name.Length() // and installed_font_family_name begins with clean_font_family_name. // If installed_font_family_name.Length() > clean_font_family_name.Length(), // m_name_delta < 0 and it measures how much tail was not matched m_family_name_delta = target_clean_family_name.Length() - candidate_clean_family_name.Length(); } m_hash_delta = (0 == m_style_delta || ON_Font::Style::Upright != target_font.FontStyle()) ? 4 : 5; } bool IsExactMatch() const { return (nullptr != m_candidate_font && 0 == m_hash_delta ); } // If Compare(lhs,rhs) < 0, then lhs is a better match for the target font passed to the constructor. static int Compare(const Internal_FontDelta& lhs, const Internal_FontDelta& rhs) { if (lhs.m_candidate_font == rhs.m_candidate_font) return 0; if (nullptr == rhs.m_candidate_font) return -1; // lhs is better because it has a candidate font if (nullptr == lhs.m_candidate_font) return 1; // rhs is better because it has a candidate font // m_hash_delta >= 0 and smaller values of m_hash_delta indicate better matches int rc = (lhs.m_hash_delta - rhs.m_hash_delta); if (0 != rc) return rc; rc = (lhs.m_family_name_delta - rhs.m_family_name_delta); if (0 != rc) { if (0 == lhs.m_family_name_delta) return -1; // lhs is in the same family if (0 == rhs.m_family_name_delta) return 1; // rhs is in the same family if (lhs.m_family_name_delta < 0 && rhs.m_family_name_delta < 0) { // When both values of m_name_delta are negative, it // means their font family names both began with the // family name we are searching for. In this // case the shortest tail is best. // The length of that tail is -m_name_delta, // so the least negative m_delta is the best match. return -rc; } // If at least one value of m_delta > 0, then the // smallest value of m_delta is the best match. return rc; } rc = (lhs.m_style_delta - rhs.m_style_delta); if (0 != rc) return rc; rc = (lhs.m_weight_delta - rhs.m_weight_delta); if (0 != rc) return rc; rc = (lhs.m_stretch_delta - rhs.m_stretch_delta); if (0 != rc) return rc; rc = (lhs.m_underline_and_strikethrough_delta - rhs.m_underline_and_strikethrough_delta); if (0 != rc) return rc; return 0; } // Points to a cadidate for matching the original font const ON_Font* m_candidate_font = nullptr; // 0: exact match // 1: same family, weight, stretch, style match - different hash // 2: same LOGFONT.lfName - different weight,stretch,style // 3: same family name - different LOGFONT.lfFaceName, weight,stretch,style // 4: some overlap in family name // 5: least favoriable match int m_hash_delta = 0; // m_family_name_delta // <0: partial overlap // 0: exact match // >0: installed font family name is longer but exact match with start int m_family_name_delta = 0; // m_logfont_name_delta // <0: partial overlap // 0: exact match // >0: installed font LOGFONT.lfFaceName name is longer but exact match with start int m_logfont_name_delta = 0; // Weight delta = absolute value of difference between weights. int m_weight_delta = 0; // Stretch (width) delta = absolute value of difference between stretch. int m_stretch_delta = 0; // Style (slope) delta = absolute value of difference between font styles. int m_style_delta = 0; // Underlined and strikethrough delta = number of different bools. int m_underline_and_strikethrough_delta = 0; }; const ON_Font* ON_Font::BestMatch( ON_Font const*const* candidate_font_list, size_t candidate_font_count ) const { if (nullptr == candidate_font_list || candidate_font_count < 1) return nullptr; if (1 == candidate_font_count) return candidate_font_list[0]; const ON_wString target_clean_logfont_name = Internal_NameOverlapCleanName(WindowsLogfontName(),true); const ON_wString target_clean_family_name = Internal_NameOverlapCleanName(FamilyName(),true); Internal_FontDelta best_match; for (size_t i = 0; i < candidate_font_count; i++) { const ON_Font* candidate_font = candidate_font_list[i]; if (nullptr == candidate_font) continue; const Internal_FontDelta delta( *this, target_clean_logfont_name, target_clean_family_name, candidate_font ); if (delta.IsExactMatch()) return candidate_font; if (nullptr == best_match.m_candidate_font || Internal_FontDelta::Compare(delta, best_match) < 0 ) best_match = delta; } return best_match.m_candidate_font; } unsigned int ON_Font::WeightStretchStyleDeviation( ON_Font::Weight prefered_weight, ON_Font::Stretch prefered_stretch, ON_Font::Style prefered_style, ON_Font::Weight available_weight, ON_Font::Stretch available_stretch, ON_Font::Style available_style ) { if (ON_Font::Weight::Unset == prefered_weight) prefered_weight = available_weight; if (ON_Font::Stretch::Unset == prefered_stretch) prefered_stretch = available_stretch; if (ON_Font::Style::Unset == prefered_style) prefered_style = available_style; const unsigned int weight_delta = abs(((int)prefered_weight) - ((int)available_weight)); const unsigned int stretch_delta = abs(((int)prefered_stretch) - ((int)available_stretch)); const unsigned int style_delta = abs(((int)prefered_style) - ((int)available_style)); return 4*(1000U * style_delta + 20U * weight_delta + stretch_delta); } unsigned int ON_Font::WeightStretchStyleDeviation( ON_Font::Weight prefered_weight, ON_Font::Stretch prefered_stretch, ON_Font::Style prefered_style, const ON_Font* available_font ) { if (nullptr == available_font) return 0xFFFFFFF; return ON_Font::WeightStretchStyleDeviation( prefered_weight, prefered_stretch, prefered_style, available_font->FontWeight(), available_font->FontStretch(), available_font->FontStyle() ); } unsigned int ON_Font::WeightStretchStyleDeviation( const ON_Font* prefered_weight_stretch_style, const ON_Font* available_font ) { if (nullptr == prefered_weight_stretch_style) prefered_weight_stretch_style = &ON_Font::Default; ON_Font::Weight prefered_weight = prefered_weight_stretch_style->FontWeight(); ON_Font::Stretch prefered_stretch = prefered_weight_stretch_style->FontStretch(); ON_Font::Style prefered_style = prefered_weight_stretch_style->FontStyle(); return ON_Font::WeightStretchStyleDeviation(prefered_weight, prefered_stretch, prefered_style, available_font); } unsigned int ON_Font::UnderlinedStrikethroughDeviation( bool bPreferedUnderline, bool bPreferedStrikethrough, bool bAvailableUnderline, bool bAvailableStrikethrough ) { const unsigned int underlined_dev = (unsigned int)abs((int)(bPreferedUnderline ? 1 : 0) - (int)(bAvailableUnderline ? 1 : 0)); const unsigned int strikethrough_dev = (unsigned int)abs((int)(bPreferedStrikethrough ? 1 : 0) - (int)(bAvailableStrikethrough ? 1 : 0)); return 2 * underlined_dev + strikethrough_dev; } unsigned int ON_Font::UnderlinedStrikethroughDeviation( bool bPreferedUnderline, bool bPreferedStrikethrough, const ON_Font* available_font ) { return ON_Font::UnderlinedStrikethroughDeviation( bPreferedUnderline, bPreferedStrikethrough, (nullptr == available_font) ? false : available_font->IsUnderlined(), (nullptr == available_font) ? false : available_font->IsStrikethrough() ); } unsigned int ON_Font::UnderlinedStrikethroughDeviation( const ON_Font* prefered_underlined_strikethrough, const ON_Font* available_font ) { return ON_Font::UnderlinedStrikethroughDeviation( (nullptr == prefered_underlined_strikethrough) ? false : prefered_underlined_strikethrough->IsUnderlined(), (nullptr == prefered_underlined_strikethrough) ? false : prefered_underlined_strikethrough->IsStrikethrough(), (nullptr == available_font) ? false : available_font->IsUnderlined(), (nullptr == available_font) ? false : available_font->IsStrikethrough() ); } unsigned int ON_Font::RichTextPropertyDeviation( bool bPreferedRtfBold, bool bPreferedItalic, bool bPreferedUnderline, bool bPreferedStrikethrough, const ON_Font* available_font ) { if (nullptr == available_font) return 0xFFFFFFFF; return ON_Font::RichTextPropertyDeviation( bPreferedRtfBold, bPreferedItalic, bPreferedUnderline, bPreferedStrikethrough, available_font->IsBold(), available_font->IsItalic(), available_font->IsUnderlined(), available_font->IsStrikethrough() ); } unsigned int ON_Font::RichTextPropertyDeviation( bool bPreferedRtfBold, bool bPreferedItalic, bool bPreferedUnderline, bool bPreferedStrikethrough, bool bAvailableRtfBold, bool bAvailableItalic, bool bAvailableUnderline, bool bAvailableStrikethrough ) { const ON_Font::Weight prefered_weight = bPreferedRtfBold ? ON_Font::Weight::Bold : ON_Font::Weight::Normal; const ON_Font::Weight available_weight = bAvailableRtfBold ? ON_Font::Weight::Bold : ON_Font::Weight::Normal; const ON_Font::Style prefered_style = bPreferedItalic ? ON_Font::Style::Italic : ON_Font::Style::Upright; const ON_Font::Style available_style = bPreferedItalic ? ON_Font::Style::Italic : ON_Font::Style::Upright; const unsigned int wss_dev = ON_Font::WeightStretchStyleDeviation( prefered_weight, ON_Font::Stretch::Medium, prefered_style, available_weight, ON_Font::Stretch::Medium, available_style ); const unsigned int us_dev = ON_Font::UnderlinedStrikethroughDeviation( bPreferedUnderline, bPreferedStrikethrough, bAvailableUnderline, bAvailableStrikethrough ); return wss_dev + us_dev; } const ON_Font* ON_Font::Internal_BestMatchWeightStretchStyle( ON_Font::Weight prefered_weight, ON_Font::Stretch prefered_stretch, ON_Font::Style prefered_style, ON_Font const * const * font_list, size_t font_count ) { if (nullptr == font_list || font_count <= 0) return nullptr; const ON_Font* best_font = nullptr; unsigned int best_delta = 0xFFFFFFFF; for (size_t i = 0; i < font_count; i++) { const ON_Font* font = font_list[i]; if (nullptr == font) continue; const unsigned int delta = ON_Font::WeightStretchStyleDeviation( prefered_weight, prefered_stretch, prefered_style, font ); if (0 == delta) return font; if (nullptr == best_font || delta < best_delta) { best_font = font; best_delta = delta; } } return best_font; } const ON_Font* ON_Font::BestMatch( const ON_SimpleArray< const ON_Font* >& font_list ) const { return BestMatch( font_list.Array(), font_list.UnsignedCount() ); } const ON_FontList& ON_Font::ManagedFontList() { return ON_ManagedFonts::ManagedFonts(); } const ON_FontList& ON_Font::InstalledFontList() { return ON_ManagedFonts::InstalledFonts(); } const ON_Font* ON_Font::InstalledFontFromRichTextProperties( const wchar_t* rtf_font_name, bool bRtfBold, bool bRtfItalic ) { ON_wString local_rtf_font_name(rtf_font_name); local_rtf_font_name.TrimLeftAndRight(); if (local_rtf_font_name.IsEmpty()) local_rtf_font_name = ON_Font::Default.RichTextFontName(); rtf_font_name = local_rtf_font_name; // It is critical that bRequireFaceMatch because the input name must // clearly identify a font in some way. The search below allows for this // name to appear in various contexts (LOGFONT, PostScript, Family+Face, Family). const bool bRequireFaceMatch = true; // It is critical that bRequireStyleMatch = false. See below const bool bRequireStyleMatch = false; // The preferred_weight is a bias used to pick between faces that have matching name properties. // An exact match is not required because bRequireStyleMatch = false; const ON_Font::Weight preferred_weight = bRtfBold ? ON_Font::Weight::Semibold : ON_Font::Weight::Medium; // The preferred_style is a bias used to pick between faces that have matching name properties. // An exact match is not required because bRequireStyleMatch = false; const ON_Font::Style preferred_style = bRtfItalic ? ON_Font::Style::Italic : ON_Font::Style::Upright; const bool bUnderlined = false; const bool bStrikethrough = false; const wchar_t* ignore_postscript_name = nullptr; const wchar_t* ignore_windows_logfont_name = nullptr; const wchar_t* ignore_family_name = nullptr; const wchar_t* ignore_prefered_face_name = nullptr; // 1st: Try LOGFONT name. For Windows this is the very best choice. // For MacOS this works the best in practice. If it fails, we move onto PostScript. const ON_Font* installed_font = ON_Font::InstalledFontList().FromNames( ignore_postscript_name, rtf_font_name, // windows_logfont_name, ignore_family_name, ignore_prefered_face_name, preferred_weight, ON_Font::Stretch::Unset, preferred_style, bRequireFaceMatch, bRequireStyleMatch, bUnderlined, bStrikethrough, 0.0 ); while (nullptr == installed_font) { // 2nd: Try PostScript name installed_font = ON_Font::InstalledFontList().FromNames( rtf_font_name, // postscript_name, ignore_windows_logfont_name, ignore_family_name, ignore_prefered_face_name, preferred_weight, ON_Font::Stretch::Unset, preferred_style, bRequireFaceMatch, bRequireStyleMatch, bUnderlined, bStrikethrough, 0.0 ); if (nullptr != installed_font) break; // 3rd: Try Family + Face name const wchar_t hyphen[2] = {ON_wString::HyphenMinus,0}; for (const wchar_t* separator = rtf_font_name + 1; 0 != *separator; ++separator) { if (ON_wString::Space != *separator && ON_wString::HyphenMinus != *separator) continue; ON_wString family_name(rtf_font_name, (int)(separator - rtf_font_name)); family_name.TrimLeftAndRight(); family_name.TrimLeftAndRight(hyphen); family_name.TrimLeftAndRight(); if (family_name.IsEmpty()) continue; ON_wString face_name(separator + 1); face_name.TrimLeftAndRight(); face_name.TrimLeftAndRight(hyphen); face_name.TrimLeftAndRight(); if (face_name.IsEmpty()) continue; installed_font = ON_Font::InstalledFontList().FromNames( ignore_postscript_name, ignore_windows_logfont_name, family_name, face_name, preferred_weight, ON_Font::Stretch::Unset, preferred_style, bRequireFaceMatch, bRequireStyleMatch, bUnderlined, bStrikethrough, 0.0 ); if (nullptr != installed_font) break; } if (nullptr != installed_font) break; // 4th: Try Family name installed_font = ON_Font::InstalledFontList().FromNames( ignore_postscript_name, ignore_windows_logfont_name, rtf_font_name, // family name ignore_prefered_face_name, preferred_weight, ON_Font::Stretch::Unset, preferred_style, bRequireFaceMatch, bRequireStyleMatch, bUnderlined, bStrikethrough, 0.0 ); if (nullptr != installed_font) break; // No installed font comes close to matching the input name parameter break; } if (nullptr != installed_font) { const ON_FontFaceQuartet q = installed_font->InstalledFontQuartet(); // only look at installed quartet! const ON_Font* f = q.ClosestFace(bRtfBold, bRtfItalic); return (nullptr != f) ? f : installed_font; } return nullptr; } static void Internal_SetFakeNamesFromExistingNames( ON_wString loc_existing, ON_wString en_existing, ON_wString& loc_fake, ON_wString& en_fake ) { loc_existing.TrimLeftAndRight(); en_existing.TrimLeftAndRight(); if (loc_existing.IsEmpty()) loc_existing = en_existing; else if (en_existing.IsEmpty()) en_existing = loc_existing; if (loc_existing.IsNotEmpty()) loc_fake = loc_existing; if (en_existing.IsNotEmpty()) en_fake = en_existing; } const ON_Font* ON_Font::Internal_DecoratedFont( bool bUnderlined, bool bStrikethrough ) const { // Underline and strikethrough are font rendering effects and are not designed into the font glyphs. if (false == bUnderlined && false == bStrikethrough) return this; if (bUnderlined ? 0 : 1 == this->IsUnderlined() ? 0 : 1 && bStrikethrough ? 0 : 1 == this->IsStrikethrough() ? 0 : 1) return this; ON_Font decorated(*this); decorated.SetUnderlined(bUnderlined); decorated.SetStrikethrough(bStrikethrough); const ON_Font* decorated_font = decorated.ManagedFont(); if (nullptr != decorated_font && ON_FontFaceQuartet::Member::Unset == decorated_font->m_quartet_member) { // Decorated faces are not explicity in quartets, // but when dealing with rich text, we need to know what quartet member they are decorating. decorated_font->m_quartet_member = this->m_quartet_member; } return (nullptr != decorated_font) ? decorated_font : this; } const ON_Font* ON_Font::FontFromRichTextProperties( ON_wString rich_text_font_name, bool bBoldQuartetMember, bool bItalicQuartetMember, bool bUnderlined, bool bStrikethrough ) { rich_text_font_name.TrimLeftAndRight(); if (rich_text_font_name.IsEmpty()) rich_text_font_name = ON_Font::Default.RichTextFontName(); const ON_FontFaceQuartet::Member rich_text_quartet_face = ON_FontFaceQuartet::MemberFromBoldAndItalic(bBoldQuartetMember, bItalicQuartetMember); ON_FontFaceQuartet installed_quartet = ON_ManagedFonts::InstalledFonts().QuartetFromQuartetName(rich_text_font_name); if ( installed_quartet.IsNotEmpty() ) { // Installed font quartets use ClosestFace() because we know all the faces // the real font contains. // If you don't get what you want, your asking for something that does not exist. // You may need to fix some user interface to not offer the choice that led you here. const ON_Font* installed_font = installed_quartet.ClosestFace(rich_text_quartet_face); // DO NOT CHANGE TO Face() if (nullptr != installed_font) return installed_font->Internal_DecoratedFont( bUnderlined, bStrikethrough); } const ON_FontFaceQuartet managed_substitute_quartet = ON_ManagedFonts::ManagedFonts().QuartetFromQuartetName(rich_text_font_name); if (managed_substitute_quartet.IsNotEmpty()) { // Missing font quartets use Face() because we don't know what the real font quartet looks like. const ON_Font* managed_quartet_face = managed_substitute_quartet.Face(rich_text_quartet_face); // DO NOT CHANGE TO ClosestFace() if (nullptr != managed_quartet_face) return managed_quartet_face->Internal_DecoratedFont(bUnderlined, bStrikethrough); // this missing face has already been added to the quartet } // We will need to make a new managed font and put it in a reasonable managed quartet. // All memory allocated for managed fonts is permanent app workspace memory. ON_MemoryAllocationTracking disable_tracking(false); { // convert the name to untracked memory so it doesn't look like a leak const ON_wString local_str(rich_text_font_name); rich_text_font_name = ON_wString::EmptyString; rich_text_font_name = ON_wString(static_cast(local_str)); } const ON_Font* installed_font = nullptr; installed_font = ON_Font::InstalledFontFromRichTextProperties(rich_text_font_name, bBoldQuartetMember, bItalicQuartetMember); if (nullptr == installed_font) { #if defined(ON_RUNTIME_APPLE) if (nullptr == installed_font) installed_font = ON_Font::InstalledFontList().FromPostScriptName(rich_text_font_name); #endif if (nullptr == installed_font) installed_font = ON_Font::InstalledFontList().FromWindowsLogfontName(rich_text_font_name); #if !defined(ON_RUNTIME_APPLE) if (nullptr == installed_font) installed_font = ON_Font::InstalledFontList().FromPostScriptName(rich_text_font_name); #endif if (nullptr == installed_font) installed_font = ON_Font::InstalledFontList().FromFamilyName(rich_text_font_name, ON_FontFaceQuartet::MemberToString(ON_FontFaceQuartet::Member::Regular) ); if (nullptr != installed_font) { installed_quartet = installed_font->InstalledFontQuartet(); // only look at installed quartet! const ON_Font* installed_rtfface = installed_quartet.ClosestFace(bBoldQuartetMember, bItalicQuartetMember); if (nullptr != installed_rtfface) installed_font = installed_rtfface; } } if (nullptr != installed_font) { // The font is installed on this device. return installed_font->Internal_DecoratedFont(bUnderlined, bStrikethrough); } ON_wString loc_quartet_name = rich_text_font_name; ON_wString en_quartet_name = rich_text_font_name; ON_wString loc_family_name = rich_text_font_name; ON_wString en_family_name = rich_text_font_name; ON_wString loc_windows_logfont_name = rich_text_font_name; ON_wString en_windows_logfont_name = rich_text_font_name; ON_Font::Weight fake_normal_weight = ON_Font::Weight::Unset; ON_Font::Weight fake_bold_weight = ON_Font::Weight::Unset; if (managed_substitute_quartet.IsNotEmpty()) { // get known names and guesses at weights from the existing parts of the quartet. const ON_Font* normal_weight_font = managed_substitute_quartet.Face(ON_FontFaceQuartet::Member::Regular); if ( nullptr == normal_weight_font) normal_weight_font = managed_substitute_quartet.Face(ON_FontFaceQuartet::Member::Italic); const ON_Font* bold_weight_font = managed_substitute_quartet.Face(ON_FontFaceQuartet::Member::Bold); if (nullptr == bold_weight_font) bold_weight_font = managed_substitute_quartet.Face(ON_FontFaceQuartet::Member::BoldItalic); const ON_Font* names_font = (nullptr != normal_weight_font) ? normal_weight_font : bold_weight_font; if (nullptr != names_font) { Internal_SetFakeNamesFromExistingNames( names_font->QuartetName(ON_Font::NameLocale::Localized), names_font->QuartetName(ON_Font::NameLocale::English), loc_quartet_name, en_quartet_name ); Internal_SetFakeNamesFromExistingNames( names_font->FamilyName(ON_Font::NameLocale::Localized), names_font->FamilyName(ON_Font::NameLocale::English), loc_family_name, en_family_name ); Internal_SetFakeNamesFromExistingNames( names_font->WindowsLogfontName(ON_Font::NameLocale::Localized), names_font->WindowsLogfontName(ON_Font::NameLocale::English), loc_windows_logfont_name, en_windows_logfont_name ); } if (nullptr != normal_weight_font) fake_normal_weight = normal_weight_font->FontWeight(); if (nullptr != bold_weight_font) fake_bold_weight = bold_weight_font->FontWeight(); const unsigned normal_unsigned = static_cast(fake_normal_weight); const unsigned bold_unsigned = static_cast(fake_bold_weight); const unsigned default_normal_unsigned = static_cast(ON_Font::Weight::Normal); const unsigned default_bold_unsigned = static_cast(ON_Font::Weight::Bold); const unsigned min_weight = static_cast(ON_Font::Weight::Thin); const unsigned max_weight = static_cast(ON_Font::Weight::Heavy); if (ON_Font::Weight::Unset != fake_normal_weight && ON_Font::Weight::Unset == fake_bold_weight) { // have to guess at a bold weight fake_bold_weight = normal_unsigned < default_bold_unsigned ? ON_Font::Weight::Bold : ((normal_unsigned + 2 <= max_weight) ? ON_Font::FontWeightFromUnsigned(normal_unsigned + 2) : ON_Font::Weight::Heavy) ; } if (ON_Font::Weight::Unset != fake_bold_weight && ON_Font::Weight::Unset == fake_normal_weight) { // have to guess at a normal weight fake_normal_weight = bold_unsigned > default_normal_unsigned ? ON_Font::Weight::Normal : ((min_weight + 2 <= bold_unsigned) ? ON_Font::FontWeightFromUnsigned(bold_unsigned - 2) : ON_Font::Weight::Thin) ; } } if (ON_Font::Weight::Unset == fake_normal_weight) fake_normal_weight = ON_Font::Weight::Normal; if (ON_Font::Weight::Unset == fake_bold_weight) fake_bold_weight = ON_Font::Weight::Bold; const double point_size = 0.0; const unsigned int logfont_charset = ON_Font::Default.LogfontCharSet(); ON_Font fake_font; fake_font.SetFontCharacteristics( point_size, rich_text_font_name, bBoldQuartetMember ? fake_bold_weight : fake_normal_weight, bItalicQuartetMember ? ON_Font::Style::Italic : ON_Font::Style::Upright, ON_Font::Stretch::Medium, false, // DO NOT PASS bUnderlined here false, // DO NOT PASS bStrikethrough here ON_FontMetrics::DefaultLineFeedRatio, logfont_charset ); fake_font.m_loc_family_name = loc_family_name; fake_font.m_en_family_name = en_family_name; fake_font.m_loc_windows_logfont_name = loc_windows_logfont_name; fake_font.m_en_windows_logfont_name = en_windows_logfont_name; // There is no reliable way to get the face name - we fake it by using the quartet face name below fake_font.m_loc_face_name = ON_FontFaceQuartet::MemberToString(rich_text_quartet_face); fake_font.m_en_face_name = fake_font.m_loc_face_name; // There is no reliable way to get the PostScript name fake_font.m_loc_postscript_name = ON_wString::EmptyString; fake_font.m_en_postscript_name = ON_wString::EmptyString; fake_font.m_quartet_member = rich_text_quartet_face; // When reading 3dm files created on a device with other fonts // Frequently a managed font is added when a dimstyle is read. // Later on annotation objects are read and rich text requests // regular/bold/italic/bold-italic faces from the dimstyle font's // quartet. When existing_managed_font is not nullptr, we need // to create a fake quartet containing that font. // When existing_managed_font is nullptr, we need to create // a fake quartet based on only the rtf_font_name. // (Sure wish we had not dumped the V5 font table, sigh.) const ON_Font* existing_managed_font = ON_Font::GetManagedFont(fake_font, false); if (nullptr != existing_managed_font) { Internal_SetFakeNamesFromExistingNames( existing_managed_font->QuartetName(ON_Font::NameLocale::Localized), existing_managed_font->QuartetName(ON_Font::NameLocale::English), loc_quartet_name, en_quartet_name ); Internal_SetFakeNamesFromExistingNames( existing_managed_font->FamilyName(ON_Font::NameLocale::Localized), existing_managed_font->FamilyName(ON_Font::NameLocale::English), loc_family_name, en_family_name ); Internal_SetFakeNamesFromExistingNames( existing_managed_font->WindowsLogfontName(ON_Font::NameLocale::Localized), existing_managed_font->WindowsLogfontName(ON_Font::NameLocale::English), loc_windows_logfont_name, en_windows_logfont_name ); fake_font.m_loc_family_name = loc_family_name; fake_font.m_en_family_name = en_family_name; fake_font.m_loc_face_name = ON_FontFaceQuartet::MemberToString(rich_text_quartet_face); fake_font.m_en_face_name = fake_font.m_loc_face_name; fake_font.m_loc_windows_logfont_name = loc_windows_logfont_name; fake_font.m_en_windows_logfont_name = en_windows_logfont_name; } // Need to make a fake rich text quartet of managed fonts so rich text // bold and italic faces work as expected. // creating a managed fake font resets the fake quartets // and this fake will get added to the quartets next time // they are needed. const ON_Font* managed_fake_font = ON_Font::GetManagedFont(fake_font, true); if (nullptr == managed_fake_font) { // should never happen return ON_Font::Default.Internal_DecoratedFont(bUnderlined, bStrikethrough); } if (managed_fake_font->IsInstalledFont()) { return managed_fake_font->Internal_DecoratedFont(bUnderlined, bStrikethrough); } // set installed substitute used to render the missing font const ON_Font* installed_substitute = managed_fake_font->SubstituteFont(); if (nullptr == installed_substitute || false == installed_substitute->IsInstalledFont() || rich_text_quartet_face != installed_substitute->m_quartet_member) { // We have better information to select the correct substitute than inside the ON_Font::GetManagedFont(fake_font, true) call above. // Use this information to specify a better substitute font. installed_substitute = ON_Font::Default.InstalledFontQuartet().ClosestFace(rich_text_quartet_face); // only look at installed quartet if (nullptr == installed_substitute) installed_substitute = &ON_Font::Default; const bool bInstalledFontIsASubstitute = true; ON_Font::Internal_SetManagedFontInstalledFont( managed_fake_font, installed_substitute, bInstalledFontIsASubstitute ); } return managed_fake_font->Internal_DecoratedFont(bUnderlined, bStrikethrough); } const ON_wString ON_Font::RichTextPropertiesToString( bool bRtfBold, bool bRtfItalic, bool bRtfUnderlined, bool bRtfStrikethrough ) { return ON_wString::FormatToString( L"%ls%ls%ls", (bRtfBold ? (bRtfItalic ? L"Bold Italic" : L"Bold") : (bRtfItalic ? L"Italic" : L"Regular")), (bRtfUnderlined ? L" Underlined" : L""), (bRtfStrikethrough ? L" Strikethrough" : L"") ); } const ON_wString ON_Font::RichTextPropertiesToString( ON_Font::Weight rtf_weight, ON_Font::Style rtf_style, bool bRtfUnderlined, bool bRtfStrikethrough ) { return RichTextPropertiesToString( ON_Font::IsBoldWeight(rtf_weight), ON_Font::Style::Italic == rtf_style, bRtfUnderlined, bRtfStrikethrough ); } const ON_wString ON_Font::RichTextPropertiesToString( const ON_Font* font ) { if (font == nullptr) font = &ON_Font::Default; return RichTextPropertiesToString( font->FontWeight(), font->FontStyle(), font->IsUnderlined(), font->IsStrikethrough() ); } const ON_Font* ON_Font::ManagedFontFromRichTextProperties( const wchar_t* rtf_font_name, bool bRtfBold, bool bRtfItalic, bool bRftUnderlined, bool bRftStrikethrough ) { // ON_Font::ManagedFontFromRichTextProperties() is deprecated. // ON_Font::FontFromRichTextProperties() is a single point source for // converting rich text font properties into a managed font. return ON_Font::FontFromRichTextProperties(rtf_font_name, bRtfBold, bRtfItalic, bRftUnderlined, bRftStrikethrough); } const ON_2dex ON_FontList::Internal_SearchSortedList( const ON_Font* key, ON_FontPtrCompareFunc compare_func, const ON_SimpleArray< const ON_Font* >& sorted_font_list ) { for (;;) { if (nullptr == key || nullptr == compare_func) break; const int sorted_count = sorted_font_list.Count(); if (sorted_count <= 0) break; const int k = sorted_font_list.BinarySearch(&key, compare_func); if (k < 0 || k >= sorted_count) break; int i0 = k; while (i0 > 0 && 0 == compare_func(&key, &(sorted_font_list[i0 - 1]) )) i0--; int i1 = k+1; while (i1 < sorted_count && 0 == compare_func(&key, &(sorted_font_list[i1]) )) i1++; return ON_2dex(i0, i1); } return ON_2dex(ON_UNSET_INT_INDEX,ON_UNSET_INT_INDEX); } void ON_FontList::Internal_EmptyLists() { m_by_index.SetCount(0); m_unsorted.SetCount(0); m_sorted.m_by_font_characteristics_hash.SetCount(0); m_sorted.m_by_postscript_name.SetCount(0); m_sorted.m_by_windows_logfont_name.SetCount(0); m_sorted.m_by_family_name.SetCount(0); m_sorted.m_by_english_postscript_name.SetCount(0); m_sorted.m_by_english_windows_logfont_name.SetCount(0); m_sorted.m_by_english_family_name.SetCount(0); m_sorted.m_by_quartet_name.SetCount(0); m_quartet_list.Destroy(); } static int Internal_CompareLogfontNameEtc(ON_Font const* const* lhs, ON_Font const* const* rhs) { int rc = ON_FontList::CompareWindowsLogfontName(lhs, rhs); if (0 != rc) return rc; rc = ON_FontList::CompareWeightStretchStyle(lhs, rhs); if (0 != rc) return rc; rc = ON_FontList::CompareUnderlinedStrikethroughPointSize(lhs, rhs); if (0 != rc) return rc; rc = ON_FontList::CompareFontCharacteristicsHash(lhs, rhs); if (0 != rc) return rc; return 0; } static int Internal_CompareFamilyNameEtc(ON_Font const* const* lhs, ON_Font const* const* rhs) { int rc = ON_FontList::CompareFamilyAndFaceName(lhs, rhs); if (0 != rc) return rc; return Internal_CompareLogfontNameEtc(lhs, rhs); } static int Internal_ComparePostScriptNameEtc(ON_Font const* const* lhs, ON_Font const* const* rhs) { int rc = ON_FontList::ComparePostScriptName(lhs, rhs); if (0 != rc) return rc; return Internal_CompareFamilyNameEtc(lhs, rhs); } static int Internal_CompareEnglishLogfontNameEtc(ON_Font const* const* lhs, ON_Font const* const* rhs) { int rc = ON_FontList::CompareEnglishWindowsLogfontName(lhs, rhs); if (0 != rc) return rc; rc = ON_FontList::CompareWeightStretchStyle(lhs, rhs); if (0 != rc) return rc; rc = ON_FontList::CompareUnderlinedStrikethroughPointSize(lhs, rhs); if (0 != rc) return rc; return ON_FontList::CompareFontCharacteristicsHash(lhs, rhs); } static int Internal_CompareEnglishFamilyNameEtc(ON_Font const* const* lhs, ON_Font const* const* rhs) { int rc = ON_FontList::CompareEnglishFamilyAndFaceName(lhs, rhs); if (0 != rc) return rc; return Internal_CompareEnglishLogfontNameEtc(lhs, rhs); } static int Internal_CompareEnglishPostScriptNameEtc(ON_Font const* const* lhs, ON_Font const* const* rhs) { int rc = ON_FontList::CompareEnglishPostScriptName(lhs, rhs); if (0 != rc) return rc; return Internal_CompareEnglishFamilyNameEtc(lhs, rhs); } static int Internal_CompareQuartetNameEtc(ON_Font const* const* lhs, ON_Font const* const* rhs) { int rc = ON_FontList::CompareQuartetName(lhs, rhs); if (0 != rc) return rc; rc = ON_FontList::CompareWeightStretchStyle(lhs, rhs); if (0 != rc) return rc; rc = ON_FontList::CompareUnderlinedStrikethroughPointSize(lhs, rhs); if (0 != rc) return rc; return ON_FontList::CompareFontCharacteristicsHash(lhs, rhs); } static int Internal_CompareFontCharacteristicsHashEtc(ON_Font const* const* lhs, ON_Font const* const* rhs) { int rc = ON_FontList::CompareFontCharacteristicsHash(lhs, rhs); if (0 != rc) return rc; return Internal_CompareFamilyNameEtc(lhs, rhs); } void ON_FontList::Internal_UpdateSortedLists() const { const int unsorted_count = m_unsorted.Count(); if (unsorted_count <= 0) return; ON_SimpleArray< const ON_Font* >* sorted_lists[8] = { &m_sorted.m_by_postscript_name, &m_sorted.m_by_windows_logfont_name, &m_sorted.m_by_family_name, &m_sorted.m_by_english_postscript_name, &m_sorted.m_by_english_windows_logfont_name, &m_sorted.m_by_english_family_name, &m_sorted.m_by_quartet_name, &m_sorted.m_by_font_characteristics_hash, }; ON_FontPtrCompareFunc compare_funcs[8] = { Internal_ComparePostScriptNameEtc, Internal_CompareLogfontNameEtc, Internal_CompareFamilyNameEtc, Internal_CompareEnglishPostScriptNameEtc, Internal_CompareEnglishLogfontNameEtc, Internal_CompareEnglishFamilyNameEtc, Internal_CompareQuartetNameEtc, Internal_CompareFontCharacteristicsHashEtc }; const int array_dex_max = (int)(sizeof(sorted_lists) / sizeof(sorted_lists[0])); for (int array_dex = 0; array_dex < array_dex_max; array_dex++) { ON_SimpleArray< const ON_Font* >& sorted_list = *(sorted_lists[array_dex]); bool bNeedSort = false; sorted_list.Reserve(sorted_list.Count() + unsorted_count); for (int j = 0; j < unsorted_count; j++) { const ON_Font* font = m_unsorted[j]; if (nullptr == font) continue; if (0 == array_dex) { if (font->PostScriptName(m_name_locale).IsEmpty()) continue; } else if (1 == array_dex) { if (font->WindowsLogfontName(m_name_locale).IsEmpty()) continue; } else if (2 == array_dex) { if (font->FamilyName(m_name_locale).IsEmpty()) continue; } else if (3 == array_dex) { const ON_wString en = font->PostScriptName(ON_Font::NameLocale::English); if (en.IsEmpty()) continue; if (ON_wString::EqualOrdinal(en, font->PostScriptName(ON_Font::NameLocale::English), true)) continue; } else if (4 == array_dex) { const ON_wString en = font->WindowsLogfontName(ON_Font::NameLocale::English); if (en.IsEmpty()) continue; if (ON_wString::EqualOrdinal(en, font->WindowsLogfontName(ON_Font::NameLocale::English), true)) continue; } else if (5 == array_dex) { const ON_wString en = font->FamilyName(ON_Font::NameLocale::English); if (en.IsEmpty()) continue; if (ON_wString::EqualOrdinal(en, font->FamilyName(ON_Font::NameLocale::English), true)) continue; } else if (6 == array_dex) { const ON_wString qname = font->QuartetName(); if (qname.IsEmpty()) continue; // m_quartet_list[] will get remade when it's needed m_quartet_list.SetCount(0); } else if (7 == array_dex) { const ON_SHA1_Hash sha1 = font->FontCharacteristicsHash(); if (sha1.IsZeroDigestOrEmptyContentHash()) continue; // no valid font wil have either one of these hashes. } else { ON_ERROR("When you add an array to ON_FontListImpl, you must add a corresponding if clause here."); } sorted_list.Append(font); bNeedSort = true; } if ( bNeedSort ) sorted_list.QuickSort(compare_funcs[array_dex]); } m_unsorted.SetCount(0); } ON_FontList::ON_FontList() : m_sorted(*(new ON_FontListImpl())) {} ON_FontList::ON_FontList( bool bMatchUnderlineAndStrikethrough ) : m_bMatchUnderlineStrikethroughAndPointSize(bMatchUnderlineAndStrikethrough) , m_sorted(*(new ON_FontListImpl())) {} ON_FontList::~ON_FontList() { ON_FontListImpl* ptr = &m_sorted; if (nullptr != ptr) { delete ptr; } } unsigned int ON_FontList::Count() const { return m_by_index.UnsignedCount(); } ON_Font::NameLocale ON_FontList::NameLocale() const { return m_name_locale; } const ON_Font* ON_FontList::FromFontCharacteristicsHash( ON_SHA1_Hash font_characteristics_hash, bool bReturnFirst) const { if (font_characteristics_hash.IsZeroDigestOrEmptyContentHash()) return nullptr; const ON_SimpleArray& by_hash = ByFontCharacteristicsHash(); // ON_FontList::CompareFontCharacteristicsHash() only looks at m_font_characteristics_hash // and m_font_characteristics_hash must not be zero content hash (handled above). const ON_Font keyf; keyf.m_font_characteristics_hash = font_characteristics_hash; const ON_Font* key = &keyf; const int i = by_hash.BinarySearch(&key, ON_FontList::CompareFontCharacteristicsHash); const int count = by_hash.Count(); if (i < 0 || i >= count) return nullptr; int i0 = i; while (i0 > 0 && 0 == ON_FontList::CompareFontCharacteristicsHash(&key, &by_hash[i0 - 1])) --i0; int i1 = i; while (i1 + 1 < count && 0 == ON_FontList::CompareFontCharacteristicsHash(&key, &by_hash[i1 + 1])) ++i1; if (i0 == i1) { // The unique installed font with this hash. return by_hash[i0]; } if (bReturnFirst) { // the first of multiple fonts with this hash. return by_hash[i0]; } return nullptr; } const ON_Font* ON_FontList::FromPostScriptName( const wchar_t* postscript_name ) const { return FromPostScriptName(postscript_name, ON_Font::Weight::Normal, ON_Font::Stretch::Condensed, ON_Font::Style::Upright, false, false); } const ON_Font* ON_FontList::FromPostScriptName( const wchar_t* postscript_name, ON_Font::Weight prefered_weight, ON_Font::Stretch prefered_stretch, ON_Font::Style prefered_style ) const { return FromNames( postscript_name, nullptr, nullptr, nullptr, prefered_weight, prefered_stretch, prefered_style, false, false ); } const ON_Font* ON_FontList::FromPostScriptName( const wchar_t* postscript_name, ON_Font::Weight prefered_weight, ON_Font::Stretch prefered_stretch, ON_Font::Style prefered_style, bool bUnderlined, bool bStrikethrough ) const { return FromNames( postscript_name, nullptr, nullptr, nullptr, prefered_weight, prefered_stretch, prefered_style, false, false, bUnderlined, bStrikethrough, 0.0 ); } const ON_Font* ON_FontList::FromWindowsLogfontName( const wchar_t* windows_logfont_name ) const { return FromWindowsLogfontName(windows_logfont_name, ON_Font::Weight::Normal, ON_Font::Stretch::Condensed, ON_Font::Style::Upright); } const ON_Font* ON_FontList::FromWindowsLogfontName( const wchar_t* windows_logfont_name, ON_Font::Weight prefered_weight, ON_Font::Stretch prefered_stretch, ON_Font::Style prefered_style ) const { return FromNames( nullptr, windows_logfont_name, nullptr, nullptr, prefered_weight, prefered_stretch, prefered_style, false, false ); } const ON_Font* ON_FontList::FromWindowsLogfontName( const wchar_t* windows_logfont_name, ON_Font::Weight prefered_weight, ON_Font::Stretch prefered_stretch, ON_Font::Style prefered_style, bool bUnderlined, bool bStrikethrough ) const { return FromNames( nullptr, windows_logfont_name, nullptr, nullptr, prefered_weight, prefered_stretch, prefered_style, false, false, bUnderlined, bStrikethrough, 0.0 ); } const ON_Font* ON_FontList::FromFamilyName( const wchar_t* family_name, const wchar_t* prefered_face_name ) const { return FromFamilyName(family_name, prefered_face_name, ON_Font::Weight::Normal, ON_Font::Stretch::Medium, ON_Font::Style::Upright); } const ON_Font* ON_FontList::FromFamilyName( const wchar_t* family_name, const wchar_t* prefered_face_name, ON_Font::Weight prefered_weight, ON_Font::Stretch prefered_stretch, ON_Font::Style prefered_style ) const { return FromNames( nullptr, nullptr, family_name, prefered_face_name, prefered_weight, prefered_stretch, prefered_style, false, false ); } const ON_Font* ON_FontList::FromFamilyName( const wchar_t* family_name, const wchar_t* prefered_face_name, ON_Font::Weight prefered_weight, ON_Font::Stretch prefered_stretch, ON_Font::Style prefered_style, bool bUnderlined, bool bStrikethrough ) const { return FromNames( nullptr, nullptr, family_name, prefered_face_name, prefered_weight, prefered_stretch, prefered_style, false, false, bUnderlined, bStrikethrough, 0.0 ); } const ON_Font* ON_FontList::FromRichTextProperties( const wchar_t* rtf_font_name, bool bRtfBold, bool bRtfItalic, bool bUnderlined, bool bStrikethrough ) const { // ballpark weight and stretch values. Closest match is returned. const ON_Font::Weight prefered_weight = bRtfBold ? ON_Font::Weight::Bold : ON_Font::Weight::Normal; const ON_Font::Stretch prefered_stretch = ON_Font::Stretch::Medium; const ON_Font::Style prefered_style = bRtfItalic ? ON_Font::Style::Italic : ON_Font::Style::Upright; bool bRequireFaceMatch = false; bool bRequireStyleMatch = true; return FromNames( rtf_font_name, // PostScript guess rtf_font_name, // Windows LOGFONT.lfFaceName guess rtf_font_name, // Family name guess nullptr, prefered_weight, prefered_stretch, prefered_style, bRequireFaceMatch, bRequireStyleMatch, bUnderlined, bStrikethrough, 0.0 ); } const ON_Font* ON_FontList::FromNames( const wchar_t* postscript_name, const wchar_t* windows_logfont_name, const wchar_t* family_name, const wchar_t* prefered_face_name, ON_Font::Weight prefered_weight, ON_Font::Stretch prefered_stretch, ON_Font::Style prefered_style, bool bRequireFaceMatch, bool bRequireStyleMatch ) const { const bool bMatchUnderlineStrikethroughAndPointSize = false; return Internal_FromNames( postscript_name, windows_logfont_name, family_name, prefered_face_name, prefered_weight, prefered_stretch, prefered_style, bRequireFaceMatch, bRequireStyleMatch, bMatchUnderlineStrikethroughAndPointSize, false, false, 0.0 ); } const ON_Font* ON_FontList::FromNames( const wchar_t* postscript_name, const wchar_t* windows_logfont_name, const wchar_t* family_name, const wchar_t* prefered_face_name, ON_Font::Weight prefered_weight, ON_Font::Stretch prefered_stretch, ON_Font::Style prefered_style, bool bRequireFaceMatch, bool bRequireStyleMatch, bool bUnderlined, bool bStrikethrough, double point_size ) const { bool bMatchUnderlineStrikethroughAndPointSize = true; return Internal_FromNames( postscript_name, windows_logfont_name, family_name, prefered_face_name, prefered_weight, prefered_stretch, prefered_style, bRequireFaceMatch, bRequireStyleMatch, bMatchUnderlineStrikethroughAndPointSize, bUnderlined, bStrikethrough, point_size ); } const ON_Font* ON_FontList::Internal_FromNames( const wchar_t* postscript_name, const wchar_t* windows_logfont_name, const wchar_t* family_name, const wchar_t* prefered_face_name, ON_Font::Weight prefered_weight, ON_Font::Stretch prefered_stretch, ON_Font::Style prefered_style, bool bRequireFaceMatch, bool bRequireStyleMatch, bool bMatchUnderlineStrikethroughAndPointSize, bool bUnderlined, bool bStrikethrough, double point_size ) const { if (ON_Font::Stretch::Unset == prefered_stretch) bRequireStyleMatch = false; if (bUnderlined) bUnderlined = true; if (bStrikethrough) bStrikethrough = true; if (!(point_size > 0.0 && point_size < ON_UNSET_POSITIVE_FLOAT)) point_size = 0.0; if (false == m_bMatchUnderlineStrikethroughAndPointSize) bMatchUnderlineStrikethroughAndPointSize = false; const ON_SimpleArray< const ON_Font* > * sorted_lists[16] = {}; ON_FontPtrCompareFunc compare_funcs[16] = { 0 }; ON_Font key; key.m_loc_postscript_name = postscript_name; key.m_loc_postscript_name.TrimLeftAndRight(); key.m_en_postscript_name = key.m_loc_postscript_name; key.m_loc_windows_logfont_name = windows_logfont_name; key.m_loc_windows_logfont_name.TrimLeftAndRight(); key.m_en_windows_logfont_name = key.m_loc_windows_logfont_name; key.m_loc_family_name = family_name; key.m_loc_family_name.TrimLeftAndRight(); key.m_en_family_name = key.m_loc_family_name; key.m_loc_face_name = prefered_face_name; key.m_loc_face_name.TrimLeftAndRight(); key.m_en_face_name = key.m_loc_face_name; key.m_font_weight = prefered_weight; key.m_font_stretch = prefered_stretch; key.m_font_style = prefered_style; const bool bKeyHasFamilyAndFace = key.m_loc_family_name.IsNotEmpty() && key.m_loc_face_name.IsNotEmpty(); const bool bKeyHasPostScriptName = key.m_loc_postscript_name.IsNotEmpty(); const bool bKeyWindowsLogfontName = key.m_loc_windows_logfont_name.IsNotEmpty(); if (false == bKeyHasFamilyAndFace) bRequireFaceMatch = false; int pass_count = 0; int postscript_name_pass[2] = { -1, -1 }; // First compare family AND face name. In general, there will not be multiple // fonts with the same family and face name combination. if (bKeyHasFamilyAndFace) { sorted_lists[pass_count] = &m_sorted.m_by_family_name; compare_funcs[pass_count] = ON_FontList::CompareFamilyAndFaceName; pass_count++; sorted_lists[pass_count] = &m_sorted.m_by_english_family_name; compare_funcs[pass_count] = ON_FontList::CompareEnglishFamilyAndFaceName; pass_count++; } #if defined(ON_RUNTIME_WIN) // On Windows, check LOGFONT.lfFaceName before PostScript // It is common for 4 distinct faces to have the same LOGFONT lfFaceName. if (bKeyWindowsLogfontName) { sorted_lists[pass_count] = &m_sorted.m_by_windows_logfont_name; compare_funcs[pass_count] = ON_FontList::CompareWindowsLogfontName; pass_count++; sorted_lists[pass_count] = &m_sorted.m_by_english_windows_logfont_name; compare_funcs[pass_count] = ON_FontList::CompareEnglishWindowsLogfontName; pass_count++; } #endif // Check PostScript name // On Windows, when simulated fonts or OpenType variable face fonts are in use, // it is very common for distict faces to have the same PostScript font name. // It is less common in MacOS. if (bKeyHasPostScriptName) { sorted_lists[pass_count] = &m_sorted.m_by_postscript_name; compare_funcs[pass_count] = ON_FontList::ComparePostScriptName; postscript_name_pass[0] = pass_count; pass_count++; sorted_lists[pass_count] = &m_sorted.m_by_english_postscript_name; compare_funcs[pass_count] = ON_FontList::CompareEnglishPostScriptName; postscript_name_pass[1] = pass_count; pass_count++; } #if !defined(ON_RUNTIME_WIN) // Windows LOGFONT.lfFaceName checked after PostScript // It is common for 4 distinct faces to have the same LOGFONT lfFaceName. if (bKeyWindowsLogfontName) { sorted_lists[pass_count] = &m_sorted.m_by_windows_logfont_name; compare_funcs[pass_count] = ON_FontList::CompareWindowsLogfontName; pass_count++; sorted_lists[pass_count] = &m_sorted.m_by_english_windows_logfont_name; compare_funcs[pass_count] = ON_FontList::CompareEnglishWindowsLogfontName; pass_count++; } #endif // The final passes search by Family name. // It is generally the case that multiple faces have the same family name. if (key.m_loc_family_name.IsNotEmpty()) { sorted_lists[pass_count] = &m_sorted.m_by_family_name; compare_funcs[pass_count] = ON_FontList::CompareFamilyName; pass_count++; sorted_lists[pass_count] = &m_sorted.m_by_english_family_name; compare_funcs[pass_count] = ON_FontList::CompareEnglishFamilyName; pass_count++; } if (pass_count <= 0) return nullptr; // Move any unsorted fonts into the sorted lists. Internal_UpdateSortedLists(); const ON_Font* font = nullptr; unsigned int font_dev = 0xFFFFFFFF; const ON_Font* pkey = &key; const ON_Font* postscript_name_match_candidate = nullptr; for (int pass = 0; pass < pass_count; pass++) { ON_FontPtrCompareFunc compare_func = compare_funcs[pass]; if (nullptr == compare_func) continue; const ON_Font* candidate = nullptr; unsigned int candidate_dev = 0xFFFFFFFF; for (int list_dex = 0; list_dex < 2; list_dex++) { if (1 == list_dex) { if (nullptr == sorted_lists[pass]) continue; } const ON_SimpleArray< const ON_Font* >& sorted_list = (1==list_dex) ? *(sorted_lists[pass]) : m_unsorted; const ON_2dex subset = (1==list_dex) ? ON_FontList::Internal_SearchSortedList(&key, compare_func, sorted_list) : ON_2dex(0,m_unsorted.Count()) ; if (subset.i < 0 || subset.j <= 0) continue; for (int i = subset.i; i < subset.j; i++) { candidate = sorted_list[i]; if (nullptr == candidate) continue; if (0 == list_dex) { if (0 != compare_func(&pkey, &candidate)) continue; } // If we're on a Mac, the PostScript name is the most reliable identification // and it overrides the subsequent name tests when we have a single installed font that is an exact match. const bool bUniquePostScriptNameMatch = (subset.i+1 == subset.j) && (pass == postscript_name_pass[0] || pass == postscript_name_pass[1]) ; if (bUniquePostScriptNameMatch) { if (nullptr == postscript_name_match_candidate) postscript_name_match_candidate = candidate; else if (postscript_name_match_candidate != candidate) { // localized and english names produced different candidates. postscript_name_match_candidate = nullptr; } } if (bMatchUnderlineStrikethroughAndPointSize) { if (candidate->IsUnderlined() != bUnderlined) continue; if (candidate->IsStrikethrough() != bStrikethrough) continue; if (candidate->PointSize() != point_size) continue; } if (bRequireStyleMatch && prefered_style != candidate->FontStyle()) continue; const bool bCandidateFamilyAndFaceMatch = bKeyHasFamilyAndFace && (ON_Font::EqualFontFamilyAndFace(&key, candidate) || 0 == ON_FontList::CompareEnglishFamilyAndFaceName(&pkey,&candidate)); if (bRequireFaceMatch && candidate->FamilyName().IsNotEmpty() && false == bCandidateFamilyAndFaceMatch) continue; const bool bFontFamilyAndFaceMatch = bKeyHasFamilyAndFace && (nullptr != font) && (ON_Font::EqualFontFamilyAndFace(&key, font) || 0 == ON_FontList::CompareEnglishFamilyAndFaceName(&pkey,&candidate)); if (bFontFamilyAndFaceMatch && false == bCandidateFamilyAndFaceMatch) continue; candidate_dev = ON_Font::WeightStretchStyleDeviation(prefered_weight, prefered_stretch, prefered_style, candidate); // On Apple, we need to try all passes to make sure we check the postscript name. // On Apple platforms we have to keep testing if (0 == candidate_dev) { #if defined(ON_RUNTIME_APPLE) if (postscript_name_match_candidate == candidate) return candidate; #else if ( bCandidateFamilyAndFaceMatch || false == bKeyHasFamilyAndFace) return candidate; #endif } if ( nullptr == font || (bCandidateFamilyAndFaceMatch && false == bFontFamilyAndFaceMatch) || candidate_dev < font_dev ) { font = candidate; font_dev = candidate_dev; } } } } if (nullptr != postscript_name_match_candidate && font != postscript_name_match_candidate) { if (nullptr == font) { font = postscript_name_match_candidate; } #if defined(ON_RUNTIME_APPLE) else { // ON Apple platforms, the postscript name is the most reliable indentification if (false == ON_wString::EqualOrdinal(font->PostScriptName(), postscript_name_match_candidate->PostScriptName(), true)) font = postscript_name_match_candidate; } #endif } return font; } unsigned int ON_FontList::FontListFromNames( const wchar_t* postscript_name, const wchar_t* windows_logfont_name, const wchar_t* family_name, const wchar_t* face_name, ON_SimpleArray< const ON_Font* >& font_list ) const { const unsigned int font_list_count0 = font_list.UnsignedCount(); ON_Font key; key.m_loc_postscript_name = postscript_name; key.m_loc_postscript_name.TrimLeftAndRight(); key.m_en_postscript_name = key.m_loc_postscript_name; key.m_loc_windows_logfont_name = windows_logfont_name; key.m_loc_windows_logfont_name.TrimLeftAndRight(); key.m_en_windows_logfont_name = key.m_loc_windows_logfont_name; key.m_loc_family_name = family_name; key.m_loc_family_name.TrimLeftAndRight(); key.m_en_family_name = key.m_loc_family_name; Internal_UpdateSortedLists(); for (int pass = 0; pass < 3; pass++) { ON_SimpleArray< const ON_Font* >* sorted_list = nullptr; ON_2dex subset(-1, -1); switch (pass) { case 0: if (key.m_loc_postscript_name.IsEmpty()) continue; sorted_list = &m_sorted.m_by_postscript_name; subset = Internal_SearchSortedList(&key, ON_FontList::ComparePostScriptName, *sorted_list); break; case 1: if (key.m_loc_windows_logfont_name.IsEmpty()) continue; sorted_list = &m_sorted.m_by_windows_logfont_name; subset = Internal_SearchSortedList(&key, ON_FontList::CompareWindowsLogfontName, *sorted_list); break; case 2: if (key.m_loc_family_name.IsEmpty()) continue; sorted_list = &m_sorted.m_by_family_name; subset = Internal_SearchSortedList(&key, ON_FontList::CompareFamilyName, *sorted_list); break; } if (subset.j <= 0) break; const ON_Font* pkey = &key; for (int i = subset.i; i < subset.j; i++) { const ON_Font* font = (*sorted_list)[i]; if ( pass < 1 && key.m_loc_windows_logfont_name.IsNotEmpty() && 0 != ON_FontList::CompareWindowsLogfontName(&pkey, &font) ) continue; if (key.m_loc_family_name.IsNotEmpty()) { if (0 != ON_FontList::CompareFamilyName(&pkey, &font)) continue; if (key.m_loc_face_name.IsNotEmpty() && false == ON_wString::EqualOrdinal(key.FaceName(m_name_locale), font->FaceName(m_name_locale), true) ) continue; } font_list.Append(font); } break; } return font_list.UnsignedCount() - font_list_count0; } const ON_Font* ON_FontList::FromFontProperties( const ON_Font* font_properties, bool bRequireFaceMatch, bool bRequireStyleMatch ) const { return FromNames( font_properties->PostScriptName(m_name_locale), font_properties->WindowsLogfontName(m_name_locale), font_properties->FamilyName(m_name_locale), font_properties->FaceName(m_name_locale), font_properties->FontWeight(), font_properties->FontStretch(), font_properties->FontStyle(), bRequireFaceMatch, bRequireStyleMatch ); } const ON_Font* ON_FontList::FromFontProperties( const ON_Font* font_properties, bool bRequireFaceMatch, bool bRequireStyleMatch, bool bUnderlined, bool bStrikethrough, double point_size ) const { return FromNames( font_properties->PostScriptName(m_name_locale), font_properties->WindowsLogfontName(m_name_locale), font_properties->FamilyName(m_name_locale), font_properties->FaceName(m_name_locale), font_properties->FontWeight(), font_properties->FontStretch(), font_properties->FontStyle(), bRequireFaceMatch, bRequireStyleMatch, bUnderlined, bStrikethrough, point_size ); } const ON_Font* ON_FontList::FamilyMemberWithWeightStretchStyle( const wchar_t* family_name, ON_Font::Weight desired_weight, ON_Font::Stretch desired_stretch, ON_Font::Style desired_style ) const { ON_Font key; key.m_loc_family_name = family_name; key.m_loc_family_name.TrimLeftAndRight(); if (key.m_loc_family_name.IsEmpty()) key.m_loc_family_name = ON_Font::DefaultFamilyName(); else key.m_en_family_name = key.m_loc_family_name; key.m_font_weight = desired_weight; key.m_font_stretch = desired_stretch; key.m_font_style = desired_style; const ON_2dex subdex = Internal_SearchSortedList(&key, ON_FontList::CompareFamilyName, m_sorted.m_by_family_name); if (subdex.j <= 0) return nullptr; const ON_Font* candidate = nullptr; unsigned int candidate_dev = 0xFFFFFFFF; for (int i = subdex.i; i < subdex.j; i++) { const ON_Font* font = m_sorted.m_by_family_name[i]; if (nullptr == font) continue; unsigned int font_dev = ON_Font::WeightStretchStyleDeviation(&key, font); if (nullptr == candidate || font_dev < candidate_dev) { candidate = font; candidate_dev = font_dev; } } return candidate; } const ON_Font* ON_FontList::FamilyMemberWithWeightStretchStyle( const ON_Font* font, ON_Font::Weight desired_weight, ON_Font::Stretch desired_stretch, ON_Font::Style desired_style ) const { if (nullptr == font) font = &ON_Font::Default; else if ( font->FamilyName().IsEmpty() && font->FaceName().IsEmpty() && font->WindowsLogfontName().IsEmpty() ) font = &ON_Font::Default; if (ON_Font::Weight::Unset == desired_weight) desired_weight = font->FontWeight(); if (ON_Font::Stretch::Unset == desired_stretch) desired_stretch = font->FontStretch(); if (ON_Font::Style::Unset == desired_style) desired_style = font->FontStyle(); ON_wString family_name = font->FamilyName(); if (family_name.IsEmpty()) { const ON_SimpleArray< const ON_Font* > * sorted_lists[2] = { &m_sorted.m_by_windows_logfont_name, &m_sorted.m_by_postscript_name }; ON_FontPtrCompareFunc compare_funcs[2] = {ON_FontList::CompareWindowsLogfontName,ON_FontList::ComparePostScriptName}; const bool bNameIsEmpty[2] = { font->WindowsLogfontName().IsEmpty(),font->PostScriptName().IsEmpty() }; const int k1 = (int)(sizeof(bNameIsEmpty) / sizeof(bNameIsEmpty[0])); for (int k = 0; k < k1 && family_name.IsEmpty(); k++) { if (bNameIsEmpty[k]) continue; const ON_SimpleArray< const ON_Font* >& sorted_list = *sorted_lists[k]; const ON_2dex subdex = Internal_SearchSortedList(font, compare_funcs[k], sorted_list); if (subdex.j <= 0) continue; for (int i = subdex.i; i < subdex.j && family_name.IsEmpty(); i++) { const ON_Font* f = sorted_list[i]; if (nullptr == f) continue; family_name = f->FamilyName(); if (family_name.IsNotEmpty()) break; } } } const ON_Font* family_member = FamilyMemberWithWeightStretchStyle(family_name, desired_weight, desired_stretch, desired_style); return family_member; } const ON_Font* ON_Font::InstalledFamilyMemberWithWeightStretchStyle( ON_Font::Weight desired_weight, ON_Font::Stretch desired_stretch, ON_Font::Style desired_style ) const { return ON_Font::InstalledFontList().FamilyMemberWithWeightStretchStyle( this, desired_weight, desired_stretch, desired_style ); } const ON_Font* ON_Font::ManagedFamilyMemberWithRichTextProperties( bool bBold, bool bItalic, bool bUnderlined, bool bStrikethrough ) const { // If this doesn't work, do not add code here. // // 1. If there's an obvious bug in ON_Font::ManagedFontFromRichTextProperties(), fix it. // // 2. If this is an installed font and the Platform is Mac and you don't like the results, // then you probably need to hand tweak the fake Apple quartets we create in // ON_ManagedFonts::Internal_SetFakeWindowsLogfontNames(). That code is Apple specific. // // 3. If this is an installed font and the Platform is Windows and you don't like the results, // then your input font is probably generating a garbage for this->RichTextFontName() and // that issue should be fixed upstream. // // 4. Ask Dale Lear for help. return ON_Font::FontFromRichTextProperties(this->RichTextFontName(), bBold, bItalic, bUnderlined, bStrikethrough); } const ON_Font* ON_Font::ManagedFamilyMemberWithWeightStretchStyle( ON_Font::Weight desired_weight, ON_Font::Stretch desired_stretch, ON_Font::Style desired_style, bool bUnderlined, bool bStrikethrough ) const { if (ON_Font::Weight::Unset == desired_weight) desired_weight = FontWeight(); if (ON_Font::Stretch::Unset == desired_stretch) desired_stretch = FontStretch(); if (ON_Font::Style::Unset == desired_style) desired_style = FontStyle(); bool bChangeWeight = (desired_weight != FontWeight()); bool bChangeStretch = (desired_stretch != FontStretch()); bool bChangeStyle = (desired_style != FontStyle()); bool bChangeUnderlined = ((bUnderlined ? true : false) != IsUnderlined()); bool bChangeStrikethrough = ((bStrikethrough ? true : false) != IsStrikethrough()); bool bChangeSomething = bChangeWeight || bChangeStretch || bChangeStyle || bChangeUnderlined || bChangeStrikethrough; const ON_Font* font = this; if ( bChangeWeight || bChangeStretch || bChangeStyle ) { const ON_Font* installed_font = InstalledFamilyMemberWithWeightStretchStyle(desired_weight, desired_stretch, desired_style); if (nullptr != installed_font) { font = installed_font; bChangeWeight = false; bChangeStretch = false; bChangeStyle = false; bChangeUnderlined = (bUnderlined ? true : false) != installed_font->IsUnderlined(); bChangeStrikethrough = ((bStrikethrough ? true : false) != installed_font->IsStrikethrough()); bChangeSomething = bChangeWeight || bChangeStretch || bChangeStyle || bChangeUnderlined || bChangeStrikethrough; } } if (false == bChangeSomething) return font->ManagedFont(); ON_Font changed_font(*font); if (bChangeWeight && ON_Font::Weight::Unset != desired_weight) changed_font.SetFontWeight(desired_weight); if (bChangeStretch && ON_Font::Stretch::Unset != desired_stretch) changed_font.SetFontStretch(desired_stretch); if (bChangeStyle && ON_Font::Style::Unset != desired_style) changed_font.SetFontStyle(desired_style); if (bChangeUnderlined) changed_font.SetUnderlined(bUnderlined); if (bChangeStrikethrough) changed_font.SetStrikethrough(bStrikethrough); return changed_font.ManagedFont(); } const ON_SimpleArray< const class ON_Font* >& ON_FontList::ByPostScriptName() const { Internal_UpdateSortedLists(); return m_sorted.m_by_postscript_name; } const ON_SimpleArray< const class ON_Font* >& ON_FontList::ByWindowsLogfontName() const { Internal_UpdateSortedLists(); return m_sorted.m_by_windows_logfont_name; } const ON_SimpleArray< const class ON_Font* >& ON_FontList::ByFamilyName() const { Internal_UpdateSortedLists(); return m_sorted.m_by_family_name; } const ON_SimpleArray< const class ON_Font* >& ON_FontList::ByQuartetName() const { Internal_UpdateSortedLists(); return m_sorted.m_by_quartet_name; } const ON_SimpleArray< const class ON_Font* >& ON_FontList::ByFontCharacteristicsHash() const { Internal_UpdateSortedLists(); return m_sorted.m_by_font_characteristics_hash; } const ON_SimpleArray< const class ON_Font* >& ON_FontList::ByIndex() const { return m_by_index; } // 24 Oct 2022 A. Cormier (RH-64285) // This functions attempts to find the local name of a font when having the english name. // A binary search would be better than a for loop but a binary search doesn't work when searching // for an english name static ON_wString ON_FontList__EnNameToLocName(const ON_wString& english_name) { const ON_SimpleArray& fonts = ON_Font::InstalledFontList().ByIndex(); for (int i = 0; i < fonts.Count(); i++) { const ON_Font* font = fonts[i]; if (nullptr == font) continue; ON_wString en_qname = font->QuartetName(ON_Font::NameLocale::English); if (ON_wString::CompareOrdinal(english_name, en_qname, true) == 0) { return font->QuartetName(); } } return ON_wString::EmptyString; } const ON_FontFaceQuartet ON_FontList::QuartetFromQuartetName( const wchar_t* quartet_name ) const { for (;;) { ON_FontFaceQuartet qname(quartet_name, nullptr, nullptr, nullptr, nullptr); if (qname.QuartetName().IsEmpty()) break; const ON_ClassArray& quartet_list = ON_FontList::QuartetList(); const int quartet_list_count = quartet_list.Count(); int i = quartet_list.BinarySearch(&qname, ON_FontFaceQuartet::CompareQuartetName); bool failedOnFirstTry = i < 0 || i >= quartet_list_count; if (failedOnFirstTry) { // ... then find the localized name and try again. // The QuartetNames in the QuartetList above are localized but sometimes the quartet_name passed to // this function is in english. In many cases the localized name and the english name are the same // so there is no problem. It is a problem for Japanese and Chinese languages for example. RH-64285 ON_wString locName = ON_FontList__EnNameToLocName(quartet_name); if (locName.IsEmpty()) { break; } qname = ON_FontFaceQuartet(locName, nullptr, nullptr, nullptr, nullptr); i = quartet_list.BinarySearch(&qname, ON_FontFaceQuartet::CompareQuartetName); if (i < 0 || i >= quartet_list_count) { break; } } while (i > 0 && 0 == ON_FontFaceQuartet::CompareQuartetName(&qname, &quartet_list[i - 1])) i--; return quartet_list[i]; } return ON_FontFaceQuartet::Empty; } const ON_Font* ON_FontList::FontFromQuartetProperties( const wchar_t* quartet_name, bool bBold, bool bItalic ) const { const ON_FontFaceQuartet qname(quartet_name, nullptr, nullptr, nullptr, nullptr); if (qname.QuartetName().IsEmpty()) return nullptr; const ON_ClassArray< ON_FontFaceQuartet >& quartet_list = ON_FontList::QuartetList(); const int quartet_list_count = quartet_list.Count(); int i = quartet_list.BinarySearch(&qname, ON_FontFaceQuartet::CompareQuartetName); if (i < 0 || i >= quartet_list_count) return nullptr; while (i > 0 && 0 == ON_FontFaceQuartet::CompareQuartetName(&qname, &quartet_list[i - 1])) i--; do { const ON_FontFaceQuartet& q = quartet_list[i]; if (0 != ON_FontFaceQuartet::CompareQuartetName(&qname, &q)) break; const ON_Font* font = q.Face(bBold, bItalic); if (nullptr != font) return font; i++; } while (i < quartet_list_count); return nullptr; } ON_3udex Internal_StretchSlantWeightDex(unsigned max_stretch_dex, unsigned max_weight_dex, const ON_Font* f) { for (;;) { if (nullptr == f) break; const unsigned stretch_dex = static_cast(f->FontStretch()); if (stretch_dex < 1 || stretch_dex >= max_stretch_dex) break; const unsigned slant_dex = (ON_Font::Style::Italic == f->FontStyle() || ON_Font::Style::Oblique == f->FontStyle()) ? 1U : 0U; const unsigned weight_dex = static_cast(f->FontWeight()); if (weight_dex < 1 || weight_dex >= max_weight_dex) break; return ON_3udex(stretch_dex, slant_dex, weight_dex); } return ON_3udex(ON_UNSET_UINT_INDEX, ON_UNSET_UINT_INDEX, ON_UNSET_UINT_INDEX); } const ON_ClassArray< ON_FontFaceQuartet >& ON_FontList::QuartetList() const { // call ByQuartetName() first to insure m_quartet_list[] is set correctly. const ON_SimpleArray& a = this->ByQuartetName(); if (m_quartet_list.Count() > 0) return m_quartet_list; const unsigned int font_count = a.UnsignedCount(); m_quartet_list.Reserve(32 + font_count / 4); const ON_Font* f = nullptr; const unsigned max_stretch_dex = 10; const unsigned max_weight_dex = 10; // fonts_by_ssw[stretch_dex][upright,italic][weight_dex] // = all fonts with the same quartet name arranged by stretch, slant, and weight. const ON_Font* fonts_by_ssw[11][2][11] = {}; // weight_count[stretch_dex][upright,italic] = number of weights available for that stretch and slant unsigned int weight_count[10][2] = {}; // stretch values range from min stretch = stretch_dex_range[0] to max stretch = stretch_dex_range[1]. unsigned stretch_dex_range[2] = { 0U,0U }; // Fonts with stretch < medium_stretch_dex are "compressed" // Fonts with stretch > medium_stretch_dex are "expanded" // When we have multiple candidates to choose from, we opt for ones closest to medium. const unsigned medium_stretch_dex = (unsigned)static_cast(ON_Font::Stretch::Medium); ON_SimpleArray regular_faces(8); ON_SimpleArray bold_faces(8); ON_SimpleArray italic_faces(8); ON_SimpleArray bolditalic_faces(8); unsigned next_i = 0U; for (unsigned i = 0; i < font_count; i = (i < next_i) ? next_i : (i+1)) { f = a[i]; if (nullptr == f) continue; const ON_wString quartet_name = f->QuartetName(); if (quartet_name.IsEmpty()) continue; // fonts_by_ssw_count = total number of undecorated fonts with the same QuartetName() unsigned fonts_by_ssw_count = 0; // total number of fonts with underline or striketrough rendering effects enabled. unsigned decorated_fonts_count = 0; // fonts_by_ssw[][][] = all the "clean" fonts with the same quartet name sorted by stretch-slant-weight memset(fonts_by_ssw, 0, sizeof(fonts_by_ssw)); memset(weight_count, 0, sizeof(weight_count)); memset(stretch_dex_range, 0, sizeof(stretch_dex_range)); regular_faces.SetCount(0); bold_faces.SetCount(0); italic_faces.SetCount(0); bolditalic_faces.SetCount(0); unsigned int upright_face_count = 0; unsigned int slanted_face_count = 0; const ON_Font* upright_faces[2] = {}; // room to save up to 2 upright faces const ON_Font* slanted_faces[2] = {}; // room to save up to 2 slanted faces // This for() loop sets the array limits for the fonts with the same quartet name // and determines how many passes are needed (substitutes are ignored in favor of // non substitutes). unsigned pass_count = 1; for (next_i = i; next_i < font_count; ++next_i) { f = a[next_i]; if (nullptr == f) break; if (false == quartet_name.EqualOrdinal(f->QuartetName(), true)) break; // f has a different quartet name - we're done getting the fonts in this quartet if (f->IsManagedSubstitutedFont()) pass_count = 2; } // This for() loop gets all the fonts with the same QuartetName() and puts them in fonts_by_ssw[][][]. // While doing that it get ranges of values stretch, counts the number of fonts that have each weight, ... for ( unsigned pass = 0; pass < pass_count; ++pass) for ( unsigned j = i; j < next_i; ++j) { f = a[j]; if (nullptr == f) continue; if (f->IsUnderlined() || f->IsStrikethrough()) { // these are rendering effects and we should have a "clean" version in this list too ++decorated_fonts_count; continue; } if (pass != (f->IsManagedSubstitutedFont() ? 1U : 0U)) continue; const ON_FontFaceQuartet::Member fm = f->m_quartet_member; // use f's stretch-slant-weight to add it to the fonts_by_ssw[][][] array. const ON_3udex ssw_dex = Internal_StretchSlantWeightDex(max_stretch_dex, max_weight_dex, f); const unsigned stretch_dex = ssw_dex.i; if (stretch_dex < 1 || stretch_dex >= max_stretch_dex) continue; const unsigned slant_dex = ssw_dex.j; const unsigned weight_dex = ssw_dex.k; if (weight_dex < 1 || weight_dex >= max_weight_dex) continue; if (nullptr != fonts_by_ssw[stretch_dex][slant_dex][weight_dex]) { // We already found one font with he same quartet name, stretch, slant, and weight. // The first one wins and that's why we use two passes when substitute fonts are involved. continue; } ++fonts_by_ssw_count; fonts_by_ssw[stretch_dex][slant_dex][weight_dex] = f; // add this font's stretch, weight, and slant to the tally for this quartet name. if (1 == fonts_by_ssw_count) { stretch_dex_range[0] = stretch_dex; stretch_dex_range[1] = stretch_dex; } else if (stretch_dex < stretch_dex_range[0]) stretch_dex_range[0] = stretch_dex; else if (stretch_dex > stretch_dex_range[1]) stretch_dex_range[1] = stretch_dex; ++weight_count[stretch_dex][slant_dex]; if (0 == slant_dex) { if (upright_face_count < 2) upright_faces[upright_face_count] = f; ++upright_face_count; } else if (1 == slant_dex) { if (slanted_face_count < 2) slanted_faces[slanted_face_count] = f; ++slanted_face_count; } // if f's role in the quartet is known, add it to the appropriate x_faces[] array. switch (fm) { case ON_FontFaceQuartet::Member::Regular: regular_faces.Append(f); break; case ON_FontFaceQuartet::Member::Bold: bold_faces.Append(f); break; case ON_FontFaceQuartet::Member::Italic: italic_faces.Append(f); break; case ON_FontFaceQuartet::Member::BoldItalic: bolditalic_faces.Append(f); break; case ON_FontFaceQuartet::Member::Unset: break; }; } // fonts_by_ssw_count = number of usable fonts with the same quartet name. // Pointers to these fonts are someplace in the fonts_by_ssw[][][] array. if (0 == fonts_by_ssw_count) continue; // nothing usable // The goal is to look at the fonts in fonts_by_ssw[][][] and select // the best choice for a rich text quartet (which may have 1 to 4 faces). const ON_Font* quartet_faces[2][2] = {}; bool bHaveQuartetFaces = false; const unsigned regular_count = regular_faces.UnsignedCount(); const unsigned bold_count = bold_faces.UnsignedCount(); const unsigned italic_count = italic_faces.UnsignedCount(); const unsigned bolditalic_count = bolditalic_faces.UnsignedCount(); if ( fonts_by_ssw_count == (regular_count + bold_count + italic_count + bolditalic_count) && regular_count <= 1 && bold_count <= 1 && italic_count <= 1 && bolditalic_count <= 1) { // Best case - every font with this quartet name knows the role it plays in the quartet and there are no contradictions. quartet_faces[0][0] = 1 == regular_count ? regular_faces[0] : nullptr; quartet_faces[0][1] = 1 == bold_count ? bold_faces[0] : nullptr; quartet_faces[1][0] = 1 == italic_count ? italic_faces[0] : nullptr; quartet_faces[1][1] = 1 == bolditalic_count ? bolditalic_faces[0] : nullptr; bHaveQuartetFaces = true; } else if (fonts_by_ssw_count == upright_face_count + slanted_face_count && upright_face_count <= 2 && slanted_face_count <= 2 && stretch_dex_range[0] == stretch_dex_range[1] ) { if (2 == upright_face_count && ON_Font::CompareWeight(upright_faces[0]->FontWeight(), upright_faces[1]->FontWeight()) > 0) { f = upright_faces[0]; upright_faces[0] = upright_faces[1]; upright_faces[1] = f; } if (2 == slanted_face_count && ON_Font::CompareWeight(slanted_faces[0]->FontWeight(), slanted_faces[1]->FontWeight()) > 0) { f = slanted_faces[0]; slanted_faces[0] = slanted_faces[1]; slanted_faces[1] = f; } quartet_faces[0][0] = upright_faces[0]; quartet_faces[0][1] = upright_faces[1]; quartet_faces[1][0] = slanted_faces[0]; quartet_faces[1][1] = slanted_faces[1]; bHaveQuartetFaces = true; } if (false == bHaveQuartetFaces) { // 1. Most Windows installed fonts have the Windows LOGFONT name reliably set // from Windows LOGFONT information when we create installed ON_Fonts from // DirectWrite fonts in opennurbs_win_dwrite.cpp. The Windows LOGFONTs partition // families into quartets. These end up with bUsePairCandidate = true. // // 2. In rare cases on Windows, font foundaries or authors fail to specify a LOGFONT name // in the ttc / ttf / postscript / ... file. // If we are able to make a reasonable guess, then we set the member here. // // 3. Apple installed fonts are created from CTFont in opennurbs_apple_nsfont.cpp // and the LOGFONT information from ttc / ttf / postscript files cannot be retrieved. // There are no Mac OS tools that reliably paritition large font families into // quartets. // // 4. Missing fonts that are created in ON_Font::FontFromRichTextProperties() have the quartet // member set to the specified rich text regular/bold/italic/bold-italic property. // // Attempt to find something usable in this mess ... unsigned stretch_dex = medium_stretch_dex; if (stretch_dex_range[0] < stretch_dex_range[1]) { // Need to pick the stretch_dex with the most members. // This happens on Mac OS (where no reliable "LOGFONT" name exists) and // with damaged Windows fonts that don't have a "LOFGONT" name set. // Pick the one closest to ON_Font::Stretch::Medium with the most faces for (unsigned k = 1; k <= medium_stretch_dex; ++k) { const unsigned k0 = medium_stretch_dex - k; const unsigned k1 = medium_stretch_dex + k; if (k0 > 0 && (weight_count[k0][0] + weight_count[k0][1]) > (weight_count[stretch_dex][0] + weight_count[stretch_dex][1])) stretch_dex = k0; if (k1 < max_stretch_dex && (weight_count[k1][0] + weight_count[k1][1]) >(weight_count[stretch_dex][0] + weight_count[stretch_dex][1])) stretch_dex = k1; } } else { stretch_dex = stretch_dex_range[0]; } if (stretch_dex < 1 || stretch_dex >= max_stretch_dex) continue; if (weight_count[stretch_dex][0] + weight_count[stretch_dex][1] <= 0) continue; const unsigned normal_weight_dex = (unsigned)static_cast(ON_Font::Weight::Normal); const unsigned medium_weight_dex = (unsigned)static_cast(ON_Font::Weight::Medium); const unsigned bold_weight_dex = (unsigned)static_cast(ON_Font::Weight::Bold); for (unsigned slant_dex = 0; slant_dex < 2; slant_dex++) { if (weight_count[stretch_dex][slant_dex] <= 2) { // 0, 1 or 2 available weights. // If there is 1 face it must be the "Regular" face. // If there are 2 faces, the lightest will be "Regular" and the heaviest will be bold. int pair_dex = 0; for (int j = 1; j < max_weight_dex && pair_dex < 2; ++j) { if (nullptr != fonts_by_ssw[stretch_dex][slant_dex][j]) quartet_faces[slant_dex][pair_dex++] = fonts_by_ssw[stretch_dex][slant_dex][j]; } continue; } // 3 or more available weights (Bahnshrift, Helvetica Neue, ...) unsigned regular_dex = (nullptr != fonts_by_ssw[stretch_dex][slant_dex][normal_weight_dex]) ? normal_weight_dex : medium_weight_dex; while (nullptr == fonts_by_ssw[stretch_dex][slant_dex][regular_dex] && regular_dex > 0) --regular_dex; unsigned bold_dex = (nullptr != fonts_by_ssw[stretch_dex][slant_dex][bold_weight_dex]) ? bold_weight_dex : regular_dex + 1; while (nullptr == fonts_by_ssw[stretch_dex][slant_dex][bold_dex] && bold_dex < max_weight_dex) ++bold_dex; if (nullptr != fonts_by_ssw[stretch_dex][slant_dex][regular_dex] && nullptr == fonts_by_ssw[stretch_dex][slant_dex][bold_dex]) { if (regular_dex > 0) { for (unsigned j = regular_dex - 1; j > 0; --j) { if (nullptr == fonts_by_ssw[stretch_dex][slant_dex][j]) continue; bold_dex = regular_dex; regular_dex = j; break; } } } else if (nullptr == fonts_by_ssw[stretch_dex][slant_dex][regular_dex] && nullptr != fonts_by_ssw[stretch_dex][slant_dex][bold_dex]) { if (bold_dex > 0) { for (unsigned j = bold_dex - 1; j > 0; --j) { if (nullptr == fonts_by_ssw[stretch_dex][slant_dex][j]) continue; regular_dex = j; break; } } } quartet_faces[slant_dex][0] = fonts_by_ssw[stretch_dex][slant_dex][regular_dex]; quartet_faces[slant_dex][1] = fonts_by_ssw[stretch_dex][slant_dex][bold_dex]; } } if (nullptr == quartet_faces[0][0] && nullptr == quartet_faces[0][1]) { // Fonts like Monotype Corsiva have only slanted faces. // A quartet name + regular/bold/italic/italic-bold user interface should offer // a regular and bold member in this situation. quartet_faces[0][0] = quartet_faces[1][0]; quartet_faces[0][1] = quartet_faces[1][1]; quartet_faces[1][0] = nullptr; quartet_faces[1][1] = nullptr; } if (nullptr == quartet_faces[0][0] && nullptr == quartet_faces[1][0]) { // This might happen if buggy code encouters a heavy font like Arial Black // and incorrectly specifies the heavy regular/italic faces as bold. // A quartet name + regular/bold/italic/italic-bold user interface should offer // a regular and italic member in this situation. quartet_faces[0][0] = quartet_faces[0][1]; quartet_faces[1][0] = quartet_faces[1][1]; quartet_faces[0][1] = nullptr; quartet_faces[1][1] = nullptr; } // If ON_Font.m_quartet_member is not set or set incorrectly, // then set it now so we get consistent answers going forward. // (Managed quartets are recomputed as new missing fonts are added and in complex cases, the quartet member can change). // Installed font quartet members are set once and never change. const ON_FontFaceQuartet::Member member[2][2] = { {ON_FontFaceQuartet::Member::Regular,ON_FontFaceQuartet::Member::Bold}, {ON_FontFaceQuartet::Member::Italic,ON_FontFaceQuartet::Member::BoldItalic} }; for (int ii = 0; ii < 2; ++ii) for (int jj = 0; jj < 2; ++jj) { f = quartet_faces[ii][jj]; if (nullptr != f) { const ON_FontFaceQuartet::Member m = f->m_quartet_member; if (ON_FontFaceQuartet::Member::Unset == m) f->m_quartet_member = member[ii][jj]; else if (m != member[ii][jj]) { if (ON_Font::FontType::InstalledFont != f->m_font_type) quartet_faces[ii][jj]->m_quartet_member = member[ii][jj]; } } } // Unset the m_quartet_member on unsed fonts with this quartet name. for (unsigned j = i; j < next_i; ++j) { f = a[j]; if (nullptr == f) continue; if (f == quartet_faces[0][0] || f == quartet_faces[0][1] || f == quartet_faces[1][0] || f == quartet_faces[1][1]) continue; if (f->IsUnderlined() || f->IsStrikethrough()) continue; // dealt with below const ON_FontFaceQuartet::Member fm = f->m_quartet_member; if (ON_FontFaceQuartet::Member::Unset == fm) continue; if (ON_Font::FontType::InstalledFont != f->m_font_type) continue; // m_quartet_member incorrectly set. // If you are debugging and this is causing a problem, the bug is not here; // it is in the code above that fills in quartet_faces[][]. f->m_quartet_member = ON_FontFaceQuartet::Member::Unset; } if (decorated_fonts_count > 0) { // This for() loop copies the clean font quartet settings to decorated (underlined and strikethrough) fonts for (unsigned j = i; j < next_i; ++j) { f = a[j]; if (nullptr == f) continue; if (false == f->IsUnderlined() && false == f->IsStrikethrough()) continue; // f is underlined or strikethrough - find the clean version in fonts_by_ssw[][][] const ON_3udex ssw_dex = Internal_StretchSlantWeightDex(max_stretch_dex, max_weight_dex, f); const ON_Font* cleanf = ( ssw_dex.i >= 1 && ssw_dex.i < max_stretch_dex && ssw_dex.j >= 0 && ssw_dex.j < 2 && ssw_dex.k >= 1 && ssw_dex.k < max_weight_dex ) ? fonts_by_ssw[ssw_dex.i][ssw_dex.k][ssw_dex.k] : nullptr; if (nullptr != cleanf) { const ON_FontFaceQuartet::Member fm = cleanf->m_quartet_member; f->m_quartet_member = fm; } } } // Now convert managed to installed when that makes sense. for (int ii = 0; ii < 2; ++ii) for (int jj = 0; jj < 2; ++jj) { f = quartet_faces[ii][jj]; if (nullptr == f) continue; if (ON_Font::FontType::InstalledFont == f->m_font_type) continue; if (f->IsManagedSubstitutedFont()) continue; // common - f is a managed font that is missing from this device. if (false == f->IsManagedFont()) continue; // troubling situation ... if (f->IsUnderlined() || f->IsStrikethrough()) continue; // troubling situation ... // There are 2 lists of fonts. // All installed fonts - this list is made once when an application starts. // Managed fonts - this list grows as an application needs fonts // and is used to handle missing fonts and fonts with effects like underlined and strikethrough. // // This is a case where a managed font, like ON_Font::Default, // the default engraving font, // and more complicated cases that arise is rich text parsing, // has an identical font that is installed. const ON_Font* installed_font = f->Internal_ManagedFontToInstalledFont(); if (nullptr == installed_font) continue; if (false == installed_font->IsInstalledFont()) continue; // bad mojo happened sometime earlier in the life of this app instance. if (false == quartet_name.EqualOrdinal(installed_font->QuartetName(), true)) continue; // troubling situation ... // It is often the case that the installed quartet has faces not in the managed font list. // ON_Font::Default is a good example. // If there ON_FontFaceQuartet installed_font_quartet = installed_font->InstalledFontQuartet(); if (false == quartet_name.EqualOrdinal(installed_font_quartet.QuartetName(), true)) continue; if (installed_font_quartet.HasRegularFace()) quartet_faces[0][0] = installed_font_quartet.RegularFace(); if (installed_font_quartet.HasBoldFace()) quartet_faces[0][1] = installed_font_quartet.BoldFace(); if (installed_font_quartet.HasItalicFace()) quartet_faces[1][0] = installed_font_quartet.ItalicFace(); if (installed_font_quartet.HasBoldItalicFace()) quartet_faces[1][1] = installed_font_quartet.BoldItalicFace(); } const ON_FontFaceQuartet q(quartet_name, quartet_faces[0][0], quartet_faces[0][1], quartet_faces[1][0], quartet_faces[1][1]); if (q.IsEmpty()) continue; m_quartet_list.Append(q); } return m_quartet_list; } unsigned int ON_FontList::AddFont( const ON_Font* font, bool bCheckforDuplicates ) { if (nullptr == font) return false; if ( font->PostScriptName(m_name_locale).IsEmpty() && font->WindowsLogfontName(m_name_locale).IsEmpty() && font->FamilyName(m_name_locale).IsEmpty() ) return false; if ( ON_Font::Weight::Unset == font->FontWeight() || ON_Font::Stretch::Unset == font->FontStretch() || ON_Font::Style::Unset == font->FontStyle() ) return false; if (bCheckforDuplicates) { const ON_Font* f = FromFontProperties(font, true, true); if ( nullptr != f && f->FontWeight() == font->FontWeight() && f->FontStretch() == font->FontStretch() && f->FontStyle() == font->FontStyle() && f->IsUnderlined() == font->IsUnderlined() && f->IsStrikethrough() == font->IsStrikethrough() && ON_wString::EqualOrdinal(f->PostScriptName(m_name_locale), font->PostScriptName(m_name_locale), true) && ON_wString::EqualOrdinal(f->WindowsLogfontName(m_name_locale), font->WindowsLogfontName(m_name_locale), true) && ON_wString::EqualOrdinal(f->FamilyName(m_name_locale), font->FamilyName(m_name_locale), true) && ON_wString::EqualOrdinal(f->FaceName(m_name_locale), font->FaceName(m_name_locale), true) ) { return false; } } m_by_index.Append(font); int count = m_by_index.Count(); if (count > 1) { for (int i = count - 2; i <= 0; i--) { // font = m_by_index[i+1] const ON_Font* a = m_by_index[i]; if (a->RuntimeSerialNumber() <= font->RuntimeSerialNumber()) break; // almost never happens so bubble insertion is ok m_by_index[i] = font; m_by_index[i + 1] = a; } } m_unsorted.Append(font); m_quartet_list.SetCount(0); return 1; } unsigned int ON_FontList::AddFonts( const ON_SimpleArray< const ON_Font* >& fonts ) { const size_t font_count = fonts.UnsignedCount(); const ON_Font * const * font_list = fonts.Array(); return AddFonts(font_count,font_list); } unsigned int ON_FontList::AddFonts( size_t font_count, const ON_Font * const * font_list ) { if (nullptr == font_list || font_count <= 0) return 0; unsigned int rc = 0; for (size_t i = 0; i < font_count; i++) { rc += AddFont(font_list[i],false); } return rc; } const ON_Font* ON_Font::InstalledFont( bool bAllowBestMatch ) const { return IsInstalledFont() ? this : ON_ManagedFonts::InstalledFonts().FromFontProperties(this, !bAllowBestMatch, !bAllowBestMatch); } bool ON_Font::IsManagedFont() const { return ( 0 != m_runtime_serial_number ); } bool ON_Font::IsInstalledFont() const { bool rc; switch (m_font_type) { case ON_Font::FontType::InstalledFont: rc = true; break; case ON_Font::FontType::ManagedFont: rc = IsManagedInstalledFont(); break; default: rc = false; break; } return rc; } bool ON_Font::IsManagedInstalledFont() const { const ON__UINT_PTR bits = 3; return IsManagedFont() && (1 == (m_managed_installed_font_and_bits & bits)); } bool ON_Font::IsManagedSubstitutedFont() const { const ON__UINT_PTR bits = 3; return IsManagedFont() && (2 == (m_managed_installed_font_and_bits & bits)); } void ON_Font::Internal_SetManagedFontInstalledFont( const ON_Font* managed_font, const ON_Font* installed_font, bool bInstalledFontIsASubstitute ) { if (nullptr != managed_font) { ON__UINT_PTR x = 0; if (nullptr != installed_font) { const ON__UINT_PTR bits = bInstalledFontIsASubstitute ? 2 : 1; const ON__UINT_PTR ptr = (ON__UINT_PTR)installed_font; x = ptr | bits; } managed_font->m_managed_installed_font_and_bits = x; } } const ON_Font* ON_Font::SubstituteFont() const { if (IsManagedSubstitutedFont()) { return Internal_ManagedFontToInstalledFont(); } return nullptr; } const ON_Font* ON_Font::Internal_ManagedFontToInstalledFont() const { const ON__UINT_PTR bits = 3; ON__UINT_PTR ptr = m_managed_installed_font_and_bits & (~bits); return ((const ON_Font*)ptr); } const ON_Font* ON_Font::GetManagedFont( const wchar_t* face_name ) { return ON_Font::GetManagedFont(0.0, face_name); } const ON_Font* ON_Font::GetManagedFont( double point_size, const wchar_t* face_name ) { const bool bBold = false; const bool bItalic = false; return ON_Font::GetManagedFont( point_size, face_name, bBold, bItalic ); } const ON_Font* ON_Font::GetManagedFont( const wchar_t* face_name, bool bBold ) { return ON_Font::GetManagedFont(0.0, face_name, bBold); } const ON_Font* ON_Font::GetManagedFont( double point_size, const wchar_t* face_name, bool bBold ) { const bool bItalic = false; return ON_Font::GetManagedFont( point_size, face_name, bBold, bItalic ); } const ON_Font* ON_Font::GetManagedFont( const wchar_t* face_name, bool bBold, bool bItalic ) { return ON_Font::GetManagedFont(0.0, face_name, bBold, bItalic); } const ON_Font* ON_Font::GetManagedFont( double point_size, const wchar_t* name, bool bBold, bool bItalic ) { return ON_Font::GetManagedFont( point_size, name, bBold ? ON_Font::Weight::Bold : ON_Font::Default.FontWeight(), bItalic ? ON_Font::Style::Italic : ON_Font::Default.FontStyle() ); } const ON_Font* ON_Font::GetManagedFont( const wchar_t* face_name, ON_Font::Weight font_weight, ON_Font::Style font_style ) { return ON_Font::GetManagedFont(0.0, face_name, font_weight, font_style); } const ON_Font* ON_Font::GetManagedFont( double point_size, const wchar_t* name, ON_Font::Weight font_weight, ON_Font::Style font_style ) { // Using ON_Font::InstalledFontFromRichTextProperties() fixes lots // of RTF parsing bugs including RH-49028 and "Arial Black" bugs. // Depending on the context, name can be LOGFONT, PostScript, Family+Face, Family, ... // The font_weight parameter is typically a "face" indication. // Treating it as a literal value causes more bugs than it fixes. const ON_Font* installed_font = ON_Font::InstalledFontFromRichTextProperties( name, ON_Font::IsBoldWeight(font_weight), (ON_Font::Style::Italic == font_style) ); if (nullptr != installed_font) { if (point_size > 0.0 && point_size < ON_Font::AnnotationFontApplePointSize) { ON_Font f(*installed_font); f.m_point_size = point_size; return f.ManagedFont(); } return installed_font->ManagedFont(); } // Less reliable approach const unsigned int logfont_charset = static_cast(ON_Font::WindowsLogfontCharSetFromFaceName(name)); return ON_Font::GetManagedFont( point_size, name, font_weight, font_style, ON_Font::Default.m_font_stretch, ON_Font::Default.m_font_bUnderlined, ON_Font::Default.m_font_bStrikethrough, ON_FontMetrics::DefaultLineFeedRatio, logfont_charset ); } const ON_Font* ON_Font::GetManagedFont( const wchar_t* face_name, ON_Font::Weight font_weight, ON_Font::Style font_style, ON_Font::Stretch font_stretch, bool bUnderlined, bool bStrikethrough, double linefeed_ratio, unsigned int logfont_charset ) { return ON_Font::GetManagedFont( 0.0, // point_size face_name, font_weight, font_style, font_stretch, bUnderlined, bStrikethrough, linefeed_ratio, logfont_charset ); } const ON_Font* ON_Font::GetManagedFont( double point_size, const wchar_t* face_name, ON_Font::Weight font_weight, ON_Font::Style font_style, ON_Font::Stretch font_stretch, bool bUnderlined, bool bStrikethrough, double linefeed_ratio, unsigned int logfont_charset ) { ON_Font font_characteristics; if ( false == font_characteristics.SetFontCharacteristics( point_size, face_name, font_weight, font_style, font_stretch, bUnderlined, bStrikethrough, linefeed_ratio, logfont_charset )) return nullptr; return font_characteristics.ManagedFont(); } #if defined(ON_OS_WINDOWS_GDI) const ON_Font* ON_Font::GetManagedFontFromWindowsLogfont( int map_mode, HDC hdc, const LOGFONT& logfont ) { ON_Font font_characteristics; if (false == font_characteristics.SetFromWindowsLogFont(map_mode, hdc,logfont)) return nullptr; return font_characteristics.ManagedFont(); } #endif const ON_Font* ON_Font::GetManagedFontFromFontDescription( const wchar_t* font_description ) { ON_Font font_characteristics; if ( false == font_characteristics.SetFromFontDescription(font_description) ) return nullptr; return font_characteristics.ManagedFont(); } bool ON_Font::IsNotAppleFontName( const wchar_t* font_description ) { if (nullptr == font_description || 0 == font_description[0]) return true; if ( ON_wString::EqualOrdinal(L"Default",font_description,true) ) return true; // In RH-35535 Marlin reports that Arial is shipped with OS X. //if ( ON_wString::EqualOrdinal(L"Arial",font_description,true) ) // return true; return false; } const ON_Font* ON_Font::GetManagedFontFromAppleFontName( const char* postscript_name ) { return ON_Font::GetManagedFontFromPostScriptName(postscript_name); } const ON_Font* ON_Font::GetManagedFontFromAppleFontName( const wchar_t* postscript_name ) { return ON_Font::GetManagedFontFromPostScriptName(postscript_name); } const ON_Font* ON_Font::GetManagedFontFromPostScriptName( const char* postscript_name ) { const ON_wString wide_string_postscript_name(postscript_name); return ON_Font::GetManagedFontFromPostScriptName(wide_string_postscript_name); } const ON_Font* ON_Font::GetManagedFontFromPostScriptName( const wchar_t* postscript_name ) { ON_wString buffer(postscript_name); buffer.TrimLeftAndRight(); postscript_name = buffer; if (nullptr == postscript_name || 0 == postscript_name[0]) return &ON_Font::Default; const ON_Font* managed_font = ON_Font::ManagedFontList().FromPostScriptName(postscript_name); if (nullptr != managed_font) return managed_font; const ON_Font* installed_font = ON_Font::InstalledFontList().FromPostScriptName(postscript_name); if (nullptr != installed_font) return installed_font->ManagedFont(); // This font is not installed. ON_Font font(ON_Font::Unset); // prefered weight/stretch/style since this font is not installed font.SetFontWeight(ON_Font::Weight::Normal); font.SetFontStretch(ON_Font::Stretch::Medium); font.SetFontStyle(ON_Font::Style::Upright); font.m_loc_postscript_name = postscript_name; font.m_en_postscript_name = font.m_loc_postscript_name; return font.ManagedFont(); } int ON_Font::WindowsLogfontWeightFromWeight( ON_Font::Weight font_weight ) { int logfont_weight = (int)(100U*static_cast(font_weight)); if ( logfont_weight < 50 ) logfont_weight = 400; if ( logfont_weight < 150 ) logfont_weight = 100; else if ( logfont_weight >= 850 ) logfont_weight = 900; else if (0 != logfont_weight % 100) { int delta = logfont_weight %100; if (delta < 50) logfont_weight -= delta; else logfont_weight += (100-delta); } return logfont_weight; } int ON_Font::AppleWeightOfFontFromWeight( ON_Font::Weight font_weight ) { return ON_Font::WindowsLogfontWeightFromWeight(font_weight)/100; } double ON_Font::AppleFontWeightTraitFromWeight( ON_Font::Weight font_weight ) { // These values are selected to optimize conversion of font weights between Windows and Apple platforms. // https://mcneel.myjetbrains.com/youtrack/issue/RH-37075 const double default_apple_font_weight_trait = 0.0; double w = ((double)((int)static_cast(font_weight)) - 400.0) / 750.0; if (w < -1.0) w = -1.0; else if (w > 1.0) w = 1.0; if (!(-1.0 <= w && w < 1.0)) w = default_apple_font_weight_trait; double apple_font_weight_trait; switch (font_weight) { case ON_Font::Weight::Unset: apple_font_weight_trait = default_apple_font_weight_trait; break; case ON_Font::Weight::Thin: apple_font_weight_trait = -0.4; break; case ON_Font::Weight::Ultralight: apple_font_weight_trait = w; break; case ON_Font::Weight::Light: apple_font_weight_trait = w; break; case ON_Font::Weight::Normal: apple_font_weight_trait = 0.0; break; case ON_Font::Weight::Medium: apple_font_weight_trait = w; break; case ON_Font::Weight::Semibold: apple_font_weight_trait = w; break; case ON_Font::Weight::Bold: apple_font_weight_trait = 0.4; break; case ON_Font::Weight::Ultrabold: apple_font_weight_trait = w; break; case ON_Font::Weight::Heavy: apple_font_weight_trait = w; break; default: apple_font_weight_trait = default_apple_font_weight_trait; break; } // The valid value range is from -1.0 to 1.0. The value of 0.0 corresponds to the regular or medium font weight. return (-1.0 <= apple_font_weight_trait && apple_font_weight_trait <= 1.0) ? apple_font_weight_trait : default_apple_font_weight_trait; } ON_Font::Weight ON_Font::WeightFromWindowsLogfontWeight( int windows_logfont_weight ) { if ( windows_logfont_weight <= 0 || windows_logfont_weight > 1000 ) return ON_Font::Weight::Normal; if ( windows_logfont_weight < 150 ) return ON_Font::Weight::Thin; if ( windows_logfont_weight >= 850 ) return ON_Font::Weight::Heavy; const ON_Font::Weight weights[] = { ON_Font::Weight::Thin, // = 1 ON_Font::Weight::Ultralight, // = 2 ON_Font::Weight::Light, // = 3 ON_Font::Weight::Normal, // = 4 ON_Font::Weight::Medium, // = 5 ON_Font::Weight::Semibold, // = 6 ON_Font::Weight::Bold, // = 7 ON_Font::Weight::Ultrabold, // = 8 ON_Font::Weight::Heavy, // = 9 }; const size_t weight_count = sizeof(weights) / sizeof(weights[0]); ON_Font::Weight font_weight = ON_Font::Weight::Normal; int delta = std::abs(static_cast(ON_Font::WindowsLogfontWeightFromWeight(font_weight)) - windows_logfont_weight); for (size_t i = 0; 0 != delta && i < weight_count; i++) { // look for a closer match int d = std::abs(static_cast(ON_Font::WindowsLogfontWeightFromWeight(weights[i])) - windows_logfont_weight); if (d < delta) { font_weight = weights[i]; delta = d; } } return font_weight; } ON_Font::Weight ON_Font::WeightFromAppleWeightOfFont( int apple_weight_of_font ) { return ON_Font::WeightFromWindowsLogfontWeight(apple_weight_of_font*100); } ON_Font::Weight ON_Font::WeightFromAppleFontWeightTrait( double apple_font_weight_trait ) { if (false == ON_IsValid(apple_font_weight_trait)) return ON_Font::Weight::Unset; const double x = (-1.0 <= apple_font_weight_trait && apple_font_weight_trait <= 1.0) ? apple_font_weight_trait : 0.0; int windows_logfont_weight = (int)(400.0 + 750.0*x); if (windows_logfont_weight < 1) windows_logfont_weight = 1; else if (windows_logfont_weight > 1000) windows_logfont_weight = 1000; return ON_Font::WeightFromWindowsLogfontWeight(windows_logfont_weight); } void ON_Font::Internal_CopyFrom( const ON_Font& src ) { if ( 0 == ((ON__UINT_PTR)(&src)) ) { ON_ERROR("nullptr is target of ON_Font copy ctor or operator=. Crash is imminent or already happened."); return; } const bool bThisIsManagedFont = (ON_Font::FontType::ManagedFont == m_font_type); if ( bThisIsManagedFont ) { if (0 == m_runtime_serial_number) { ON_ERROR("Invalid parameters: true == bThisIsManagedFont and 0 == m_runtime_serial_number."); return; } } else { if (0 != m_runtime_serial_number && ON_Font::FontType::ManagedFont != m_font_type) { ON_ERROR("Invalid parameters: false == bThisIsManagedFont and 0 != m_runtime_serial_number."); return; } } if ( ((ON__UINT_PTR)(&src)) <= ON_PTR_SEMAPHORE_MAX ) { // If 8 == ((ON__UINT_PTR)(&src)), Initializing ON_Font::Unset (and m_runtime_serial_number is 0 ) // If 16 == ((ON__UINT_PTR)(&src)), Initializing ON_Font::Default (and m_runtime_serial_number is already set to 1) // (Multiples of 8 are used so that runtime pointer alignment checkers don't get alarmed.) const bool bDefaultFont = (m_runtime_serial_number > 0); m_font_weight = bDefaultFont ? ON_Font::Weight::Normal : ON_Font::Weight::Unset; m_font_stretch = bDefaultFont ? ON_Font::Stretch::Medium : ON_Font::Stretch::Unset; m_font_style = bDefaultFont ? ON_Font::Style::Upright : ON_Font::Style::Unset; m_loc_family_name = (bDefaultFont ? ON_wString(ON_Font::DefaultFamilyName()) : ON_wString::EmptyString); m_en_family_name = (bDefaultFont ? ON_wString(ON_Font::DefaultFamilyName()) : ON_wString::EmptyString); m_loc_face_name = (bDefaultFont ? ON_wString(ON_Font::DefaultFaceName()) : ON_wString::EmptyString); m_en_face_name = (bDefaultFont ? ON_wString(ON_Font::DefaultFaceName()) : ON_wString::EmptyString); m_loc_windows_logfont_name = (bDefaultFont ? ON_wString(ON_Font::DefaultWindowsLogfontName()) : ON_wString::EmptyString); m_en_windows_logfont_name = (bDefaultFont ? ON_wString(ON_Font::DefaultWindowsLogfontName()) : ON_wString::EmptyString); m_quartet_member = bDefaultFont ? ON_FontFaceQuartet::Member::Regular : ON_FontFaceQuartet::Member::Unset; m_loc_postscript_name = (bDefaultFont ? ON_wString(ON_Font::DefaultPostScriptName()) : ON_wString::EmptyString); m_en_postscript_name = (bDefaultFont ? ON_wString(ON_Font::DefaultPostScriptName()) : ON_wString::EmptyString); m_font_bUnderlined = false; m_font_bStrikethrough = false; m_apple_font_weight_trait = 0.0; m_windows_logfont_weight = 400; m_logfont_charset = ON_Font::WindowsConstants::logfont_default_charset; m_point_size = 0.0; m_font_origin = bDefaultFont ? #if defined(ON_RUNTIME_WIN) ON_Font::Origin::WindowsFont #elif defined(ON_RUNTIME_APPLE) ON_Font::Origin::AppleFont #else ON_Font::Origin::Unknown #endif : ON_Font::Origin::Unset; const ON_Font* installed_font = bDefaultFont ? ON_Font::InstalledFontList().FromFontProperties(this, true, true) : nullptr; if ( nullptr != installed_font && ON_Font::EqualFontFamily(this, installed_font) && m_font_style == installed_font->FontStyle() ) { if ( ON_Font::FontType::ManagedFont == this->m_font_type && this->m_runtime_serial_number > 0 && 0 == this->m_managed_installed_font_and_bits) { // When 1 == m_runtime_serial_number, this font is ON_Font::Default // and its face is installed on this device. Otherwise // this is a managed font being created by some other process. // // See RH-58472 for rare cases when this is required. (A V5 file being read at Rhino startup). ON_Font::Internal_SetManagedFontInstalledFont(this, installed_font, false); } // Set stretch from installed font. m_font_stretch = installed_font->FontStretch(); // want an exact name match if ( installed_font->m_loc_postscript_name.IsNotEmpty() ) m_loc_postscript_name = installed_font->m_loc_postscript_name; if ( installed_font->m_en_postscript_name.IsNotEmpty() ) m_en_postscript_name = installed_font->m_en_postscript_name; if ( installed_font->m_loc_family_name.IsNotEmpty() ) m_loc_family_name = installed_font->m_loc_family_name; if ( installed_font->m_en_family_name.IsNotEmpty() ) m_en_family_name = installed_font->m_en_family_name; #if defined(ON_RUNTIME_WIN) if ( installed_font->m_loc_face_name.IsNotEmpty() ) m_loc_face_name = installed_font->m_loc_face_name; if ( installed_font->m_en_face_name.IsNotEmpty() ) m_en_face_name = installed_font->m_en_face_name; if ( installed_font->m_loc_windows_logfont_name.IsNotEmpty() ) m_loc_windows_logfont_name = installed_font->m_loc_windows_logfont_name; if ( installed_font->m_en_windows_logfont_name.IsNotEmpty() ) m_en_windows_logfont_name = installed_font->m_en_windows_logfont_name; if (m_loc_windows_logfont_name.IsNotEmpty() || m_en_windows_logfont_name.IsNotEmpty()) m_quartet_member = installed_font->m_quartet_member; else m_quartet_member = ON_FontFaceQuartet::Member::Unset; m_logfont_charset = installed_font->m_logfont_charset; #endif // exact platform weight match m_windows_logfont_weight = installed_font->m_windows_logfont_weight; m_apple_font_weight_trait = installed_font->m_apple_font_weight_trait; m_panose1 = installed_font->m_panose1; // share glyph cache m_font_glyph_cache = installed_font->m_font_glyph_cache; } } else { m_font_weight = src.m_font_weight; m_windows_logfont_weight = src.m_windows_logfont_weight; m_apple_font_weight_trait = src.m_apple_font_weight_trait; m_font_style = src.m_font_style; m_font_stretch = src.m_font_stretch; m_font_bUnderlined = src.m_font_bUnderlined; m_font_bStrikethrough = src.m_font_bStrikethrough; m_logfont_charset = src.m_logfont_charset; m_locale_name = src.m_locale_name; m_loc_postscript_name = src.m_loc_postscript_name; m_en_postscript_name = src.m_en_postscript_name; m_loc_family_name = src.m_loc_family_name; m_en_family_name = src.m_en_family_name; m_loc_face_name = src.m_loc_face_name; m_en_face_name = src.m_en_face_name; m_loc_windows_logfont_name = src.m_loc_windows_logfont_name; m_en_windows_logfont_name = src.m_en_windows_logfont_name; if (m_loc_windows_logfont_name.IsNotEmpty() || m_en_windows_logfont_name.IsNotEmpty()) m_quartet_member = src.m_quartet_member; else m_quartet_member = ON_FontFaceQuartet::Member::Unset; bool bCopyCache = (0 == m_runtime_serial_number && ON_Font::FontType::Unset == m_font_type); if ( false == bCopyCache && ON_Font::FontType::ManagedFont == m_font_type && ON_Font::FontType::InstalledFont == src.m_font_type && nullptr != src.m_font_glyph_cache.get() && nullptr == m_font_glyph_cache.get() ) { // destination font is managed and src font is installed bCopyCache = true; } if (bCopyCache) m_font_glyph_cache = src.m_font_glyph_cache; m_point_size = src.m_point_size; m_font_origin = src.m_font_origin; m_panose1 = src.m_panose1; m_simulated = src.m_simulated; } m_font_characteristics_hash = ON_SHA1_Hash::ZeroDigest; } ON_Font::ON_Font() { ////const size_t sz = sizeof(*this); ////const char* p0 = (const char*)this; ////const char* p1 = (const char*)(&this->m_outline_figure_type); ////const char* p2 = (const char*)(&this->m_quartet_member); ////const char* p3 = (const char*)(&this->m_reserved2); ////const char* p4 = (const char*)(this + 1); ////const size_t sz1 = (p1 - p0); ////const size_t sz2 = (p2 - p0); ////const size_t sz3 = (p3 - p0); ////const size_t sz4 = (p4 - p0); ////if (sz != sz4 || sz1 <= 0 && sz2 <= sz1 && sz3 <= sz4 || sz4 != sz) //// ON_TextLog::Null.PrintNewLine(); /* sz1 144 const unsigned __int64 sz2 145 const unsigned __int64 sz3 146 const unsigned __int64 sz4 192 const unsigned __int64 sz 192 const unsigned __int64 */ } ON_Font::ON_Font( ON_Font::FontType font_type, const ON_Font& src ) : m_runtime_serial_number( (ON_Font::FontType::ManagedFont == font_type) ? (++ON_Font::__runtime_serial_number_generator) // only managed fonts have non-zero m_runtime_serial_number : 0 ) , m_font_type(font_type) { // This is a private constructor used to create managed fonts. Internal_CopyFrom(src); } ON_Font::ON_Font(const ON_Font& src) : m_runtime_serial_number( (ON_PTR_SEMAPHORE2 == ((ON__UINT_PTR)&src)) ? 1 : 0) , m_font_type( (ON_PTR_SEMAPHORE2 == ((ON__UINT_PTR)&src)) ? ON_Font::FontType::ManagedFont : ON_Font::FontType::Unset) { // (ON_PTR_SEMAPHORE1 == ((ON__UINT_PTR)&src)) means we are constructing ON_Font::Unset. // (ON_PTR_SEMAPHORE2 == ((ON__UINT_PTR)&src)) means we are constructing ON_Font::Default. // Otherwise "this" should be an ordinary and unmanaged font. // See opennurbs_statics.cpp and ON_Font::CopyHelper() for more information. Internal_CopyFrom(src); } ON_Font& ON_Font::operator=(const ON_Font& src) { if (this != &src) { if (IsManagedFont() ) { // managed fonts can never be modified if ( false == ON_Font::EqualFontCharacteristics(*this, src) ) { ON_ERROR("Attempt to modify a managed font"); } } else { Internal_CopyFrom(src); } } return *this; } bool ON_Font::SetFontCharacteristics( const wchar_t* gdi_logfont_name, bool bBold, bool bItalic, bool bUnderlined, bool bStrikethrough ) { return SetFontCharacteristics( 0.0, gdi_logfont_name, bBold, bItalic, bUnderlined, bStrikethrough ); } bool ON_Font::SetFontCharacteristics( double point_size, const wchar_t * gdi_logfont_name, bool bBold, bool bItalic, bool bUnderlined, bool bStrikethrough ) { if (nullptr == gdi_logfont_name || 0 == gdi_logfont_name[0]) { gdi_logfont_name = ON_Font::Default.m_loc_windows_logfont_name; } return SetFontCharacteristics( point_size, gdi_logfont_name, (bBold ? ON_Font::Weight::Bold : ON_Font::Weight::Normal), (bItalic ? ON_Font::Style::Italic : ON_Font::Style::Upright), ON_Font::Default.m_font_stretch, bUnderlined, bStrikethrough, ON_FontMetrics::DefaultLineFeedRatio, ON_Font::WindowsLogfontCharSetFromFaceName(gdi_logfont_name) ); } bool ON_Font::SetFontCharacteristicsForExperts( double point_size, const ON_wString postscript_name, const ON_wString quartet_name, ON_FontFaceQuartet::Member quartet_member, const ON_wString family_name, const ON_wString face_name, ON_Font::Weight font_weight, ON_Font::Style font_style, ON_Font::Stretch font_stretch, bool bUnderlined, bool bStrikethrough, unsigned char logfont_charset, int windows_logfont_weight, double apple_font_weight_trait, ON_PANOSE1 panose1 ) { this->m_point_size = point_size; this->m_windows_logfont_weight = windows_logfont_weight; this->m_apple_font_weight_trait = apple_font_weight_trait; this->m_en_windows_logfont_name = quartet_name.Duplicate(); this->m_en_windows_logfont_name.TrimLeftAndRight(); this->m_quartet_member = quartet_member; this->m_en_postscript_name = postscript_name.Duplicate(); this->m_en_postscript_name.TrimLeftAndRight(); this->m_loc_postscript_name = this->m_en_postscript_name; this->m_en_family_name = family_name.Duplicate(); this->m_en_family_name.TrimLeftAndRight(); this->m_loc_family_name = this->m_en_family_name; this->m_en_face_name = face_name.Duplicate(); this->m_en_face_name.TrimLeftAndRight(); this->m_loc_face_name = this->m_en_face_name; this->m_font_weight = font_weight; this->m_font_stretch = font_stretch; this->m_font_style = font_style; this->m_font_bUnderlined = bUnderlined; this->m_font_bStrikethrough = bStrikethrough; this->m_logfont_charset = logfont_charset; this->m_panose1 = panose1; this->m_font_origin = ON_Font::Origin::Unset; this->m_simulated = 0; this->m_font_characteristics_hash = ON_SHA1_Hash::ZeroDigest; return this->IsValid(); } bool ON_Font::IsValidFaceName( const wchar_t* face_name ) { if ( nullptr == face_name || 0 == face_name[0] || ON_wString::Space == face_name[0]) return false; int i = 0; while (i < 32 && 0 != face_name[i]) { if (face_name[i] < ON_wString::Space ) return false; switch (face_name[i]) { case ';': case '"': case '\'': case '`': case '=': case '#': // lots more return false; //case '@': - There are valid fonts like @Gulim with '@' in the name. // case '.' - Many apple font names begin with a period // case '-' - A hyphen is common in PostScript names // case ' ' - A space is common in many names default: break; } i++; } if (0 != face_name[i]) return false; return true; } ON_Font::Weight ON_Font::FontWeightFromUnsigned( unsigned int unsigned_font_weight ) { switch (unsigned_font_weight) { ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Weight::Unset); ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Weight::Thin); ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Weight::Ultralight); ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Weight::Light); ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Weight::Normal); ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Weight::Medium); ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Weight::Semibold); ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Weight::Bold); ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Weight::Ultrabold); ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Weight::Heavy); default: break; } ON_ERROR("unsigned_font_weight is not valid"); return ON_Font::Weight::Unset; } bool ON_Font::EqualWeightStretchStyle( const ON_Font* lhs, const ON_Font* rhs, bool bUnsetIsEqual ) { return ON_Font::EqualWeight(lhs, rhs, bUnsetIsEqual) && ON_Font::EqualStretch(lhs, rhs, bUnsetIsEqual) && ON_Font::EqualStyle(lhs, rhs, bUnsetIsEqual); } bool ON_Font::EqualWeight( const ON_Font* lhs, const ON_Font* rhs, bool bUnsetIsEqual ) { if (nullptr == lhs || nullptr == rhs) return false; if (lhs->m_font_weight != rhs->m_font_weight) { if (false == bUnsetIsEqual) return false; if (ON_Font::Weight::Unset != lhs->m_font_weight && ON_Font::Weight::Unset != rhs->m_font_weight) return false; } return true; } bool ON_Font::EqualStretch( const ON_Font* lhs, const ON_Font* rhs, bool bUnsetIsEqual ) { if (nullptr == lhs || nullptr == rhs) return false; if (lhs->m_font_stretch != rhs->m_font_stretch) { if (false == bUnsetIsEqual) return false; if (ON_Font::Stretch::Unset != lhs->m_font_stretch && ON_Font::Stretch::Unset != rhs->m_font_stretch) return false; } return true; } bool ON_Font::EqualStyle( const ON_Font* lhs, const ON_Font* rhs, bool bUnsetIsEqual ) { if (nullptr == lhs || nullptr == rhs) return false; if (lhs->m_font_style != rhs->m_font_style) { if (false == bUnsetIsEqual) return false; if (ON_Font::Style::Unset != lhs->m_font_style && ON_Font::Style::Unset != rhs->m_font_style) return false; } return true; } int ON_Font::CompareWeight( ON_Font::Weight weight_a, ON_Font::Weight weight_b ) { unsigned int a = static_cast(weight_a); unsigned int b = static_cast(weight_b); if ( a < b ) return -1; if ( a < b ) return 1; return 0; } ON_Font::Style ON_Font::FontStyleFromUnsigned( unsigned int unsigned_font_style ) { switch (unsigned_font_style) { ON_ENUM_FROM_UNSIGNED_CASE( ON_Font::Style::Upright); ON_ENUM_FROM_UNSIGNED_CASE( ON_Font::Style::Italic); ON_ENUM_FROM_UNSIGNED_CASE( ON_Font::Style::Oblique); default: break; } ON_ERROR("unsigned_font_style is not valid"); return ON_Font::Style::Upright; } bool ON_Font::IsBoldWeight( ON_Font::Weight weight ) { return (static_cast(weight) >= static_cast(ON_Font::Weight::Semibold)); } const wchar_t* ON_Font::WeightToWideString( ON_Font::Weight font_weight ) { const wchar_t* s; switch (font_weight) { case ON_Font::Weight::Unset: s = L"Unsetweight"; break; case ON_Font::Weight::Thin: s = L"Thin"; break; case ON_Font::Weight::Ultralight: s = L"Ultralight"; break; case ON_Font::Weight::Light: s = L"Light"; break; case ON_Font::Weight::Normal: s = L"Normal"; break; case ON_Font::Weight::Medium: s = L"Medium"; break; case ON_Font::Weight::Semibold: s = L"Semibold"; break; case ON_Font::Weight::Bold: s = L"Bold"; break; case ON_Font::Weight::Ultrabold: s = L"Ultrabold"; break; case ON_Font::Weight::Heavy: s = L"Heavy"; break; default: s = L""; break; } return s; } const wchar_t* ON_Font::StretchToWideString( ON_Font::Stretch font_stretch ) { const wchar_t* s; switch (font_stretch) { case ON_Font::Stretch::Unset: s = L"Unsetstretch"; break; case ON_Font::Stretch::Ultracondensed: s = L"Ultracondensed"; break; case ON_Font::Stretch::Extracondensed: s = L"Extracondensed"; break; case ON_Font::Stretch::Condensed: s = L"Condensed"; break; case ON_Font::Stretch::Semicondensed: s = L"Semicondensed"; break; case ON_Font::Stretch::Medium: s = L"Medium"; break; case ON_Font::Stretch::Semiexpanded: s = L"Semiexpanded"; break; case ON_Font::Stretch::Expanded: s = L"Expanded"; break; case ON_Font::Stretch::Extraexpanded: s = L"Extraexpanded"; break; case ON_Font::Stretch::Ultraexpanded: s = L"Ultraexpanded"; break; default: s = L""; break; } return s; } const wchar_t* ON_Font::StyleToWideString( ON_Font::Style font_style ) { const wchar_t* s; switch (font_style) { case ON_Font::Style::Unset: s = L"Unsetstyle"; break; case ON_Font::Style::Upright: s = L"Upright"; break; case ON_Font::Style::Italic: s = L"Italic"; break; case ON_Font::Style::Oblique: s = L"Oblique"; break; default: s = L""; break; } return s; } ON_Font::Stretch ON_Font::FontStretchFromUnsigned( unsigned int unsigned_font_stretch ) { switch (unsigned_font_stretch) { ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Stretch::Unset); ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Stretch::Ultracondensed); ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Stretch::Extracondensed); ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Stretch::Condensed); ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Stretch::Semicondensed); ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Stretch::Medium); ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Stretch::Semiexpanded); ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Stretch::Expanded); ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Stretch::Extraexpanded); ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Stretch::Ultraexpanded); default: break; } ON_ERROR("unsigned_font_stretch is not valid"); return ON_Font::Stretch::Unset; } unsigned int ON_Font::FontCharacteristicsAsUnsigned() const { return ON_Font::Internal_FontCharacteristicsAsUnsigned( FontWeight(), FontStyle(), FontStretch(), m_font_bUnderlined, m_font_bStrikethrough ); } unsigned int ON_Font::Internal_FontCharacteristicsAsUnsigned( ON_Font::Weight font_weight, ON_Font::Style font_style, ON_Font::Stretch font_stretch, bool bUnderlined, bool bStrikethrough ) { unsigned int a[][2] = { { 2U, 1U }, // insures 0 is not a valid FontCharacteristicsAsUnsigned() value. { 10U, static_cast(font_weight) }, { 4U, static_cast(font_style) }, { 10U, static_cast(font_stretch) }, // The static_cast in b ? 1U : 0U is to work around a CLang compiler bug. { 2U, static_cast(bUnderlined ? 1U : 0U) }, { 2U, static_cast(bStrikethrough ? 1U : 0U) } // insert new information below this line. }; const int count = (int)(sizeof(a) / sizeof(a[0])); int i = count-1; unsigned int u = a[i][1] % a[i][0]; for (i--; i >= 0; i--) { u = (u * a[i][0]) + (a[i][1] % a[i][0]); } return u; } void ON_Font::Internal_GetFontCharacteristicsFromUnsigned( unsigned int font_characteristics_as_unsigned, ON_Font::Weight& font_weight, ON_Font::Stretch& font_stretch, ON_Font::Style& font_style, bool& bUnderlined, bool& bStrikethrough ) { unsigned int u = font_characteristics_as_unsigned; const unsigned int u_one = u % 2; u /= 2; const unsigned int u_font_weight = u % 10; u /= 10; const unsigned int u_font_style = u % 4; u /= 4; const unsigned int u_font_stretch = u % 10; u /= 10; const unsigned int u_bUnderlined = u % 2; u /= 2; const unsigned int u_bStrikethrough = u % 2; u /= 2; // extract new information below this line font_weight = (1U == u_one && u_font_weight > 0) ? ON_Font::FontWeightFromUnsigned(u_font_weight) : ON_Font::Default.FontWeight(); font_style = (1U == u_one) ? ON_Font::FontStyleFromUnsigned(u_font_style) : ON_Font::Default.FontStyle(); font_stretch = (1U == u_one) ? ON_Font::FontStretchFromUnsigned(u_font_stretch) : ON_Font::Default.FontStretch(); bUnderlined = (1U == u_one) ? (1 == u_bUnderlined) : ON_Font::Default.IsUnderlined(); bStrikethrough = (1U == u_one) ? (1 == u_bStrikethrough) : ON_Font::Default.IsStrikethrough(); } bool ON_Font::Internal_SetFontCharacteristicsFromUnsigned( unsigned int font_characteristics_as_unsigned ) { ON_Font::Weight font_weight = ON_Font::Weight::Normal; ON_Font::Stretch font_stretch = ON_Font::Stretch::Medium; ON_Font::Style font_style = ON_Font::Style::Upright; bool bUnderlined = false; bool bStrikethrough = false; ON_Font::Internal_GetFontCharacteristicsFromUnsigned( font_characteristics_as_unsigned, font_weight, font_stretch, font_style, bUnderlined, bStrikethrough ); const ON_wString windows_logfont_name = WindowsLogfontName(); bool rc = SetFontCharacteristics( windows_logfont_name, font_weight, font_style, font_stretch, bUnderlined, bStrikethrough ); return rc; } unsigned int ON_Font::CRC32( bool bIgnoreNameOrdinalCase ) const { unsigned int u = FontCharacteristicsAsUnsigned(); const ON_wString windows_logfont_name = WindowsLogfontName(); const ON_wString windows_logfont_name_crc = bIgnoreNameOrdinalCase ? windows_logfont_name.MapStringOrdinal(ON_StringMapOrdinalType::MinimumOrdinal) : windows_logfont_name; // SHould probably include PostScript name as well. ON__UINT32 hash = ON_CRC32(0, sizeof(u), &u ); hash = ON_CRC32(hash,windows_logfont_name_crc.Length()*sizeof(wchar_t),static_cast(windows_logfont_name_crc)); #if defined(ON_RUNTIME_WIN) if ( m_point_size > 0.0 ) hash = ON_CRC32(hash, sizeof(m_point_size), &m_point_size); #endif return hash; } bool ON_Font::SetFontCharacteristics( const wchar_t* gdi_logfont_name, ON_Font::Weight font_weight, ON_Font::Style font_style, ON_Font::Stretch font_stretch, bool bUnderlined, bool bStrikethrough ) { return SetFontCharacteristics( 0.0, gdi_logfont_name, font_weight, font_style, font_stretch, bUnderlined, bStrikethrough ); } bool ON_Font::SetFontCharacteristics( double point_size, const wchar_t* gdi_logfont_name, ON_Font::Weight font_weight, ON_Font::Style font_style, ON_Font::Stretch font_stretch, bool bUnderlined, bool bStrikethrough ) { const unsigned char logfont_charset = ON_Font::WindowsLogfontCharSetFromFaceName(gdi_logfont_name); double linefeed_ratio = ON_FontMetrics::DefaultLineFeedRatio; return SetFontCharacteristics( point_size, gdi_logfont_name, font_weight, font_style, font_stretch, bUnderlined, bStrikethrough, linefeed_ratio, logfont_charset ); } bool ON_Font::SetFontCharacteristics( const wchar_t* gdi_logfont_name, ON_Font::Weight font_weight, ON_Font::Style font_style, ON_Font::Stretch font_stretch, bool bUnderlined, bool bStrikethrough, double linefeed_ratio, unsigned int logfont_charset ) { return SetFontCharacteristics( 0.0, gdi_logfont_name, font_weight, font_style, font_stretch, bUnderlined, bStrikethrough, linefeed_ratio, logfont_charset ); } bool ON_Font::SetFontCharacteristics( double point_size, const wchar_t* gdi_logfont_name, ON_Font::Weight font_weight, ON_Font::Style font_style, ON_Font::Stretch font_stretch, bool bUnderlined, bool bStrikethrough, double linefeed_ratio, unsigned int logfont_charset ) { if ( false == ON_FONT_MODIFICATION_PERMITTED ) return false; // Save face_name - it often points to this->m_face_name // and *this = ON_Font::Unset will set this->m_face_name to the empty string. ON_wString local_face_name(gdi_logfont_name); local_face_name.TrimLeftAndRight(); gdi_logfont_name = static_cast(local_face_name); *this = ON_Font::Unset; if (false == ON_Font::IsValidFaceName(gdi_logfont_name)) return false; if (logfont_charset >= 256) return false; ON_Font new_characteristics(ON_Font::Unset); new_characteristics.m_loc_windows_logfont_name = gdi_logfont_name; new_characteristics.m_en_windows_logfont_name = new_characteristics.m_loc_windows_logfont_name; new_characteristics.m_font_weight = ON_Font::FontWeightFromUnsigned(static_cast(font_weight)); new_characteristics.m_point_size = (point_size > 0.0 && point_size < 2147483640.0) ? point_size : 0.0; new_characteristics.m_windows_logfont_weight = ON_Font::WindowsLogfontWeightFromWeight(new_characteristics.m_font_weight); new_characteristics.m_apple_font_weight_trait = ON_Font::AppleFontWeightTraitFromWeight(new_characteristics.m_font_weight); new_characteristics.m_font_style = font_style; new_characteristics.m_font_stretch = font_stretch; new_characteristics.m_font_bUnderlined = bUnderlined ? true : false; new_characteristics.m_font_bStrikethrough = bStrikethrough; new_characteristics.m_loc_family_name = ON_Font::FamilyNameFromDirtyName(gdi_logfont_name); new_characteristics.m_en_family_name = new_characteristics.m_loc_family_name; if ( ON_wString::EqualOrdinal(L"CityBlueprint", -1, gdi_logfont_name, -1, true) || ON_wString::EqualOrdinal(L"CountryBlueprint", -1, gdi_logfont_name, -1, true) ) { // These two fonts were didstrubuted with ACAD for decades. // Thy have several errors in their defintion including being // marked as SYMBOL_CHARSET rather than ANSI_CHARSET. logfont_charset = ON_Font::WindowsConstants::logfont_symbol_charset; } else if (ON_Font::logfont_symbol_charset == logfont_charset) { // verify this is correct. logfont_charset = ON_Font::WindowsLogfontCharSetFromFaceName(new_characteristics.WindowsLogfontName()); } else { logfont_charset = ON_Font::WindowsConstants::logfont_default_charset; } new_characteristics.m_logfont_charset = (ON_Font::WindowsConstants::logfont_symbol_charset == (unsigned char)logfont_charset) ? ON_Font::WindowsConstants::logfont_symbol_charset : ON_Font::WindowsConstants::logfont_default_charset; if ( // 3 fast checks to avoid time consuming hash calculation false == ON_wString::EqualOrdinal(WindowsLogfontName(),new_characteristics.WindowsLogfontName(),false) || false == ON_wString::EqualOrdinal(PostScriptName(),new_characteristics.PostScriptName(),false) || false == ON_wString::EqualOrdinal(FamilyName(),new_characteristics.FamilyName(),false) || false == ON_wString::EqualOrdinal(FaceName(),new_characteristics.FaceName(),false) || m_font_weight != new_characteristics.m_font_weight || m_font_stretch != new_characteristics.m_font_stretch || m_font_style != new_characteristics.m_font_style || FontCharacteristicsHash() != new_characteristics.FontCharacteristicsHash() ) { Internal_CopyFrom(new_characteristics); if (0 == m_runtime_serial_number) { // destination font is not managed m_font_glyph_cache = nullptr; } if ( ON_Font::Stretch::Medium == m_font_stretch && (ON_Font::Weight::Normal == m_font_weight || ON_Font::Weight::Bold == m_font_weight) && (ON_Font::Style::Upright == m_font_style || ON_Font::Style::Italic == m_font_style) ) { const ON_wString logfontName = WindowsLogfontName(); ON_wString postscript_suffix; bool bSetMakePostScriptName = false; if ( ON_wString::EqualOrdinal(L"Arial", logfontName, true) ) { // Because Arial is common and its PostScript name has "MT" appended, // the post script name is set here so the font will readily work // in situation that key off the PostScript name. bSetMakePostScriptName = true; postscript_suffix = L"MT"; } else if (ON_wString::EqualOrdinal(L"Segoe Print", logfontName, true)) { bSetMakePostScriptName = true; } else if (ON_wString::EqualOrdinal(L"Segoe Script", logfontName, true)) { bSetMakePostScriptName = true; } else if (ON_wString::EqualOrdinal(L"Segoe UI", logfontName, true)) { bSetMakePostScriptName = true; } else if (ON_wString::EqualOrdinal(L"Microsoft YaHei", logfontName, true)) { bSetMakePostScriptName = true; } else if (ON_wString::EqualOrdinal(L"Microsoft YaHei UI", logfontName, true)) { bSetMakePostScriptName = true; } for (;;) { if (false == bSetMakePostScriptName) break; // The fonts listed above all remove spaces from m_face_name to get // the beginning of the postscript name. ON_wString postscript_name = logfontName; postscript_name.TrimLeftAndRight(); postscript_name.Remove(ON_wString::Space); if (postscript_name.IsEmpty()) break; // add -