Files
opennurbs/opennurbs_font.cpp
2025-08-12 12:41:00 -07:00

13932 lines
438 KiB
C++

//
// Copyright (c) 1993-2022 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
#include "opennurbs.h"
#if !defined(ON_COMPILING_OPENNURBS)
// This check is included in all opennurbs source .c and .cpp files to insure
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined
// and the opennurbs .h files alter what is declared and how it is declared.
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
#endif
#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<ON__UINT8>(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<const wchar_t*>(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 condition 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 condition 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<const ON_Font*> 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<ON_Font*>(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 milliseconds 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 definition.
// 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<ON_GlyphMap> m_glyphmap;
};
ON_FontGlyphCache* ON_FontGlyphCache::New()
{
ON_FontGlyphCache* font_cache = new ON_FontGlyphCache();
font_cache->m_glyphmap = std::make_shared<ON_GlyphMap>();
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 warrant 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<ON_wString&>(s);
clean.Destroy();
clean = static_cast<const wchar_t*>(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 various 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 default font.
// Current settings:
// Windows: Arial
// Apple: Helvetic Neue
// managed_font is a missing font.
// We have to substitute 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<ON_FontGlyphCache>(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<LOGFONT>*)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>& 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<LOGFONT> 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<const ON_Font*>& 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> 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<ON_WindowsDWriteFontInformation> 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<const wchar_t*>(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<const wchar_t*>(regular_ps));
if (nullptr != m_bold)
sample += ON_wString::FormatToString(L"{\\f0\\b Bold: %ls}{\\par}", static_cast<const wchar_t*>(bold_ps));
if (nullptr != m_italic)
sample += ON_wString::FormatToString(L"{\\f0\\i Italic: %ls}{\\par}", static_cast<const wchar_t*>(italic_ps));
if (nullptr != m_bold_italic)
sample += ON_wString::FormatToString(L"{\\f0\\b\\i Bold Italic: %ls}{\\par}", static_cast<const wchar_t*>(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<const wchar_t*>(regular_ps));
if (bold_ps.IsNotEmpty())
sample += ON_wString::FormatToString(L"{\\f%d %ls;}", fdex++, static_cast<const wchar_t*>(bold_ps));
if (italic_ps.IsNotEmpty())
sample += ON_wString::FormatToString(L"{\\f%d %ls;}", fdex++, static_cast<const wchar_t*>(italic_ps));
if (bold_italic_ps.IsNotEmpty())
sample += ON_wString::FormatToString(L"{\\f%d %ls;}", fdex++, static_cast<const wchar_t*>(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<const wchar_t*>(regular_ps));
if (nullptr != m_bold)
sample += ON_wString::FormatToString(L"{\\f%d\\b Bold: %ls}{\\par}", fdex++, static_cast<const wchar_t*>(bold_ps));
if (nullptr != m_italic)
sample += ON_wString::FormatToString(L"{\\f%d\\i Italic: %ls}{\\par}", fdex++, static_cast<const wchar_t*>(italic_ps));
if (nullptr != m_bold_italic)
sample += ON_wString::FormatToString(L"{\\f%d\\b\\i Bold Italic: %ls}{\\par}", fdex++, static_cast<const wchar_t*>(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" <not available>");
}
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" <not available>");
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<unsigned char>(lhs_font->FontStyle());
const int rhs_font_style = (int)static_cast<unsigned char>(rhs_font->FontStyle());
rc = (lhs_font_style - rhs_font_style);
if (0 != rc)
return rc;
// width
const int lhs_font_stretch = (int)static_cast<unsigned char>(lhs_font->FontStretch());
const int rhs_font_stretch = (int)static_cast<unsigned char>(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<unsigned char>(lhs_font->FontStretch());
const int rhs_font_stretch = (int)static_cast<unsigned char>(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
{
// Modification 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<const wchar_t*>(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 substitute 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 PostScript 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<unsigned int>(target_font.FontWeight())) - ((int)static_cast<unsigned int>(candidate_font->FontWeight())));
m_stretch_delta
= bEqualPostScriptName
? 0
: abs(((int)static_cast<unsigned int>(target_font.FontStretch())) - ((int)static_cast<unsigned int>(candidate_font->FontStretch())));
m_style_delta
= bEqualPostScriptName
? 0
: abs(((int)static_cast<unsigned int>(target_font.FontStyle())) - ((int)static_cast<unsigned int>(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 candidate 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 favorable 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 explicitly 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<const wchar_t*>(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<unsigned>(fake_normal_weight);
const unsigned bold_unsigned = static_cast<unsigned>(fake_bold_weight);
const unsigned default_normal_unsigned = static_cast<unsigned>(ON_Font::Weight::Normal);
const unsigned default_bold_unsigned = static_cast<unsigned>(ON_Font::Weight::Bold);
const unsigned min_weight = static_cast<unsigned>(ON_Font::Weight::Thin);
const unsigned max_weight = static_cast<unsigned>(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<const ON_Font*>& 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<const ON_Font*>& 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<ON_FontFaceQuartet>& 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<unsigned char>(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<unsigned char>(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<const ON_Font*>& 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<unsigned char>(ON_Font::Stretch::Medium);
ON_SimpleArray<const ON_Font*> regular_faces(8);
ON_SimpleArray<const ON_Font*> bold_faces(8);
ON_SimpleArray<const ON_Font*> italic_faces(8);
ON_SimpleArray<const ON_Font*> 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<unsigned char>(ON_Font::Weight::Normal);
const unsigned medium_weight_dex = (unsigned)static_cast<unsigned char>(ON_Font::Weight::Medium);
const unsigned bold_weight_dex = (unsigned)static_cast<unsigned char>(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 encounters 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<unsigned int>(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);
// preferred 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<unsigned int>(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<unsigned char>(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<int>(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<int>(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<unsigned int>(weight_a);
unsigned int b = static_cast<unsigned int>(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<unsigned int>(weight) >= static_cast<unsigned int>(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<unsigned int>(font_weight) },
{ 4U, static_cast<unsigned int>(font_style) },
{ 10U, static_cast<unsigned int>(font_stretch) },
// The static_cast<unsigned int> in b ? 1U : 0U is to work around a CLang compiler bug.
{ 2U, static_cast<unsigned int>(bUnderlined ? 1U : 0U) },
{ 2U, static_cast<unsigned int>(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<const wchar_t*>(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<const wchar_t*>(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<unsigned char>(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 distributed with ACAD for decades.
// Thy have several errors in their definition 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 -<Weight><Style>
ON_wString postscript_face;
if (ON_Font::Weight::Bold == m_font_weight)
postscript_face += L"Bold";
if (ON_Font::Style::Italic == m_font_style)
postscript_face += L"Italic";
if (postscript_face.IsNotEmpty())
{
postscript_name += ON_wString::HyphenMinus;
postscript_name += postscript_face;
}
// Add suffix
postscript_name += postscript_suffix;
m_loc_postscript_name = postscript_name;
m_en_postscript_name = postscript_name;
break;
}
}
Internal_AfterModification();
}
return true;
}
bool ON_Font::SetFromAppleFontName(
const wchar_t* apple_font_name
)
{
return SetFromAppleFontName(apple_font_name, 0.0);
}
bool ON_Font::SetFromAppleFontName(
const wchar_t* apple_font_name,
double point_size
)
{
ON_wString local_apple_font_name(apple_font_name);
local_apple_font_name.TrimLeftAndRight();
if (local_apple_font_name.IsEmpty())
return false;
apple_font_name = static_cast<const wchar_t*>(local_apple_font_name);
const bool bAnnotationFont
= ON_Font::IsValidPointSize(point_size) && point_size < ON_Font::AnnotationFontCellHeight
? false
: true;
bool rc = false;
#if defined (ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE)
// If the current computer has the same Apple font, this will return the highest fidelity match.
bool bIsSubstituteFont = false;
CTFontRef appleFont = ON_Font::AppleCTFont(apple_font_name,point_size, bIsSubstituteFont);
if (nullptr != appleFont)
{
// In some cases, the NSfont.fontName is different from apple_font_name
// For example, when apple_font_name = "Arial", NSfont.fontName = "ArialMT".
const bool b = SetFromAppleCTFont(appleFont, bAnnotationFont);
CFRelease(appleFont);
if(b)
return true;
}
#endif
rc = SetFromFontDescription(apple_font_name, apple_font_name);
if (false == rc)
{
// Assume this name is valid but the particular font is not
// installed on this computer and unconditionally set m_postscript_font_name
m_loc_postscript_name = local_apple_font_name;
m_loc_family_name = ON_Font::FamilyNameFromDirtyName(m_loc_postscript_name);
m_en_postscript_name = m_loc_postscript_name;
m_en_family_name = m_loc_family_name;
}
m_point_size
= bAnnotationFont
? 0.0 // indicates annotation font point size on all platforms
: point_size;
// Not running on an Apple platform
Internal_AfterModification();
return rc;
}
bool Internal_EqualName(
const ON_wString& lhs,
const ON_wString& rhs
)
{
return (lhs.IsNotEmpty() && ON_wString::EqualOrdinal(lhs, rhs, true));
}
bool ON_Font::EqualFontFamily(
const ON_Font* lhs,
const ON_Font* rhs
)
{
if (nullptr == lhs || nullptr == rhs)
return false;
if (lhs == rhs)
return true;
if (Internal_EqualName(lhs->m_en_family_name, rhs->m_en_family_name))
return true;
if (Internal_EqualName(lhs->FamilyName(ON_Font::NameLocale::LocalizedFirst), rhs->FamilyName(ON_Font::NameLocale::LocalizedFirst)))
return true;
if (lhs->FaceName().IsNotEmpty() && rhs->FamilyName().IsNotEmpty())
return false; // both lhs and rhs have non-empty family names.
// Testing PostScript, and LOGFONT names handles cases where the family name is missing.
const bool rc
= Internal_EqualName(lhs->m_en_postscript_name, rhs->m_en_postscript_name)
|| Internal_EqualName(lhs->m_en_windows_logfont_name, rhs->m_en_windows_logfont_name)
|| Internal_EqualName(lhs->PostScriptName(ON_Font::NameLocale::LocalizedFirst),rhs->PostScriptName(ON_Font::NameLocale::LocalizedFirst))
|| Internal_EqualName(lhs->WindowsLogfontName(ON_Font::NameLocale::LocalizedFirst),rhs->WindowsLogfontName(ON_Font::NameLocale::LocalizedFirst))
;
return rc;
}
bool ON_Font::EqualFontFamilyAndFace(
const ON_Font* lhs,
const ON_Font* rhs
)
{
if (nullptr == lhs || nullptr == rhs)
return false;
if (lhs == rhs)
return true;
bool rc = EqualFontFamily(lhs,rhs);
if (rc)
{
rc = Internal_EqualName(lhs->m_en_face_name, rhs->m_en_face_name)
|| Internal_EqualName(lhs->FaceName(ON_Font::NameLocale::LocalizedFirst), rhs->FaceName(ON_Font::NameLocale::LocalizedFirst))
;
}
return rc;
}
const ON_wString ON_Font::Locale() const
{
return m_locale_name;
}
const wchar_t* ON_Font::AppleFontNameAsPointer() const
{
return ON_Font::Internal_GetName(ON_Font::NameLocale::LocalizedFirst, m_loc_postscript_name, m_en_postscript_name).Array();
}
const ON_wString& ON_Font::AppleFontName() const
{
return
m_loc_postscript_name.IsNotEmpty()
? m_loc_postscript_name
: m_en_postscript_name;
}
const wchar_t* ON_Font::PostScriptNameAsPointer() const
{
return ON_Font::Internal_GetName(ON_Font::NameLocale::LocalizedFirst, m_loc_postscript_name, m_en_postscript_name).Array();
}
const ON_wString ON_Font::PostScriptName(ON_Font::NameLocale name_locale) const
{
return ON_Font::Internal_GetName(name_locale, m_loc_postscript_name, m_en_postscript_name);
}
const ON_wString ON_Font::PostScriptName() const
{
return ON_Font::Internal_GetName(ON_Font::NameLocale::LocalizedFirst, m_loc_postscript_name, m_en_postscript_name);
}
const ON_wString ON_Font::FamilyName(ON_Font::NameLocale name_locale) const
{
return ON_Font::Internal_GetName(name_locale, m_loc_family_name, m_en_family_name);
}
const ON_wString ON_Font::FamilyName() const
{
return FamilyName(ON_Font::NameLocale::LocalizedFirst);
}
const ON_wString ON_Font::FaceName(
ON_Font::NameLocale name_locale
) const
{
return ON_Font::Internal_GetName(name_locale, m_loc_face_name, m_en_face_name);
}
const ON_wString ON_Font::FaceName() const
{
return FaceName(ON_Font::NameLocale::LocalizedFirst);
}
const ON_wString ON_Font::WindowsLogfontName(
ON_Font::NameLocale name_locale
) const
{
return ON_Font::Internal_GetName(name_locale, m_loc_windows_logfont_name, m_en_windows_logfont_name);
}
const ON_wString ON_Font::WindowsLogfontName() const
{
return WindowsLogfontName(ON_Font::NameLocale::LocalizedFirst);
}
class Internal_FakeWindowsLogfontName
{
public:
~Internal_FakeWindowsLogfontName() = default;
Internal_FakeWindowsLogfontName(const Internal_FakeWindowsLogfontName&) = default;
Internal_FakeWindowsLogfontName() = default;
Internal_FakeWindowsLogfontName& operator=(const Internal_FakeWindowsLogfontName&) = default;
Internal_FakeWindowsLogfontName(
const wchar_t* family_name,
const wchar_t* postscript_name,
const wchar_t* fake_logfont_name,
ON_FontFaceQuartet::Member quartet_member
)
: m_fake_logfont_name(fake_logfont_name)
, m_family_and_postcript_name_hash(Internal_FakeWindowsLogfontName::NameHash(family_name, postscript_name))
, m_quartet_member(quartet_member)
// for debugging
//, m_family_name(family_name)
//, m_postscript_name(postscript_name)
{
m_fake_logfont_name.TrimLeftAndRight();
if (
m_fake_logfont_name.EqualOrdinal(family_name, true)
|| (ON_FontFaceQuartet::Member::Unset != quartet_member && m_fake_logfont_name.IsEmpty())
|| m_family_and_postcript_name_hash.IsZeroDigestOrEmptyContentHash()
)
{
ON_ERROR("Invalid input.");
m_fake_logfont_name = ON_wString::EmptyString;
m_family_and_postcript_name_hash = ON_SHA1_Hash::EmptyContentHash;
m_quartet_member = ON_FontFaceQuartet::Member::Unset;
}
}
static const ON_SHA1_Hash NameHash(
const wchar_t* family_name,
const wchar_t* postscript_name
)
{
if (nullptr == family_name || 0 == family_name[0])
return ON_SHA1_Hash::EmptyContentHash;
if (nullptr == postscript_name || 0 == postscript_name[0])
return ON_SHA1_Hash::EmptyContentHash;
ON_wString s1(family_name);
s1.Remove(ON_wString::Space);
s1.Remove(ON_wString::HyphenMinus);
s1.TrimLeftAndRight();
if (s1.IsEmpty())
return ON_SHA1_Hash::EmptyContentHash;
ON_wString s2(postscript_name);
s2.Remove(ON_wString::Space);
s2.Remove(ON_wString::HyphenMinus);
s2.TrimLeftAndRight();
if (s2.IsEmpty())
return ON_SHA1_Hash::EmptyContentHash;
// add a hyphen between family and postscript name
// to insure hash for Family = A and postscript = BC
// is different from hash for Family = AB and postscript = C
s1 += ON_wString::HyphenMinus;
s1 += s2;
return s1.ContentHash(ON_StringMapOrdinalType::MinimumOrdinal);
}
static int CompareFamilyAndPostscriptNameHash(
const Internal_FakeWindowsLogfontName* lhs,
const Internal_FakeWindowsLogfontName* rhs
)
{
if (lhs == rhs)
return 0;
// nulls sort last
if (nullptr == lhs)
return 1;
if (nullptr == rhs)
return -1;
return ON_SHA1_Hash::Compare(lhs->m_family_and_postcript_name_hash, rhs->m_family_and_postcript_name_hash);
}
const ON_SHA1_Hash QuartetFamilyAndPostscriptNameHash() const
{
return m_family_and_postcript_name_hash;
}
ON_FontFaceQuartet::Member QuartetMember() const
{
return m_quartet_member;
}
const ON_wString FakeWindowsLogfontName() const
{
return m_fake_logfont_name;
}
private:
ON_SHA1_Hash m_family_and_postcript_name_hash = ON_SHA1_Hash::EmptyContentHash;
ON_wString m_fake_logfont_name = ON_wString::EmptyString;
ON_FontFaceQuartet::Member m_quartet_member = ON_FontFaceQuartet::Member::Unset;
// for debugging
//const ON_wString m_family_name = ON_wString::EmptyString;
//const ON_wString m_postscript_name = ON_wString::EmptyString;
};
const ON_wString ON_Font::FakeWindowsLogfontNameFromFamilyAndPostScriptNames(
ON_wString family_name,
ON_wString postscript_name
)
{
// This function is used to partition Apple fonts into groups with
// at most 4 faces and assign a face windows logfont name to the group.
//
// This is required for archaic font selection interfaces, like ones in Rhino 6 and 7,
// that use quartets of regular/italic/bold/bold-italic to specify fonts.
//
// Because modern fonts can have many faces (Helvetica Neue has 14 as of 2019)
// and those faces dont' fit the 1970's LOGFONT model of regular/italic/bold/bold-italic.
// American Typewriter and Avenir are other example of commonly used fonts that have
// more than 4 faces and don't have fit well into the regular/italic/bold/bold-italic UI.
//
// It fixes bugs like
// https://mcneel.myjetbrains.com/youtrack/issue/RH-37074
// https://mcneel.myjetbrains.com/youtrack/issue/RH-53129
// https://mcneel.myjetbrains.com/youtrack/issue/RH-53430
//
family_name.TrimLeftAndRight();
postscript_name.TrimLeftAndRight();
if (family_name.IsEmpty() || postscript_name.IsEmpty())
return ON_wString::EmptyString;
static Internal_FakeWindowsLogfontName fake_names[] =
{
// Quartets that need a fake LOGFONT name different from family_name in order to
// appear in an archaic quartet name + regular/bold/italic/bold-italic UI
//
// Internal_FakeWindowsLogfontName(family_name,postscript_name,fake_logfont_name,member);
// Combinations that are not explicitly specified are correctly handled by using the family name as the fake logfont name.
// The explicitly fake logfont name should always be different from the family name.
Internal_FakeWindowsLogfontName(L"American Typewriter", L"AmericanTypewriter-Light", L"American Typewriter Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"American Typewriter", L"AmericanTypewriter-Semibold", L"American Typewriter Semibold", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"American Typewriter", L"AmericanTypewriter-CondensedLight", L"American Typewriter Condensed Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"American Typewriter", L"AmericanTypewriter-Condensed", L"American Typewriter Condensed", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"American Typewriter", L"AmericanTypewriter-CondensedBold", L"American Typewriter Condensed", ON_FontFaceQuartet::Member::Bold),
Internal_FakeWindowsLogfontName(L"Apple Braille", L"AppleBraille-Outline6Dot", L"Apple Braille Outline 6 Dot", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Apple Braille", L"AppleBraille-Outline8Dot", L"Apple Braille Outline 8 Dot", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Apple Braille", L"AppleBraille-Pinpoint6Dot", L"Apple Braille Pinpoint 6 Dot", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Apple Braille", L"AppleBraille-Pinpoint8Dot", L"Apple Braille Pinpoint 8 Dot", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Apple SD Gothic Neo", L"AppleSDGothicNeo-Thin", L"Apple SD Gothic Neo Thin", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Apple SD Gothic Neo", L"AppleSDGothicNeo-UltraLight", L"Apple SD Gothic Neo Ultralight", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Apple SD Gothic Neo", L"AppleSDGothicNeo-Light", L"Apple SD Gothic Neo Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Apple SD Gothic Neo", L"AppleSDGothicNeo-Medium", L"Apple SD Gothic Neo Medium", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Apple SD Gothic Neo", L"AppleSDGothicNeo-SemiBold", L"Apple SD Gothic Neo Semibold", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Apple SD Gothic Neo", L"AppleSDGothicNeo-ExtraBold", L"Apple SD Gothic Neo Extra Bold", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Apple SD Gothic Neo", L"AppleSDGothicNeo-Heavy", L"Apple SD Gothic Neo Heavy", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Arial Hebrew", L"ArialHebrew-Light", L"Arial Hebrew Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Arial Hebrew Scholar", L"ArialHebrewScholar-Light", L"Arial Hebrew Scholar Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Avenir", L"Avenir-Book", L"Avenir Book", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Avenir", L"Avenir-BookOblique", L"Avenir Book", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Avenir", L"Avenir-Light", L"Avenir Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Avenir", L"Avenir-LightOblique", L"Avenir Light", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Avenir", L"Avenir-Medium", L"Avenir Medium", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Avenir", L"Avenir-MediumOblique", L"Avenir Medium", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Avenir", L"Avenir-Heavy", L"Avenir Heavy", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Avenir", L"Avenir-HeavyOblique", L"Avenir Heavy", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Avenir", L"Avenir-Black", L"Avenir Black", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Avenir", L"Avenir-BlackOblique", L"Avenir-Black", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Avenir Next", L"AvenirNext-UltraLight", L"Avenir Next Ultralight", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Avenir Next", L"AvenirNext-UltraLightItalic", L"Avenir Next Ultralight", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Avenir Next", L"AvenirNext-Medium", L"Avenir Next Medium", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Avenir Next", L"AvenirNext-MediumItalic", L"Avenir Next Medium", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Avenir Next", L"AvenirNext-DemiBold", L"Avenir Next Demibold", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Avenir Next", L"AvenirNext-DemiBoldItalic", L"Avenir Next Demibold", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Avenir Next", L"AvenirNext-Heavy", L"Avenir Next Heavy", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Avenir Next", L"AvenirNext-HeavyItalic", L"Avenir Next Heavy", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Avenir Next Condensed", L"AvenirNextCondensed-UltraLight", L"Avenir Next Condensed Ultralight", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Avenir Next Condensed", L"AvenirNextCondensed-UltraLightItalic", L"Avenir Next Condensed Ultralight", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Avenir Next Condensed", L"AvenirNextCondensed-Medium", L"Avenir Next Condensed Medium", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Avenir Next Condensed", L"AvenirNextCondensed-MediumItalic", L"Avenir Next Condensed Medium", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Avenir Next Condensed", L"AvenirNextCondensed-DemiBold", L"Avenir Next Condensed Demibold", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Avenir Next Condensed", L"AvenirNextCondensed-DemiBoldItalic", L"Avenir Next Condensed Demibold", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Avenir Next Condensed", L"AvenirNextCondensed-Heavy", L"Avenir Next Condensed Heavy", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Avenir Next Condensed", L"AvenirNextCondensed-HeavyItalic", L"Avenir Next Condensed Heavy", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Baskerville", L"Baskerville-SemiBold", L"Baskerville Semibold", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Baskerville", L"Baskerville-SemiBoldItalic", L"Baskerville Semibold", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Chalkboard SE", L"ChalkboardSE-Light", L"Chalkboard SE Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Charter", L"Charter-Black", L"Charter Black", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Charter", L"Charter-BlackItalic", L"Charter Black", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Copperplate", L"Copperplate-Light", L"Copperplate Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Damascus", L"DamascusLight", L"Damascus Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Damascus", L"DamascusMedium", L"Damascus Medium", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Damascus", L"DamascusSemiBold", L"Damascus Semibold", ON_FontFaceQuartet::Member::Regular),
// DINPro https://mcneel.myjetbrains.com/youtrack/issue/RH-54627
// DO NOT try making a fake quartet that combines regular/bold.
// The last parameter for all DINPro "fake quartets" must be ON_FontFaceQuartet::Member::Regular.
// When text is created and then later edited the, the Mac RTF used by Eto and the "composed" RTF used by Rhino
// are not currently capable looking at the "fake quartet" information and preserving the font. So the Mac users get
// to see 5 "fake quartets" for DINPro. The "fix" is to return to the Rhino 5 Mac font UI.
Internal_FakeWindowsLogfontName(L"DINPro", L"DINPro-Regular", L"DINPro Regular", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"DINPro", L"DINPro-Bold", L"DINPro Bold", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"DINPro", L"DINPro-Light", L"DINPro Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"DINPro", L"DINPro-Medium", L"DINPro Medium", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"DINPro", L"DINPro-Black", L"DINPro Black", ON_FontFaceQuartet::Member::Regular),
// Futura https://mcneel.myjetbrains.com/youtrack/issue/RH-56085
// DO NOT try making a fake quartet that combines regular/bold.
// The last parameter for all Futura "fake quartets" must be ON_FontFaceQuartet::Member::Regular.
// When text is created and then later edited the, the Mac RTF used by Eto and the "composed" RTF used by Rhino
// are not currently capable looking at the "fake quartet" information and preserving the font. So the Mac users get
// to see 5 "fake quartets" for Futura. The "fix" is to return to the Rhino 5 Mac font UI.
Internal_FakeWindowsLogfontName(L"Futura", L"Futura-Medium", L"Futura Medium", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Futura", L"Futura-Bold", L"Futura Bold", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Futura", L"Futura-MediumItalic", L"Futura Medium Italic", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Futura", L"Futura-CondensedMedium", L"Futura Condensed Medium", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Futura", L"Futura-CondensedExtraBold", L"Futura Condensed ExtraBold", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Gill Sans", L"GillSans-Light", L"Gill Sans Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Gill Sans", L"GillSans-LightItalic", L"Gill Sans Light", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Gill Sans", L"GillSans-SemiBold", L"Gill Sans Semibold", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Gill Sans", L"GillSans-SemiBoldItalic", L"Gill Sans Semibold", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Gill Sans", L"GillSans-UltraBold", L"Gill Sans Ultrabold", ON_FontFaceQuartet::Member::Regular),
// Gill Sans Std https://mcneel.myjetbrains.com/youtrack/issue/RH-57050
// Gill Sans Std is a customize version of Gill Sans that some customer was using.
// It is not the Gill Sans that ships with modern versions of Mac OS or Windows.
// The next line insures the fake Mac LOGFONT name is the same as the one
// Windows uses and insures a file created on Mac/Windows and then read on Windows/Mac
// will have identical Rhino RTF in the .3dm file.
// This is a cosmetic change that will reduce confusion when developers are looking
// at text that uses this rare font and will feel more comfortable if the name Windows
// automatically assigns to the LOGFONT is an exact match with the fake LOGFONT name
// used in Mac OS.
Internal_FakeWindowsLogfontName(L"Gill Sans Std", L"GillSansStd-Light", L"Gill Sans Std Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Helvetica", L"Helvetica-Light", L"Helvetica Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Helvetica", L"Helvetica-LightOblique", L"Helvetica Light", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Helvetica Neue", L"HelveticaNeue-UltraLight", L"Helvetica Neue Ultralight", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Helvetica Neue", L"HelveticaNeue-UltraLightItalic", L"Helvetica Neue Ultralight", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Helvetica Neue", L"HelveticaNeue-Thin", L"Helvetica Neue Thin", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Helvetica Neue", L"HelveticaNeue-ThinItalic", L"Helvetica Neue Thin", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Helvetica Neue", L"HelveticaNeue-Light", L"Helvetica Neue Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Helvetica Neue", L"HelveticaNeue-LightItalic", L"Helvetica Neue Light", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Helvetica Neue", L"HelveticaNeue-Medium", L"Helvetica Neue Medium", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Helvetica Neue", L"HelveticaNeue-MediumItalic", L"Helvetica Neue Medium", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Helvetica Neue", L"HelveticaNeue-CondensedBold", L"Helvetica Neue Condensed", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Helvetica Neue", L"HelveticaNeue-CondensedBlack", L"Helvetica Neue Condensed", ON_FontFaceQuartet::Member::Bold),
Internal_FakeWindowsLogfontName(L"Hiragino Sans", L"HiraginoSans-W3", L"Hiragino Sans W3", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Hiragino Sans", L"HiraginoSans-W6", L"Hiragino Sans W6", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Hiragino Sans", L"HiraginoSans-W0", L"Hiragino Sans W0", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Hiragino Sans", L"HiraginoSans-W1", L"Hiragino Sans W1", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Hiragino Sans", L"HiraginoSans-W2", L"Hiragino Sans W2", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Hiragino Sans", L"HiraginoSans-W4", L"Hiragino Sans W4", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Hiragino Sans", L"HiraginoSans-W5", L"Hiragino Sans W5", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Hiragino Sans", L"HiraginoSans-W7", L"Hiragino Sans W7", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Hiragino Sans", L"HiraginoSans-W8", L"Hiragino Sans W8", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Hiragino Sans", L"HiraginoSans-W9", L"Hiragino Sans W9", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Hoefler Text", L"HoeflerText-Ornaments", L"Hoefler Text Ornaments", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"ITF Devanagari", L"ITFDevanagari-Light", L"ITF Devanagari Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"ITF Devanagari", L"ITFDevanagari-Medium", L"ITF Devanagari Medium", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"ITF Devanagari", L"ITFDevanagari-Demi", L"ITF Devanagari Demibold", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"ITF Devanagari Marathi", L"ITFDevanagariMarathi-Light", L"ITF Devanagari Marathi Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"ITF Devanagari Marathi", L"ITFDevanagariMarathi-Medium", L"ITF Devanagari Marathi Medium", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"ITF Devanagari Marathi", L"ITFDevanagariMarathi-Demi", L"ITF Devanagari Marathi Demibold", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Kohinoor Bangla", L"KohinoorBangla-Light", L"Kohinoor Bangla Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Kohinoor Bangla", L"KohinoorBangla-Medium", L"Kohinoor Bangla Medium", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Kohinoor Bangla", L"KohinoorBangla-Semibold", L"Kohinoor Bangla Semibold", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Kohinoor Devanagari", L"KohinoorDevanagari-Light", L"Kohinoor Devanagari Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Kohinoor Devanagari", L"KohinoorDevanagari-Medium", L"Kohinoor Devanagari Medium", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Kohinoor Devanagari", L"KohinoorDevanagari-Semibold", L"Kohinoor Devanagari Semibold", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Kohinoor Telugu", L"KohinoorTelugu-Light", L"Kohinoor Telugu Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Kohinoor Telugu", L"KohinoorTelugu-Medium", L"Kohinoor Telugu Medium", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Kohinoor Telugu", L"KohinoorTelugu-Semibold", L"Kohinoor Telugu Semibold", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Muna", L"MunaBlack", L"Muna Black", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Optima", L"Optima-ExtraBlack", L"Optima Black", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Papyrus", L"Papyrus-Condensed", L"Papyrus Condensed", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Phosphate", L"Phosphate-Inline", L"Phosphate Inline", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Phosphate", L"Phosphate-Solid", L"Phosphate Solid", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"PingFang HK", L"PingFangHK-Ultralight", L"PingFang HK Ultralight", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"PingFang HK", L"PingFangHK-Thin", L"PingFang HK Thin", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"PingFang HK", L"PingFangHK-Light", L"PingFang HK Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"PingFang HK", L"PingFangHK-Medium", L"PingFang HK Medium", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"PingFang SC", L"PingFangSC-Ultralight", L"PingFang SC Ultralight", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"PingFang SC", L"PingFangSC-Thin", L"PingFang SC Thin", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"PingFang SC", L"PingFangSC-Light", L"PingFang SC Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"PingFang SC", L"PingFangSC-Medium", L"PingFang SC Medium", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"PingFang TC", L"PingFangTC-Ultralight", L"PingFang TC Ultralight", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"PingFang TC", L"PingFangTC-Thin", L"PingFang TC Thin", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"PingFang TC", L"PingFangTC-Light", L"PingFang TC Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"PingFang TC", L"PingFangTC-Medium", L"PingFang TC Medium", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Skia", L"Skia-Regular_Light", L"Skia Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Skia", L"Skia-Regular_Black", L"Skia Black", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Skia", L"Skia-Regular_Condensed", L"Skia Condensed", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Skia", L"Skia-Regular_Black-Condensed", L"Skia Condensed Black", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Skia", L"Skia-Regular_Light-Condensed", L"Skia Condensed Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Skia", L"Skia-Regular_Extended", L"Skia Extended", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Skia", L"Skia-Regular_Black-Extended", L"Skia Extended Black", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Skia", L"Skia-Regular_Light-Extended", L"Skia Extended Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Songti SC", L"STSongti-SC-Light", L"Songti SC Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Songti SC", L"STSongti-SC-Black", L"Songti SC Black", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Songti TC", L"STSongti-TC-Light", L"Songti TC Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Sukhumvit Set", L"SukhumvitSet-Light", L"Sukhumvit Set Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Sukhumvit Set", L"SukhumvitSet-Medium", L"Sukhumvit Set Medium", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Sukhumvit Set", L"SukhumvitSet-SemiBold", L"Sukhumvit Set Semibold", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Sukhumvit Set", L"SukhumvitSet-Thin", L"Sukhumvit Set Thin", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Thonburi", L"Thonburi-Light", L"Thonburi Light", ON_FontFaceQuartet::Member::Regular),
// RH-68433
Internal_FakeWindowsLogfontName(L"Graphik", L"Graphik-Black", L"Graphik Black", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Graphik", L"Graphik-BlackItalic", L"Graphik Black", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Graphik", L"Graphik-Bold", L"Graphik Bold", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Graphik", L"Graphik-BoldItalic", L"Graphik Bold", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Graphik", L"Graphik-Extralight", L"Graphik Extralight", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Graphik", L"Graphik-ExtralightItalic", L"Graphik Extralight", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Graphik", L"Graphik-Light", L"Graphik Light", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Graphik", L"Graphik-LightItalic", L"Graphik Light", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Graphik", L"Graphik-Medium", L"Graphik Medium", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Graphik", L"Graphik-MediumItalic", L"Graphik Medium", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Graphik", L"Graphik-Regular", L"Graphik Regular", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Graphik", L"Graphik-RegularItalic", L"Graphik Regular", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Graphik", L"Graphik-Semibold", L"Graphik Semibold", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Graphik", L"Graphik-SemiboldItalic", L"Graphik Semibold", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Graphik", L"Graphik-Super", L"Graphik Super", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Graphik", L"Graphik-SuperItalic", L"Graphik Super", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Graphik", L"Graphik-Thin", L"Graphik Thin", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Graphik", L"Graphik-ThinItalic", L"Graphik Thin", ON_FontFaceQuartet::Member::Italic),
// RH-68539
Internal_FakeWindowsLogfontName(L"Neue Haas Grotesk Display Pro", L"NHaasGroteskDSPro-15UltTh", L"NeueHaasGroteskDisp UltTh", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Neue Haas Grotesk Display Pro", L"NHaasGroteskDSPro-16UltThIt", L"NeueHaasGroteskDisp UltTh", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Neue Haas Grotesk Display Pro", L"NHaasGroteskDSPro-25Th", L"NeueHaasGroteskDisp Pro Thin", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Neue Haas Grotesk Display Pro", L"NHaasGroteskDSPro-26ThIt", L"NeueHaasGroteskDisp Pro Thin", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Neue Haas Grotesk Display Pro", L"NHaasGroteskDSPro-35XLt", L"NeueHaasGroteskDisp Pro XLt", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Neue Haas Grotesk Display Pro", L"NHaasGroteskDSPro-36XLtIt", L"NeueHaasGroteskDisp Pro XLt", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Neue Haas Grotesk Display Pro", L"NHaasGroteskDSPro-45Lt", L"NeueHaasGroteskDisp Pro Lt", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Neue Haas Grotesk Display Pro", L"NHaasGroteskDSPro-46LtIt", L"NeueHaasGroteskDisp Pro Lt", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Neue Haas Grotesk Display Pro", L"NHaasGroteskDSPro-55Rg", L"NeueHaasGroteskDisp Pro", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Neue Haas Grotesk Display Pro", L"NHaasGroteskDSPro-56It", L"NeueHaasGroteskDisp Pro", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Neue Haas Grotesk Display Pro", L"NHaasGroteskDSPro-75Bd", L"NeueHaasGroteskDisp Pro", ON_FontFaceQuartet::Member::Bold),
Internal_FakeWindowsLogfontName(L"Neue Haas Grotesk Display Pro", L"NHaasGroteskDSPro-76BdIt", L"NeueHaasGroteskDisp Pro", ON_FontFaceQuartet::Member::BoldItalic),
Internal_FakeWindowsLogfontName(L"Neue Haas Grotesk Display Pro", L"NHaasGroteskDSPro-65Md", L"NeueHaasGroteskDisp Pro Md", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Neue Haas Grotesk Display Pro", L"NHaasGroteskDSPro-66MdIt", L"NeueHaasGroteskDisp Pro Md", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Neue Haas Grotesk Display Pro", L"NHaasGroteskDSPro-95Blk", L"NeueHaasGroteskDisp Pro Blk", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Neue Haas Grotesk Display Pro", L"NHaasGroteskDSPro-96BlkIt", L"NeueHaasGroteskDisp Pro Blk", ON_FontFaceQuartet::Member::Italic),
};
static bool bFakeNamesAreSorted = false;
if (false == bFakeNamesAreSorted)
{
bFakeNamesAreSorted = true;
qsort(fake_names, sizeof(fake_names) / sizeof(fake_names[0]), sizeof(fake_names[0]), (int(*)(const void*,const void*))Internal_FakeWindowsLogfontName::CompareFamilyAndPostscriptNameHash);
}
const Internal_FakeWindowsLogfontName key(family_name, postscript_name, L"", ON_FontFaceQuartet::Member::Unset);
const Internal_FakeWindowsLogfontName* fake_name
= (const Internal_FakeWindowsLogfontName*)bsearch((const void*)&key, (const void*)fake_names, sizeof(fake_names) / sizeof(fake_names[0]), sizeof(fake_names[0]), (int(*)(const void*, const void*))Internal_FakeWindowsLogfontName::CompareFamilyAndPostscriptNameHash);
if (
fake_name
&& ON_FontFaceQuartet::Member::Unset != fake_name->QuartetMember()
&& false == fake_name->QuartetFamilyAndPostscriptNameHash().IsZeroDigestOrEmptyContentHash()
&& fake_name->FakeWindowsLogfontName().IsNotEmpty()
)
{
// this family is complicated enough that it
// has to be partitioned into multiple fake Windows LOGFONT quartets.
return fake_name->FakeWindowsLogfontName();
}
return family_name; // use family_name as the fake LOGFONT name
}
static bool Internal_TestInstalledFontsFailure()
{
return false; // <- breakpoint here
}
bool ON_Font::TestInstalledFontList(ON_TextLog& text_log)
{
const class ON_FontList& font_list = ON_Font::InstalledFontList();
const unsigned font_count = font_list.Count();
if (font_count <= 0)
{
text_log.Print("ERROR: 0 = ON_Font::InstalledFontList().Count()\n");
return Internal_TestInstalledFontsFailure();
}
const ON_SimpleArray< const class ON_Font* >& by_hash = font_list.ByFontCharacteristicsHash();
if (font_count != by_hash.UnsignedCount())
{
text_log.Print("ERROR: nullptr = ON_Font::InstalledFontList()..FromFontCharacteristicsHash(ON_Font::Default.FontCharacteristicsHash(),false)\n");
return Internal_TestInstalledFontsFailure();
}
const bool bReturnFirst = false;
bool bPassedTest = true;
text_log.Print("Testing %u installed fonts:\n", font_count);
{
const ON_TextLogIndent indent1(text_log);
text_log.Print(L"FromFontCharacteristicsHash() tests ...");
{
ON_TextLogIndent indent2(text_log);
unsigned error_count = 0;
for (unsigned i = 0; i < font_count; ++i)
{
const ON_Font* f = by_hash[i];
const ON_SHA1_Hash h = f->FontCharacteristicsHash();
const ON_Font* f1 = f = font_list.FromFontCharacteristicsHash(h, bReturnFirst);
if (f != f1)
{
if (0 == error_count)
text_log.PrintNewLine();
++error_count;
text_log.Print("ERROR: nullptr = ON_Font::InstalledFontList().FromFontCharacteristicsHash(by_hash[%u],false).\n", i);
bPassedTest = false;
}
}
if (error_count > 0)
text_log.Print("FAILED. %u errors.\n", error_count);
else
text_log.Print(" passed.\n");
}
{
const ON_Font* f = font_list.FromFontCharacteristicsHash(ON_Font::Default.FontCharacteristicsHash(), bReturnFirst);
if (nullptr == f)
{
text_log.Print("ERROR: nullptr = ON_Font::InstalledFontList()..FromFontCharacteristicsHash(ON_Font::Default.FontCharacteristicsHash(),false)\n");
bPassedTest = false;
}
}
}
const ON_ClassArray< ON_FontFaceQuartet >& quartet_list = font_list.QuartetList();
const unsigned quartet_count = quartet_list.UnsignedCount();
text_log.Print("Testing %u quartets:\n", quartet_count);
{
const ON_TextLogIndent indent1(text_log);
unsigned error_count = 0;
for (unsigned i = 0; i < quartet_count; ++i)
{
const ON_FontFaceQuartet& q = quartet_list[i];
const ON_wString qname = q.QuartetName();
if (qname.IsEmpty())
{
++error_count;
text_log.Print("ERROR: nullptr = quartet_list[%u].QuartetName() is empty\n", i);
bPassedTest = false;
continue;
}
const ON_FontFaceQuartet q1 = font_list.QuartetFromQuartetName(qname);
bool bQuartetFromQuartetNamePass = q.QuartetName() == q1.QuartetName();
const ON_Font* f[4] = { q.RegularFace(),q.BoldFace(),q.ItalicFace(),q.BoldItalicFace() };
const ON_Font* f1[4] = { q1.RegularFace(),q1.BoldFace(),q1.ItalicFace(),q1.BoldItalicFace() };
const bool bExpectedIsBoldInQuartet[4] = { false,true,false,true };
const bool bExpectedIsItalicInQuartet[4] = { false,false,true,true };
const ON_wString qface[4] = { ON_wString(L"regular"), ON_wString(L"bold"), ON_wString(L"italic"), ON_wString(L"bolditalic") };
for (unsigned k = 0; k < 4; ++k)
{
if (bQuartetFromQuartetNamePass && f[k] != f1[k])
bQuartetFromQuartetNamePass = false;
if (nullptr != f[k])
{
ON_wString qname1 = qname;
qname1 += L" (";
qname1 += qface[k];
qname1 += L")";
const ON_SHA1_Hash h = f[k]->FontCharacteristicsHash();
const ON_Font* f2 = font_list.FromFontCharacteristicsHash(h, bReturnFirst);
if (f2 != f[k])
{
++error_count;
text_log.Print("ERROR: nullptr = ON_Font::InstalledFontList().FromFontCharacteristicsHash(%ls,false).\n", qname1.Array());
bPassedTest = false;
}
const bool bIsBoldInQuartet = f[k]->IsBoldInQuartet();
const bool bIsItalicInQuartet = f[k]->IsItalicInQuartet();
if (bIsBoldInQuartet != bExpectedIsBoldInQuartet[k])
{
++error_count;
text_log.Print("ERROR: IsBoldInQuartet(%ls) = %ls.\n", qname1.Array(), bIsBoldInQuartet?L"true":L"false");
bPassedTest = false;
}
if (bIsItalicInQuartet != bExpectedIsItalicInQuartet[k])
{
++error_count;
text_log.Print("ERROR: IsItalicInQuartet(%ls) = %ls.\n", qname1.Array(), bIsItalicInQuartet ? L"true" : L"false");
bPassedTest = false;
}
}
}
if (false == bQuartetFromQuartetNamePass)
{
++error_count;
text_log.Print(L"ERROR: QuartetFromQuartetName(%ls) failed.\n",static_cast<const wchar_t*>(qname));
}
}
if (error_count > 0)
text_log.Print("FAILED. %u quartet errors.\n", error_count);
else
text_log.Print("Passed.\n");
}
return bPassedTest;
}
const ON_wString ON_Font::QuartetName(
ON_Font::NameLocale name_locale
) const
{
// The Windows OS partitions a font family (which can have many face) into
// subsets with at most 4 faces that can be tagged regular/italic/bold/bold-italic
// and assigns each subset a unique "LOGFONT" name. Note that some subsets may
// have fewer than 4 faces because there isn't an italic or bold member.
// There are Windows OS font picking tools that use
// LOGFONT name + regular/italic/bold/bold-italic to select a particular face
// and these work ok because Windows OS generates the quartets and all applications
// choosing to use the archaic regular/italic/bold/bold-italic font UI have consistent
// quartets.
//
// Modern Mac OS does not have native font selection UI that uses the
// archaic regular/italic/bold/bold-italic approach.
//
// On Mac OS, opennurbs generates a fake LOGFONT name because Rhino 6 and 7 use
// the archaic regular/italic/bold/bold-italic font UI on oth Windows and Mac.
// Helvetica Neue, American Typewriter, and Avenir are examples of common
// Mac OS font families that do not fit well with the
// archaic regular/italic/bold/bold-italic font UI. Code in
// opennurbs_apple_nsfont.cpp is used to assign fake LOGFONT names for fonts
// like these.
return WindowsLogfontName(name_locale);
}
ON_FontFaceQuartet::Member ON_Font::QuartetFaceMember() const
{
// DO NOT call any other code to guess the anser.
// This value is set only when the quartet member is known with 100% certainty.
// See IsBoldInQuartet() or IsItalicInQuartet() for code that starts
// guessing when m_quartet_member is Unset.
return this->m_quartet_member;
}
const ON_wString ON_Font::QuartetName() const
{
return ON_Font::QuartetName(ON_Font::NameLocale::LocalizedFirst);
}
const ON_wString ON_Font::QuartetDescription() const
{
ON_wString s = this->QuartetName();
if (s.IsEmpty())
return ON_wString::EmptyString;
if (s.IsNotEmpty())
{
const ON_FontFaceQuartet::Member m = ON_Font::QuartetFaceMember();
if (ON_FontFaceQuartet::Member::Unset != m)
{
s += L" (";
s += ON_FontFaceQuartet::MemberToString(m);
s += L")";
}
}
return s;
}
bool ON_Font::IsBoldInQuartet() const
{
if (ON_FontFaceQuartet::Member::Unset != this->m_quartet_member)
{
return ON_FontFaceQuartet::Member::Bold == this->m_quartet_member || ON_FontFaceQuartet::Member::BoldItalic == this->m_quartet_member;
}
// ... sigh, start guessing from imperfect information
const ON_Font::Weight font_weight = this->FontWeight();
ON_FontFaceQuartet q = FontQuartet();
if (q.IsEmpty())
{
// this font is not associated with a quartet.
// The best we can do is look at the unreliable m_font_weight setting.
// This will be wrong for light, heavy, black, ... fonts.
return ON_Font::IsBoldWeight(font_weight);
}
const bool bProbablyItalic = (ON_Font::Style::Italic == m_font_style || ON_Font::Style::Oblique == m_font_style);
const ON_Font* regular2[2] = { q.Face(false,bProbablyItalic), q.Face(false,!bProbablyItalic) };
const ON_Font* bold2[2] = { q.Face(true,bProbablyItalic), q.Face(true,!bProbablyItalic) };
if (this == regular2[0] || this == regular2[1])
return false;
if (this == bold2[0] || this == bold2[1])
return false;
const ON_SHA1_Hash this_hash = this->FontCharacteristicsHash();
if (nullptr != regular2[0] && regular2[0]->FontCharacteristicsHash() == this_hash)
return false;
if (nullptr != regular2[1] && regular2[1]->FontCharacteristicsHash() == this_hash)
return false;
if (nullptr != bold2[0] && bold2[0]->FontCharacteristicsHash() == this_hash)
return true;
if (nullptr != bold2[1] && bold2[1]->FontCharacteristicsHash() == this_hash)
return true;
if (this->IsInstalledFont())
{
// Doesn't work with managed fonts (missing) because their quartets are typically not completely filled in.
// NOTE The q.IsEmpty() test above insures at least one of regular2[] or bold2[] has a non nullptr.
if (nullptr == bold2[0] && nullptr == bold2[1])
return false; // every face in this quartet is regular.
if (nullptr == regular2[0] && nullptr == regular2[1])
return true; // every face in this quartet is bold.
}
if (ON_Font::Weight::Unset == this->FontWeight())
return false; // who knows?
if (nullptr == regular2[0])
regular2[0] = regular2[1];
if (nullptr == bold2[0])
bold2[0] = bold2[1];
unsigned int regular_weight;
if (nullptr != regular2[0] && ON_Font::Weight::Unset != regular2[0]->FontWeight())
regular_weight = static_cast<unsigned>(regular2[0]->FontWeight());
else if (nullptr != bold2[0] && ON_Font::Weight::Unset != bold2[0]->FontWeight())
regular_weight = static_cast<unsigned>(bold2[0]->FontWeight())-1;
else
regular_weight = static_cast<unsigned>(ON_Font::Weight::Normal);
return static_cast<unsigned>(font_weight) > regular_weight;
}
bool ON_Font::IsItalicInQuartet() const
{
if (ON_FontFaceQuartet::Member::Unset != this->m_quartet_member)
{
return ON_FontFaceQuartet::Member::Italic == this->m_quartet_member || ON_FontFaceQuartet::Member::BoldItalic == this->m_quartet_member;
}
const bool bProbablyItalic = (ON_Font::Style::Italic == m_font_style || ON_Font::Style::Oblique == m_font_style);
const ON_FontFaceQuartet q = InstalledFontQuartet(); // only look at installed quartets for italic query
if (q.IsEmpty())
return bProbablyItalic; // this font is not in a quartet
const ON_SHA1_Hash this_hash = this->FontCharacteristicsHash();
const ON_Font* upright2[2] = { q.RegularFace(), q.BoldFace() };
if (nullptr != upright2[0] && upright2[0]->FontCharacteristicsHash() == this_hash)
return false;
if (nullptr != upright2[1] && upright2[1]->FontCharacteristicsHash() == this_hash)
return false;
const ON_Font* italic2[2] = { q.ItalicFace(), q.BoldItalicFace() };
if (nullptr != italic2[0] && italic2[0]->FontCharacteristicsHash() == this_hash)
return true;
if (nullptr != italic2[1] && italic2[1]->FontCharacteristicsHash() == this_hash)
return true;
// NOTE The q.IsEmpty() test above insures at least one of upright2[] or italic2[] has a non nullptr.
if (nullptr == italic2[0] && nullptr == italic2[1])
return false; // every font in htis quartet is upright.
if (nullptr == upright2[0] && nullptr == upright2[1])
return true; // every font in this quartet is italic.
bool bBoldInQuartet = this->IsBoldInQuartet();
const ON_Font* upright = upright2[bBoldInQuartet ? 1 : 0];
const ON_Font* italic = italic2[bBoldInQuartet ? 1 : 0];
if (nullptr == upright && nullptr == italic)
{
// Assume regular/bold is not reliable
upright = upright2[bBoldInQuartet ? 0 : 1];
italic = italic2[bBoldInQuartet ? 0 : 1];
}
if (nullptr == italic)
return false; // No italic with same regular/bold in this quartet.
if (nullptr == upright)
return true; // No upright with same regular/bold in this quartet.
return bProbablyItalic; // best we can do
}
const ON_FontFaceQuartet ON_Font::FontQuartet() const
{
const ON_wString quartet_name[2] = { QuartetName(), this->RichTextFontName() };
const ON_SHA1_Hash quartet_name_hash[2] = { ON_Font::FontNameHash(quartet_name[0],false), ON_Font::FontNameHash(quartet_name[1],false) };
const int imin = quartet_name[0].IsNotEmpty() ? 0 : 1;
const int imax = (quartet_name[1].IsNotEmpty() && (quartet_name[0].IsEmpty() || quartet_name_hash[0] != quartet_name_hash[1])) ? 1 : 0;
for (int i = imin; i <= imax; ++i)
{
ON_FontFaceQuartet installed_quartet = ON_Font::InstalledFontList().QuartetFromQuartetName(quartet_name[i]);
if (installed_quartet.IsNotEmpty())
return installed_quartet;
}
for (int i = imin; i <= imax; ++i)
{
ON_FontFaceQuartet managed_quartet = ON_Font::ManagedFontList().QuartetFromQuartetName(quartet_name[i]);
if (managed_quartet.IsNotEmpty())
return managed_quartet;
}
return ON_FontFaceQuartet::Empty;
}
const ON_FontFaceQuartet ON_Font::InstalledFontQuartet() const
{
return InstalledFontList().QuartetFromQuartetName(QuartetName());
}
const ON_wString& ON_Font::Internal_GetName(
ON_Font::NameLocale name_locale,
const ON_wString& localized_name,
const ON_wString& english_name
)
{
if (ON_Font::NameLocale::Localized == name_locale)
return localized_name;
if (ON_Font::NameLocale::English == name_locale)
return english_name;
return localized_name.IsNotEmpty() ? localized_name : english_name;
}
const ON_wString Internal_RichTextCleaner
(
const wchar_t* dirty_name
)
{
ON_wString clean_name(dirty_name);
clean_name.TrimLeftAndRight();
clean_name.Remove(ON_wString::Space);
clean_name.Remove(ON_wString::HyphenMinus);
return clean_name;
}
const ON_wString ON_Font::RichTextFontName() const
{
return ON_Font::RichTextFontName(this, false);
}
const ON_wString ON_Font::RichTextFontName(
const ON_Font* font,
bool bDefaultIfEmpty
)
{
// Depending on what created the RTF or what will be parsing the RTF,
// the face name in the RTF can be a PostScript name, LOGFONT.lfFaceName,
// IDWriteFont family name, ..., and probably several other choices.
// The complete PostScript name is a bad choice. It doesn't work for
// WIndows RTF readers. Opennurbs relies on a hueristic to convert
// PostScript names to family names.
//
// The Windows LOGFONT.lfFaceName is a bad choice. It doesn't work for
// Apple RTF readers but it works better on Windows readers than
// the proper family name for faces like "Arial Narrow" and "Arial Black"
// In these cases. "Arial" is the family name, but there are generally
// not enough details in RTF files and the code that parses it to recover
// and select the Narrow or Black face if the family is set to Arial.
//
// For fonts that have only the 4 standard faces (regular, bold, italic,
// and bold-italic), the ON_Font::FamilyName() generally works well both
// platforms. However, it is not easy to determine when a font will only
// be published with the 4 standard faces and popular fonts often have
// more than 4 faces so they can offer a wider variety of weight,
// stretch/width, and style/slope options.
//
// The more general problem is that modern fonts and font rendering
// has outgrown the limitations codes that parse RTF.
const ON_Font* fonts[2]{ font, bDefaultIfEmpty ? &ON_Font::Default : nullptr };
ON_wString rtf_font_name;
for (int i = 0; i < 2; i++)
{
const ON_Font* f = fonts[i];
if (nullptr == f)
continue;
// It is important to use the WindowsLogfontName
// before resorting to family name. This gives the best
// results for getting the right answer from font faces
// like "Arial Narrow" and "Arial Black". In both cases,
// "Arial" is the more correct choice of a family name
// but there are generally not enough details in RTF files
// and the code that parses it to use the Narrow or Black
// face if the family is set to Arial.
rtf_font_name = f->WindowsLogfontName();
if (rtf_font_name.IsNotEmpty())
break;
rtf_font_name = f->FamilyName();
if (rtf_font_name.IsNotEmpty())
break;
rtf_font_name = ON_Font::FamilyNameFromDirtyName(f->PostScriptName());
if (rtf_font_name.IsNotEmpty())
break;
// DO NOT USE FACE NAME - it is a term like "Regular" / "Bold" / ...and is useless.
}
if (rtf_font_name.IsEmpty() && bDefaultIfEmpty)
{
rtf_font_name = ON_Font::DefaultFamilyName();
}
return rtf_font_name;
}
/*
InternalHashToName is used to map hash values from
a ON_Font::FontNameHash() to the corresponding correct font Family name.
*/
class InternalHashToName
{
public:
InternalHashToName() = default;
~InternalHashToName() = default;
InternalHashToName(const InternalHashToName&) = default;
InternalHashToName& operator=(const InternalHashToName&) = default;
public:
InternalHashToName
(
const wchar_t* dirty_name,
const wchar_t* clean_family_name
)
: m_dirty_name_hash(ON_Font::FontNameHash(dirty_name,true))
, m_family_name(clean_family_name)
{}
InternalHashToName
(
const wchar_t* clean_family_name
)
: m_dirty_name_hash(ON_Font::FontNameHash(clean_family_name,false))
, m_family_name(clean_family_name)
{}
public:
static int CompareHash(const void* lhs, const void* rhs)
{
return ON_SHA1_Hash::Compare(((const InternalHashToName*)lhs)->m_dirty_name_hash, ((const InternalHashToName*)rhs)->m_dirty_name_hash);
}
static size_t SortAndCullByHash(InternalHashToName* a, size_t a_count)
{
if (nullptr == a || a_count <= 0)
return 0;
ON_qsort(a, a_count, sizeof(a[0]), InternalHashToName::CompareHash);
size_t culled_count = 1;
const InternalHashToName* k1 = &a[0];
for (size_t i = 1; i < a_count; i++)
{
if (0 == InternalHashToName::CompareHash(k1, &a[i]) )
continue;
a[culled_count] = a[i];
k1 = &a[culled_count];
culled_count++;
}
return culled_count;
}
public:
ON_SHA1_Hash m_dirty_name_hash = ON_SHA1_Hash::ZeroDigest;
const wchar_t* m_family_name = nullptr;
};
static ON_OutlineFigure::Type Internal_FigureTypeFromHashedFontName(
const InternalHashToName* key
)
{
static InternalHashToName single_stroke_name_map[] =
{
// Known single stroke fonts names
// Machine Tool
InternalHashToName(L"Machine Tool Gothic"), // Family
InternalHashToName(L"MachineToolGothic"), // PostScript
InternalHashToName(L"Machine Tool Grenadier"), // Family
InternalHashToName(L"MachineToolGrenadier"), // PostScript
InternalHashToName(L"Machine Tool Handscript"), // Family
InternalHashToName(L"MachineToolHandscript"), // PostScript
InternalHashToName(L"Machine Tool SanSerif"), // Family
InternalHashToName(L"MachineToolSanSerif"), // PostScript
InternalHashToName(L"Machine Tool Script"), // Family
InternalHashToName(L"MachineToolScript"), // PostScript
// MecSoft
InternalHashToName(L"MecSoft_Font-1"), // Family
InternalHashToName(L"MecSoft_Font-1"), // PostScript
// Orach Technic 1L
InternalHashToName(L"OrachTech1Lotf"), // Family
InternalHashToName(L"OrachTech1Lotf"), // PostScript
InternalHashToName(L"OrachTechDemo1Lotf"), // Family
InternalHashToName(L"OrachTechDemo1Lotf"), // PostScript
InternalHashToName(L"OrachTech1Lttf"), // Family
InternalHashToName(L"OrachTech1Lttf"), // PostScript
InternalHashToName(L"OrachTechDemo1Lttf"), // Family
InternalHashToName(L"OrachTechDemo1Lttf"), // PostScript
// Rhino Single Stroke by Lowell Walmsley
InternalHashToName(L"RHSS"), // Family
InternalHashToName(L"RhSS-Normal"), // PostScript
// SD Stroke Open Type Font
InternalHashToName(L"SDstroke"), // Family
InternalHashToName(L"SDstroke-latin"), // PostScript
// Single line fonts
// Included here for now until
// https://mcneel.myjetbrains.com/youtrack/issue/RH-57328
// Gets fonts with the field 10 description set.
InternalHashToName(L"SLF-RHN Architect"), // Family
InternalHashToName(L"SLF-RHN-Architect"), // PostScript
InternalHashToName(L"SLF-RHN Industrial"), // Family
InternalHashToName(L"SLF-RHN-Industrial"), // PostScript
InternalHashToName(L"SLF-RHN White Linen"), // Family
InternalHashToName(L"SLF-RHN-WhiteLiinen"), // PostScript
// https://mcneel.myjetbrains.com/youtrack/issue/RH-67652
InternalHashToName(L"AVGauge Engraving"), // Family
InternalHashToName(L"AVGauge_Engraving"), // PostScript
};
static InternalHashToName double_stroke_name_map[] =
{
// Known double stroke fonts names
// If a double stroke font is treated as a single stroke font,
// then the "TrueType" automatic closure segment is dropped
// and glyphs for code points like o O 8 Q, ... have missing segments.
// CamBam Stick fonts
InternalHashToName(L"1CamBam_Stick_1"), // Family
InternalHashToName(L"1CamBamStick1-normal"), // PostScript
InternalHashToName(L"1CamBam_Stick_2"), // Family
InternalHashToName(L"1CamBamStick2-normal"), // PostScript
InternalHashToName(L"1CamBam_Stick_3"), // Family
InternalHashToName(L"1CamBamStick3-normal"), // PostScript
InternalHashToName(L"1CamBam_Stick_4"), // Family
InternalHashToName(L"1CamBamStick4-normal"), // PostScript
InternalHashToName(L"1CamBam_Stick_5"), // Family
InternalHashToName(L"1CamBamStick5-normal"), // PostScript
InternalHashToName(L"1CamBam_Stick_6"), // Family
InternalHashToName(L"1CamBamStick6-normal"), // PostScript
InternalHashToName(L"1CamBam_Stick_7"), // Family
InternalHashToName(L"1CamBamStick7-normal"), // PostScript
InternalHashToName(L"1CamBam_Stick_8"), // Family
InternalHashToName(L"1CamBamStick8-normal"), // PostScript
InternalHashToName(L"1CamBam_Stick_9"), // Family
InternalHashToName(L"1CamBamStick9"), // PostScript
// CNC Vector
InternalHashToName(L"CNC Vector"), // Family
InternalHashToName(L"CNCVector"), // PostScript
InternalHashToName(L"CNC Vector Mirror"), // Family
InternalHashToName(L"CNCVectorMirror"), // PostScript
// NOTE WELL:
// Orach Technic 2L fonts are NOT a double stroke fonts.
// The figure outlines are simple closed perimenters
// that contain postive areas.
// The behave poorly in simulated bold face.
};
static InternalHashToName perimeter_name_map[] =
{
// Known perimeter stroke fonts names
// In fact, almost every TrueType, PostScript, and OpenType font
// has periemter figures. The list below contains fonts
// that some people might think are single or double stroke
// and common fonts so we don't keep checking.
// Common
InternalHashToName(L"Arial"),
InternalHashToName(L"Bahnschrift"),
InternalHashToName(L"CityBlueprint"),
InternalHashToName(L"CountryBlueprint"),
InternalHashToName(L"Courier"),
InternalHashToName(L"Courier New"),
InternalHashToName(L"Franklin Gothic"),
InternalHashToName(L"Helvetica"),
InternalHashToName(L"Helvetica Neue"),
InternalHashToName(L"Segoe UI"),
InternalHashToName(L"Times New Roman"),
InternalHashToName(L"Tecnnic"),
InternalHashToName(L"ArialMT"),
InternalHashToName(L"CourierNewPS"),
InternalHashToName(L"CourierNewPSMT"),
InternalHashToName(L"FranklinGothic"),
InternalHashToName(L"HelveticaNeue"),
InternalHashToName(L"TimesNewRomanPS"),
InternalHashToName(L"TimesNewRomanPSMT"),
// Orach Technic 1L
InternalHashToName(L"OrachTech2Lotf"), // Family
InternalHashToName(L"OrachTech2Lotf"), // PostScript
InternalHashToName(L"OrachTechDemo2Lotf"), // Family
InternalHashToName(L"OrachTechDemo2Lotf"), // PostScript
InternalHashToName(L"OrachTech2Lttf"), // Family
InternalHashToName(L"OrachTech2Lttf"), // PostScript
InternalHashToName(L"OrachTechDemo2Lttf"), // Family
InternalHashToName(L"OrachTechDemo2Lttf"), // PostScript
};
static size_t single_stroke_name_map_count = 0;
static size_t double_stroke_name_map_count = 0;
static size_t perimeter_name_map_count = 0;
if ( nullptr == key)
return ON_OutlineFigure::Type::Unknown;
const InternalHashToName* e;
const size_t sizeof_e = sizeof(*e);
if (0 == single_stroke_name_map_count)
{
single_stroke_name_map_count = InternalHashToName::SortAndCullByHash(single_stroke_name_map, (sizeof(single_stroke_name_map) / sizeof_e));
double_stroke_name_map_count = InternalHashToName::SortAndCullByHash(double_stroke_name_map, (sizeof(double_stroke_name_map) / sizeof_e));
perimeter_name_map_count = InternalHashToName::SortAndCullByHash(perimeter_name_map, (sizeof(perimeter_name_map) / sizeof_e));
}
e = (const InternalHashToName*)bsearch(key, single_stroke_name_map, single_stroke_name_map_count, sizeof_e, InternalHashToName::CompareHash);
if (nullptr != e)
return ON_OutlineFigure::Type::SingleStroke;
e = (const InternalHashToName*)bsearch(key, double_stroke_name_map, double_stroke_name_map_count, sizeof_e, InternalHashToName::CompareHash);
if (nullptr != e)
return ON_OutlineFigure::Type::DoubleStroke;
e = (const InternalHashToName*)bsearch(key, perimeter_name_map, perimeter_name_map_count, sizeof_e, InternalHashToName::CompareHash);
if (nullptr != e)
return ON_OutlineFigure::Type::Perimeter;
// return Unset instead of Unknown in this internal tool
return ON_OutlineFigure::Type::Unset;
}
static bool Internal_IsEngravingFont(
const InternalHashToName* key
)
{
// The structure of the font is a "perimeter", but the intended use
// is for engraving. The OrachTech*2* fonts behave poorly when used
// as perimeter fonts.
static InternalHashToName double_line_name_map[] =
{
// Orach Technic 2L
InternalHashToName(L"OrachTech2Lotf"), // Family
InternalHashToName(L"OrachTech2Lotf"), // PostScript
InternalHashToName(L"OrachTechDemo2Lotf"), // Family
InternalHashToName(L"OrachTechDemo2Lotf"), // PostScript
InternalHashToName(L"OrachTech2Lttf"), // Family
InternalHashToName(L"OrachTech2Lttf"), // PostScript
InternalHashToName(L"OrachTechDemo2Lttf"), // Family
InternalHashToName(L"OrachTechDemo2Lttf"), // PostScript
};
static size_t double_line_name_map_count = 0;
const InternalHashToName* e;
const size_t sizeof_e = sizeof(*e);
if (0 == double_line_name_map_count)
{
double_line_name_map_count = InternalHashToName::SortAndCullByHash(double_line_name_map, (sizeof(double_line_name_map) / sizeof_e));
}
e = (const InternalHashToName*)bsearch(key, double_line_name_map, double_line_name_map_count, sizeof_e, InternalHashToName::CompareHash);
if (nullptr != e)
return true;
ON_OutlineFigure::Type outline_type = Internal_FigureTypeFromHashedFontName(key);
if (ON_OutlineFigure::Type::SingleStroke == outline_type)
return true;
if (ON_OutlineFigure::Type::DoubleStroke == outline_type)
return true;
return false;
}
const wchar_t* ON_OutlineFigure::OrientationToWideString(
ON_OutlineFigure::Orientation orientation
)
{
const wchar_t* s;
switch (orientation)
{
case ON_OutlineFigure::Orientation::Unset:
s = L"Unset";
break;
case ON_OutlineFigure::Orientation::CounterClockwise:
s = L"CounterClockwise";
break;
case ON_OutlineFigure::Orientation::Clockwise:
s = L"Clockwise";
break;
case ON_OutlineFigure::Orientation::NotOriented:
s = L"NotOriented";
break;
case ON_OutlineFigure::Orientation::Error:
s = L"Error";
break;
default:
s = L"<INVALID>";
break;
}
return s;
}
ON_OutlineFigure::Type ON_OutlineFigure::FigureTypeFromFontName(
const wchar_t* font_name
)
{
if (nullptr == font_name || 0 == font_name[0])
return ON_OutlineFigure::Type::Unset; // better than unknown when name is empty
const InternalHashToName key(font_name, nullptr);
return Internal_FigureTypeFromHashedFontName(&key);
}
ON_OutlineFigure::Type ON_OutlineFigure::FigureTypeFromField10Description(
const ON_wString field_10_description
)
{
ON_wString description(field_10_description);
// ignore white space and hyphens
description.Remove(ON_wString::Space);
description.Remove(ON_wString::Tab);
description.Remove(ON_wString::HyphenMinus);
const int description_len = description.Length();
if (description_len > 0)
{
description.MakeLowerOrdinal();
const ON_wString s[2] = {
ON_wString(L"singlestroke"),
ON_wString(L"doublestroke")
};
const ON_OutlineFigure::Type t[2] = {
ON_OutlineFigure::Type::SingleStroke,
ON_OutlineFigure::Type::DoubleStroke
};
size_t count = sizeof(t) / sizeof(t[0]);
for (size_t i = 0; i < count; ++i)
{
if (description.Find(s[i]) >= 0)
return t[i];
}
}
return ON_OutlineFigure::Type::Unset;
}
ON_OutlineFigure::Type ON_Font::OutlineFigureType() const
{
if (m_outline_figure_type != ON_OutlineFigure::Type::Unset)
return m_outline_figure_type;
const ON_wString names[] =
{
FamilyName(),
FamilyName(ON_Font::NameLocale::English),
PostScriptName(),
PostScriptName(ON_Font::NameLocale::English)
};
InternalHashToName key[sizeof(names)/sizeof(names[0])];
const int name_count = (int)(sizeof(names) / sizeof(names[0]));
int key_count = 0;
for (int i = 0; i < name_count; i++)
{
const ON_wString& name = names[i];
if (name.IsEmpty())
continue;
// computing the hash is much more expensive than checking for duplicate names (which is common)
bool bSkipName = name.IsEmpty();
for (int j = 0; false == bSkipName && j < i; j++)
bSkipName = (name == names[j]);
if (bSkipName)
continue;
// compute name hash
key[key_count] = InternalHashToName(name, nullptr);
// searching for a duplicate hash is more expensive than checking for duplicate hash
// (which is common because of space and hyphen differences between family and postscript names)
for (int j = 0; false == bSkipName && j < key_count; j++)
bSkipName = (key[key_count].m_dirty_name_hash == key[i].m_dirty_name_hash);
if (bSkipName)
continue;
// search for matching name hash is lists of known single stroke and double stroke fonts
ON_OutlineFigure::Type figure_type = Internal_FigureTypeFromHashedFontName(&key[key_count]);
key_count++;
if (
ON_OutlineFigure::Type::Unset != figure_type
&& ON_OutlineFigure::Type::Unknown != figure_type
&& ON_OutlineFigure::Type::Mixed != figure_type
)
return figure_type;
}
return ON_OutlineFigure::Type::Unknown; // unknown means don't try again
}
bool ON_Font::IsSingleStrokeFont() const
{
return ON_OutlineFigure::Type::SingleStroke == OutlineFigureType();
}
bool ON_Font::IsDoubleStrokeFont() const
{
return ON_OutlineFigure::Type::DoubleStroke == OutlineFigureType();
}
bool ON_Font::IsSingleStrokeOrDoubleStrokeFont() const
{
const ON_OutlineFigure::Type figure_type = OutlineFigureType();
return (
ON_OutlineFigure::Type::SingleStroke == figure_type
|| ON_OutlineFigure::Type::DoubleStroke == figure_type
);
}
const ON_Font* ON_Font::DefaultEngravingFont()
{
// The PostScript name works on Widows and is the best way on Apple.
/*
Font description = "SLF-RHN Architect Regular"
Family name = "SLF-RHN Architect"
Face name = "Regular"
PostScript name = "SLFRHNArchitect-Regular"
Quartet = SLF-RHN Architect (Regular)
Windows LOGFONT name = "SLF-RHN Architect"
Rich text name = "SLF-RHN Architect"
Origin = Windows Font
Outline type = Engraving - single stroke
PointSize = annotation default
Quartet: SLF-RHN Architect (Regular member)
Weight = Normal
Stretch = Medium
Style = Upright
Underlined = false
Strikethrough = false
Symbol font = false
Engraving font = Single-stroke
Font characteristics SHA-1 hash = E5527949C70756BFBC586AB04A9CFAD9FB5D9038
LOGFONT
*/
static const ON_Font* default_engraving_font = nullptr;
if (nullptr == default_engraving_font)
{
default_engraving_font = ON_Font::InstalledFontList().FromNames(
L"SLFRHNArchitect-Regular", // postscript_name,
L"SLF-RHN Architect", // windows_logfont_name,
L"SLF-RHN Architect", // family_name,
L"Regular", // prefered_face_name,
ON_Font::Weight::Normal, // prefered_weight,
ON_Font::Stretch::Medium, // prefered_stretch,
ON_Font::Style::Upright, // prefered_style,
false, // bRequireFaceMatch,
false, // bRequireStyleMatch,
false, // bUnderlined,
false, // bStrikethrough,
0.0 // point_size
);
}
return default_engraving_font;
}
bool ON_Font::IsEngravingFont() const
{
const ON_wString names[] =
{
FamilyName(),
FamilyName(ON_Font::NameLocale::English),
PostScriptName(),
PostScriptName(ON_Font::NameLocale::English)
};
InternalHashToName key[sizeof(names)/sizeof(names[0])];
const int name_count = (int)(sizeof(names) / sizeof(names[0]));
int key_count = 0;
for (int i = 0; i < name_count; i++)
{
const ON_wString& name = names[i];
if (name.IsEmpty())
continue;
// computing the hash is much more expensive than checking for duplicate names (which is common)
bool bSkipName = name.IsEmpty();
for (int j = 0; false == bSkipName && j < i; j++)
bSkipName = (name == names[j]);
if (bSkipName)
continue;
// compute name hash
key[key_count] = InternalHashToName(name, nullptr);
// searching for a duplicate hash is more expensive than checking for duplicate hash
// (which is common because of space and hyphen differences between family and postscript names)
for (int j = 0; false == bSkipName && j < key_count; j++)
bSkipName = (key[key_count].m_dirty_name_hash == key[i].m_dirty_name_hash);
if (bSkipName)
continue;
// search for matching name hash is lists of known single stroke and double stroke fonts
bool bIsEngravingFont = Internal_IsEngravingFont(&key[key_count]);
key_count++;
if (bIsEngravingFont)
return true;
}
return false;
}
const ON_wString ON_Font::FamilyNameFromDirtyName(
const wchar_t* dirty_name
)
{
if (nullptr == dirty_name
|| 0 == dirty_name[0]
|| ON_wString::HyphenMinus == dirty_name[0])
{
return ON_wString::EmptyString;
}
// incredibly common query
if (ON_wString::EqualOrdinal(ON_Font::DefaultFamilyName(), dirty_name, true))
{
return ON_wString(ON_Font::DefaultFamilyName());
}
static bool bInitialized = false;
static ON_SimpleArray<InternalHashToName> clean_names;
static ON_SimpleArray<InternalHashToName> dirty_names;
const size_t sizeof_name = sizeof(clean_names[0]);
if (false == bInitialized)
{
bInitialized = true;
ON_MemoryAllocationTracking disable_tracking(false);
ON_SimpleArray<const ON_Font*> installed_fonts;
ON_Font::GetInstalledFontList(installed_fonts);
size_t installed_count = installed_fonts.UnsignedCount();
clean_names.Reserve(16 + installed_count);
dirty_names.Reserve(16 + 2*installed_count);
// Append known clean family names that contain a hypen.
clean_names.Append(InternalHashToName(L"AvenirLT-Roman"));
clean_names.Append(InternalHashToName(L"MecSoft_Font-1"));
clean_names.Append(InternalHashToName(L"MingLiU_HKSCS-ExtB"));
clean_names.Append(InternalHashToName(L"MingLiU-ExtB"));
clean_names.Append(InternalHashToName(L"OCR-A II"));
clean_names.Append(InternalHashToName(L"PMingLiU-ExtB"));
clean_names.Append(InternalHashToName(L"SimSun-ExtB"));
// Append known clean family names that end in "MT"
clean_names.Append(InternalHashToName(L"Brush Script MT"));
clean_names.Append(InternalHashToName(L"Devanagari MT"));
clean_names.Append(InternalHashToName(L"French Script MT"));
clean_names.Append(InternalHashToName(L"Gujarati MT"));
clean_names.Append(InternalHashToName(L"Gurmukhi MT"));
clean_names.Append(InternalHashToName(L"New Peninim MT"));
clean_names.Append(InternalHashToName(L"OCR B MT"));
// Append common clean family names (in case they are not installed on this computer)
clean_names.Append(InternalHashToName(L"Arial"));
clean_names.Append(InternalHashToName(L"Bahnschrift"));
clean_names.Append(InternalHashToName(L"CityBlueprint")); // common in ACAD dwg files
clean_names.Append(InternalHashToName(L"CountryBlueprint")); // common in ACAD dwg files
clean_names.Append(InternalHashToName(L"Courier"));
clean_names.Append(InternalHashToName(L"Courier New"));
clean_names.Append(InternalHashToName(L"Franklin Gothic"));
clean_names.Append(InternalHashToName(L"Helvetica"));
clean_names.Append(InternalHashToName(L"Helvetica Neue"));
clean_names.Append(InternalHashToName(L"Segoe UI"));
clean_names.Append(InternalHashToName(L"Times New Roman"));
clean_names.Append(InternalHashToName(L"Tecnnic")); // common in ACAD dwg files
// Append common dirty names (in case they are not installed on this computer)
dirty_names.Append(InternalHashToName(L"ArialMT", L"Arial"));
dirty_names.Append(InternalHashToName(L"CourierNewPS", L"Courier New")); // Handles all CourierNewPS-...MT names
dirty_names.Append(InternalHashToName(L"CourierNewPSMT", L"Courier New"));
dirty_names.Append(InternalHashToName(L"FranklinGothic", L"Franklin Gothic"));
dirty_names.Append(InternalHashToName(L"HelveticaNeue", L"Helvetica Neue")); // Handles all PostScript "HelveticaNeue-..." names
dirty_names.Append(InternalHashToName(L"TimesNewRomanPS", L"Times New Roman")); // Handles all TimesNewRomanPS-...MT names
dirty_names.Append(InternalHashToName(L"TimesNewRomanPSMT", L"Times New Roman"));
const wchar_t* prev_clean_family_name = nullptr;
for (size_t i = 0; i < installed_count; i++)
{
#if defined(ON_RUNTIME_ANDROID) || defined(ON_RUNTIME_LINUX) || defined(ON_RUNTIME_WASM)
const ON_Font* font = installed_fonts[(int)i];
#else
const ON_Font* font = installed_fonts[i];
#endif
if (nullptr == font)
continue;
// NOTE:
// font and the clean_family_name pointer persist for the lifetime of the application.
const wchar_t* clean_family_name = static_cast<const wchar_t*>(font->FamilyName());
if (nullptr == clean_family_name || 0 == clean_family_name[0])
continue;
// Add clean_family_name to clean_names[]
if (false == ON_wString::EqualOrdinal(clean_family_name, prev_clean_family_name, true))
{
// Since the installed font list is sorted by family name, we can limit the sorting
// and culling time for clean_names[] by keeping track of prev_clean_family_name
clean_names.Append(InternalHashToName(clean_family_name));
prev_clean_family_name = clean_family_name;
}
// Add PostScript name and LOGFONT.lfFaceName to dirty_names[]
for (int j = 0; j < 2; j++)
{
// NOTE:
// font and the other_name pointer persist for the lifetime of the application.
//
// Do NOT use the font->Description() string. That string does not persist
// And it adds to additional functionallity because it has the form <clean family name>-...
const wchar_t* other_name
= (0 == j)
? static_cast<const wchar_t*>(font->PostScriptName())
: static_cast<const wchar_t*>(font->WindowsLogfontName());
if (nullptr == other_name || 0 == other_name[0])
continue;
if (ON_wString::EqualOrdinal(clean_family_name, other_name, true))
{
// LOGFONT.lfFaceName or PostScript name = clean family name
// and does not need to be in the "dirty" list. If this name
// is ever passed in, it woll be handled by the clean_list[] entry.
continue;
}
dirty_names.Append(InternalHashToName(other_name,clean_family_name));
}
}
// cull duplicates and ambiguous cases
for (int adex = 0; adex < 2; adex++)
{
ON_SimpleArray<InternalHashToName>& a
= (0 == adex)
? clean_names
: dirty_names;
const size_t count0 = a.UnsignedCount();
ON_qsort(a.Array(), count0, sizeof_name, InternalHashToName::CompareHash);
int count1 = 0;
InternalHashToName candidate;
for (size_t i = 0; i < count0; i++)
{
#if defined(ON_RUNTIME_ANDROID) || defined(ON_RUNTIME_LINUX) || defined(ON_RUNTIME_WASM)
InternalHashToName e = a[(int)i];
#else
InternalHashToName e = a[i];
#endif
if (candidate.m_dirty_name_hash == e.m_dirty_name_hash)
{
if (nullptr == candidate.m_family_name)
continue;
if (ON_wString::EqualOrdinal(candidate.m_family_name, e.m_family_name, true))
continue; // duplicate
// rare ambiguous case
// Fonts with different family names have the same dirty name.
candidate.m_family_name = nullptr;
continue;
}
if (candidate.m_family_name)
{
a[count1++] = candidate;
}
candidate = e;
}
if (candidate.m_family_name)
{
a[count1++] = candidate;
}
a.SetCount(count1);
a.Shrink();
}
}
const InternalHashToName* e = nullptr;
const InternalHashToName clean_key(dirty_name);
e = (const InternalHashToName*)bsearch(&clean_key, clean_names.Array(), clean_names.UnsignedCount(), sizeof_name, InternalHashToName::CompareHash);
if (nullptr != e)
{
// Ignoring case, spaces, and hyphens, dirty_name is a clean family.
return ON_wString(e->m_family_name);
}
const InternalHashToName dirty_key(dirty_name, dirty_name);
e = (const InternalHashToName*)bsearch(&dirty_key, dirty_names.Array(), dirty_names.UnsignedCount(), sizeof_name, InternalHashToName::CompareHash);
if (nullptr != e)
{
// Ignoring case, spaces, and hyphens, dirty_name is a PostScript or LOGFONT.lfFaceName
// and we know the corresponding clean family name.
return ON_wString(e->m_family_name);
}
// No clean family name is exlicity known for the dirty_name parameter.
// Truncate the input dirty_name at the first hypen to remove PostScript face descriptions.
// (This is the reason we explicty add all known faces with hypens above).
ON_wString best_guess(dirty_name);
for (const wchar_t* c = dirty_name; 0 != *c; c++)
{
if (ON_wString::HyphenMinus != *c)
continue;
// Truncate dirty_name at the first hyphen
int len = (int)(c - dirty_name);
if (len >= 6 && 'M' == dirty_name[len - 2] && 'T' == dirty_name[len - 1])
{
// dirty_name = "...MT-...". Assume that we are dealing with a PostScript name
// and the clean family name does not include the MT.
// Above we have explicity handled known clean family names that end in MT.
len -= 2;
}
best_guess.SetLength(len);
break;
}
return best_guess;
}
const ON_String ON_Font::CleanFontName(
const wchar_t* dirty_font_name,
ON_StringMapOrdinalType map
)
{
ON_wString clean_font_name(dirty_font_name);
wchar_t* s0 = clean_font_name.Array();
wchar_t* s1 = s0;
const bool bMap = (ON_StringMapOrdinalType::Identity != map);
for (wchar_t* s = s0, c = *s++; 0 != c; c = *s++)
{
if (
ON_wString::Space == c
|| ON_wString::HyphenMinus == c
|| ON_wString::Underscore == c
)
{
// PostScript, DWrite, LOGFONT all treat spaces, underbars and hyphens
// differently. The purpose of this function is to get a string that
// can be used to search for matching fonts. Ignoring spaces, underbars and hyphens
// makes it possible to find installed fonts using a "dirty name" from another computer.
continue;
}
if (bMap)
c = ON_wString::MapCharacterOrdinal(map, c);
*s1++ = c;
}
*s1 = 0;
clean_font_name.SetLength(s1 - s0);
if (map != ON_StringMapOrdinalType::Identity)
{
return clean_font_name.MapStringOrdinal(map);
}
return clean_font_name;
}
const ON_SHA1_Hash ON_Font::FontNameHash(
const wchar_t* font_name,
bool bStopAtHyphen
)
{
ON_SHA1 sha1;
const wchar_t* font_name0 = font_name;
for (wchar_t c = (nullptr != font_name) ? *font_name++ : 0; 0 != c; c = *font_name++)
{
if (ON_wString::Space == c)
continue;
if (ON_wString::Underscore == c)
continue;
if (ON_wString::HyphenMinus == c)
{
if (bStopAtHyphen)
{
// The hyphens in these special cases are integral parts of the
// family part of the PostScript name and cannot terminate hashing.
if (font_name == (font_name0 + 6) && ON_wString::EqualOrdinal(L"Arial-Black", 11, font_name0, 11, true))
continue;
if (font_name == (font_name0 + 3) && ON_wString::EqualOrdinal(L"MS-", 3, font_name0, 3, true))
continue;
if (font_name == (font_name0 + 4) && ON_wString::EqualOrdinal(L"OCR-A", 5, font_name0, 5, true))
continue;
if (font_name == (font_name0 + 9) && ON_wString::EqualOrdinal(L"AvenirLT-Roman", 14, font_name0, 14, true))
continue;
if (font_name == (font_name0 + 12) && ON_wString::EqualOrdinal(L"MecSoftFont-1", 13, font_name0, 13, true))
continue;
if (font_name == (font_name0 + 13) && ON_wString::EqualOrdinal(L"MecSoft_Font-1", 14, font_name0, 14, true))
continue;
// Single Line Font face names include the following. The hypen after SLF and the hyphen after RHN-
// are part of the face name.
// SLF-RHN-Architect
// SLF-RHN-Industrial
// SLF-RHN-WhiteLiinen (yup, Linen with ii)
if (font_name == (font_name0 + 4) && ON_wString::EqualOrdinal(L"SLF-", 4, font_name0, 4, true))
continue;
if (font_name == (font_name0 + 8) && ON_wString::EqualOrdinal(L"SLF-RHN-", 8, font_name0, 8, true))
continue;
// Terminate hashing at this hypen because it separates the family name from the face name in a postscript name.
break;
}
continue;
}
ON__UINT32 cp = ON_DecodeUTF16SurrogatePair(c, *font_name, 0);
if (0 != cp)
{
// (c,*font_name) is a UTF-16 surrogate pair for unicode codepoint cp
font_name++;
}
else
{
cp = ON_UnicodeMapCodePointOrdinal(ON_StringMapOrdinalType::MinimumOrdinal, (ON__UINT32)c);
}
sha1.AccumulateUnsigned32(cp);
}
return sha1.Hash();
}
int ON_Font::CompareFontName(const ON_wString& lhs, const ON_wString& rhs)
{
return ON_Font::CompareFontNameWideChar(lhs,rhs);
}
int ON_Font::CompareFontNameWideChar(const wchar_t* lhs, const wchar_t* rhs)
{
if (lhs == rhs)
return 0;
if (nullptr == lhs)
return 1;
if (nullptr == rhs)
return -1;
if (ON_Font::FontNameHash(lhs, false) == ON_Font::FontNameHash(rhs, false))
return 0;
return ON_wString::CompareOrdinal(lhs, rhs, true);
}
int ON_Font::CompareFontNamePointer(const ON_wString* lhs, const ON_wString* rhs)
{
if (lhs == rhs)
return 0;
if (nullptr == lhs)
return 1;
if (nullptr == rhs)
return -1;
return ON_Font::CompareFontName(*lhs, *rhs);
}
int ON_Font::CompareFontNameToHyphen(const ON_wString& lhs, const ON_wString& rhs)
{
return ON_Font::CompareFontNameToHyphenWideChar(lhs, rhs);
}
int ON_Font::CompareFontNameToHyphenWideChar(const wchar_t* lhs, const wchar_t* rhs)
{
if (lhs == rhs)
return 0;
if (nullptr == lhs)
return 1;
if (nullptr == rhs)
return -1;
if (ON_Font::FontNameHash(lhs, true) == ON_Font::FontNameHash(rhs, true))
return 0;
return ON_wString::CompareOrdinal(lhs, rhs, true);
}
int ON_Font::CompareFontNameToHyphenPointer(const ON_wString* lhs, const ON_wString* rhs)
{
if (lhs == rhs)
return 0;
if (nullptr == lhs)
return 1;
if (nullptr == rhs)
return -1;
return ON_Font::CompareFontNameToHyphen(*lhs, *rhs);
}
static bool IsAtoZ(
const wchar_t* s
)
{
return (nullptr != s && ((s[0] >= 'A' && s[0] <= 'Z') || (s[0] >= 'a' && s[0] <= 'z')));
}
static const unsigned int ParseToken(
const wchar_t*& s,
size_t count,
const wchar_t*const* token,
const unsigned int* token_rc,
unsigned int token_not_found_rc
)
{
if (IsAtoZ(s))
{
for (size_t i = 0; i < count; i++)
{
const int len = (int)ON_wString::Length(token[i]);
if (ON_wString::EqualOrdinal(token[i], len, s, len, true))
{
s += len;
return token_rc[i];
}
}
}
return token_not_found_rc;
}
static bool SkipSeparator(bool bSkipSpace, const wchar_t*& s)
{
if (nullptr == s)
return false;
bool rc = false;
switch (s[0])
{
case '-':
case '_':
case ',':
case ';':
case '.':
rc = true;
break;
default:
if (bSkipSpace && ON_wString::Space == s[0])
rc = true;
break;
}
if (rc)
s++;
return rc;
}
static bool ParseStretch(
const wchar_t*& s,
ON_Font::Stretch& font_stretch
)
{
if (false == IsAtoZ(s))
return false;
const wchar_t* s1 = s;
const wchar_t* prefix_token[] = { L"SEMI", L"DEMI", L"EXTRA", L"ULTRA" };
const size_t prefix_count = sizeof(prefix_token) / sizeof(prefix_token[0]);
unsigned int prefix_rc[prefix_count] = { 1, 1, 2, 3 };
const unsigned int prefix_id = ParseToken(s1, prefix_count, prefix_token, prefix_rc, 0);
if ( 0 != prefix_id )
SkipSeparator(true,s1);
const unsigned int medium_rc = (0 != prefix_id) ? 0 : 2;
const wchar_t* name_token[] = { L"CONDENSED", L"MEDIUM", L"NORMAL", L"EXPANDED", };
const size_t name_count = sizeof(name_token) / sizeof(name_token[0]);
unsigned int name_rc[name_count] = { 1,medium_rc,medium_rc,3 };
const unsigned int name_id = ParseToken(s1, name_count, name_token, name_rc, 0);
if (0 == name_id)
return false;
bool rc = false;
switch (name_id)
{
case 1: // Condensed
switch (prefix_id)
{
case 0: // no prefix
font_stretch = ON_Font::Stretch::Condensed;
rc = true;
break;
case 1: // semi
font_stretch = ON_Font::Stretch::Semicondensed;
rc = true;
break;
case 2: // extra
font_stretch = ON_Font::Stretch::Extracondensed;
rc = true;
break;
case 3: // ultra
font_stretch = ON_Font::Stretch::Ultracondensed;
rc = true;
break;
}
break;
case 2: // Medium
switch (prefix_id)
{
case 0: // no prefix
font_stretch = ON_Font::Stretch::Medium;
rc = true;
break;
}
break;
case 3: // Expanded
switch (prefix_id)
{
case 0: // no prefix
font_stretch = ON_Font::Stretch::Expanded;
rc = true;
break;
case 1: // semi
font_stretch = ON_Font::Stretch::Semiexpanded;
rc = true;
break;
case 2: // extra
font_stretch = ON_Font::Stretch::Extraexpanded;
rc = true;
break;
case 3: // ultra
font_stretch = ON_Font::Stretch::Ultraexpanded;
rc = true;
break;
}
break;
}
if (rc)
{
s = s1;
return true;
}
return false;
}
static bool ParseWeight(
const wchar_t*& s,
ON_Font::Weight& font_weight
)
{
if (false == IsAtoZ(s))
return false;
const wchar_t* s1 = s;
const wchar_t* prefix_token[] = { L"SEMI", L"DEMI", L"EXTRA", L"ULTRA" };
const size_t prefix_count = sizeof(prefix_token) / sizeof(prefix_token[0]);
unsigned int prefix_rc[prefix_count] = { 1, 1, 2, 2 };
const unsigned int prefix_id = ParseToken(s1, prefix_count, prefix_token, prefix_rc, 0);
if ( prefix_id > 0 )
SkipSeparator(true,s1);
const unsigned int medium_rc = (0 != prefix_id) ? 0 : 3;
const unsigned int normal_rc = (0 != prefix_id) ? 0 : 4;
const wchar_t* name_token[] = { L"THIN", L"LIGHT", L"NORMAL", L"REGULAR", L"MEDIUM", L"BOLD", L"HEAVY", L"BLACK" };
const size_t name_count = sizeof(name_token) / sizeof(name_token[0]);
unsigned int name_rc[name_count] = { 1, 2, normal_rc, normal_rc, medium_rc, 5, 6, 6 };
const unsigned int name_id = ParseToken(s1, name_count, name_token, name_rc, 0);
if (0 == name_id)
return false;
bool rc = false;
switch (name_id)
{
case 1: // Thin
font_weight = ON_Font::Weight::Thin;
rc = true;
break;
case 2: // Light
switch (prefix_id)
{
case 0:
font_weight = ON_Font::Weight::Light;
rc = true;
case 1: // Semi
font_weight = ON_Font::Weight::Light;
rc = true;
case 2: // Ultra
font_weight = ON_Font::Weight::Ultralight;
rc = true;
break;
}
break;
case 3: // Normal
font_weight = ON_Font::Weight::Normal;
rc = true;
break;
case 4: // Medium
font_weight = ON_Font::Weight::Medium;
rc = true;
break;
case 5: // Bold
switch (prefix_id)
{
case 0:
font_weight = ON_Font::Weight::Bold;
rc = true;
case 1: // Semi
font_weight = ON_Font::Weight::Semibold;
rc = true;
case 2: // Ultra
font_weight = ON_Font::Weight::Ultrabold;
rc = true;
break;
}
break;
case 6: // Heavy
font_weight = ON_Font::Weight::Heavy;
rc = true;
break;
}
if (rc)
{
s = s1;
return true;
}
return false;
}
static bool ParseStyle(
const wchar_t*& s,
ON_Font::Style& font_style
)
{
if (false == IsAtoZ(s))
return false;
const wchar_t* s1 = s;
const wchar_t* name_token[] = { L"UPRIGHT", L"ROMAN", L"ITALIC", L"OBLIQUE" };
const size_t name_count = sizeof(name_token) / sizeof(name_token[0]);
unsigned int name_rc[name_count] = { 1,1,2,3 };
const unsigned int name_id = ParseToken(s1, name_count, name_token, name_rc, 0);
if (0 == name_id)
return false;
bool rc = false;
switch (name_id)
{
case 1: // Upright
font_style = ON_Font::Style::Upright;
rc = true;
break;
case 2: // Italic
font_style = ON_Font::Style::Italic;
rc = true;
break;
case 3: // Oblique
font_style = ON_Font::Style::Oblique;
rc = true;
break;
}
if (rc)
{
s = s1;
return true;
}
return false;
}
bool ON_Font::SetFromPostScriptName(
const wchar_t* postscript_font_name
)
{
if ( false == ON_FONT_MODIFICATION_PERMITTED)
return false;
// Save name in local memory before clearing this
ON_wString local_postscript_font_name(postscript_font_name);
local_postscript_font_name.TrimLeftAndRight();
postscript_font_name = static_cast<const wchar_t*>(local_postscript_font_name);
*this = ON_Font::Unset;
if (nullptr == postscript_font_name || 0 == postscript_font_name[0])
return false;
#if defined(ON_RUNTIME_WIN)
// TODO: Get Windows IDWriteFont with matching post_script_name
#elif defined (ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE)
// If the current computer has the same font, this will return the
// highest fidelity match.
// NOTE WELL: The NSFont API fails for some valid PostScript names.
// For example, the some of the 14 or so faces Helvetica Neue
// are not round tripped using the NSFont by name creation and
// are round tripped by using the CTFont API.
bool bIsSubstituteFont = false;
CTFontRef apple_font = ON_Font::AppleCTFont(postscript_font_name,0.0, bIsSubstituteFont);
if (nullptr != apple_font)
{
return SetFromAppleCTFont(apple_font,true);
}
#endif
return SetFromFontDescription(nullptr, postscript_font_name);
}
bool ON_Font::SetFromFontDescription(
const wchar_t* font_description
)
{
const wchar_t* postscript_font_name = nullptr;
return SetFromFontDescription(
font_description,
postscript_font_name
);
}
bool ON_Font::SetFromFontDescription(
const wchar_t* font_description,
const wchar_t* postscript_name
)
{
ON_wString local_font_description(font_description);
local_font_description.TrimLeftAndRight();
font_description = static_cast<const wchar_t*>(local_font_description);
ON_wString local_postscript_name(postscript_name);
local_postscript_name.TrimLeftAndRight();
postscript_name = static_cast<const wchar_t*>(local_postscript_name);
if (nullptr == font_description || font_description[0] <= ON_wString::Space)
{
font_description = local_postscript_name;
local_font_description = local_postscript_name;
if (nullptr == font_description || font_description[0] <= ON_wString::Space)
return false;
}
// As names are discovered that do not work in the code below,
// add a special case here. These are typically fonts that have
// words like Upright, Italic, Oblique, Regular, Semi, Demi, Extra, Ultra, Medium
// Black, Heavy, ... as part of the face name and those words must not
// be parsed as possible weight, style or stretch characteristics applied
// to a root face name.
const wchar_t* special_cases[] = {
// Apple's "Times New Roman" and "Times New Roman Bold Italic" is an example of why
// special cases are required. The face name is "Times New Roman" and "Roman" no
// longer indicates an upright style.
// However, Apple's "Avenir Roman" and "Avenir Oblique" fonts provide an example
// where "Roman" is style and "Avenir" is a face name. The default parsing
// handles Avenir and any other font names that are using Roman as a style.
L"Times New Roman",
// Put new special case names above this nullptr which terminates the special_cases[] list.
nullptr
};
// NOTE WELL:
// It is important that local_face_name be created from a pointer
// so that it's string buffer is not shared with other ON_wStrings.
// wchar_t values in local_face_name are modifed via const_cast<> below.
ON_wString local_face_name = static_cast<const wchar_t*>(font_description);
const wchar_t* characteristics = nullptr;
int face_name_length = local_face_name.Length();
for (int i = 0; nullptr != special_cases[i]; i++)
{
const int special_case_length = ON_wString::Length(special_cases[i]);
if (special_case_length > face_name_length)
continue;
if (special_case_length < face_name_length && local_face_name[special_case_length] > ON_wString::Space )
continue;
if (false == ON_wString::EqualOrdinal(special_cases[i], special_case_length, font_description, special_case_length, true))
continue;
characteristics = static_cast<const wchar_t*>(local_face_name) + special_case_length;
break;
}
if (nullptr == characteristics)
characteristics = static_cast<const wchar_t*>(local_face_name) + 1;
const wchar_t x = (wchar_t)1;
ON_Font::Weight font_weight = ON_Font::Default.m_font_weight;
for (wchar_t* s0 = const_cast<wchar_t*>(characteristics); 0 != *s0; s0++)
{
const wchar_t* s1 = s0;
if (ParseWeight(s1, font_weight))
{
while ( s0 < s1 )
*s0++ = x; // NOTE - modifies local_face_name content
break;
}
}
ON_Font::Style font_style = ON_Font::Default.m_font_style;
for (wchar_t* s0 = const_cast<wchar_t*>(characteristics); 0 != *s0; s0++)
{
const wchar_t* s1 = s0;
if (ParseStyle(s1, font_style))
{
while ( s0 < s1 )
*s0++ = x; // NOTE - modifies local_face_name content
break;
}
}
ON_Font::Stretch font_stretch = ON_Font::Default.m_font_stretch;
for (wchar_t* s0 = const_cast<wchar_t*>(characteristics); 0 != *s0; s0++)
{
const wchar_t* s1 = s0;
if (ParseStretch(s1, font_stretch))
{
while ( s0 < s1 )
*s0++ = x; // NOTE - modifies local_face_name content
break;
}
}
face_name_length = 0;
for (const wchar_t* s = static_cast<const wchar_t*>(local_face_name); 0 != *s; s++)
{
if (*s >= ON_wString::Space)
face_name_length++;
else
{
while (*s > 0 && *s <= ON_wString::Space)
s++;
break;
}
}
local_face_name.SetLength(face_name_length);
local_face_name.TrimLeftAndRight();
const wchar_t* face_name = static_cast<const wchar_t*>(local_face_name);
bool rc = SetFontCharacteristics(
face_name,
font_weight,
font_style,
font_stretch,
false,
false,
ON_FontMetrics::DefaultLineFeedRatio,
ON_Font::WindowsConstants::logfont_default_charset
);
if (rc)
{
m_loc_postscript_name = local_postscript_name;
m_en_postscript_name = m_loc_postscript_name;
}
return rc;
}
//////////////////////////////////////////////////////////////////////
//
// ON_Object overrides
bool ON_Font::IsValid(ON_TextLog* text_log) const
{
return (
(FamilyName().IsNotEmpty() || WindowsLogfontName().IsNotEmpty() || PostScriptName().IsNotEmpty())
&& ON_Font::Weight::Unset != m_font_weight
&& ON_Font::Style::Unset != m_font_style
&& ON_Font::Stretch::Unset != m_font_stretch
);
}
void ON_Font::Dump(ON_TextLog& dump) const
{
const bool bTextHash = dump.IsTextHash();
ON_wString s;
if (bTextHash)
{
dump.Print(L"Font family name = ...\n");
dump.PushIndent();
dump.Print(
L"The font face name and other properties depend on the platform \n"
"or the fonts installed on a particular computer. Information like \n"
"this is omitted from dumps used for SHA-1 hash caluculations so \n"
"hash values from different platforms and computers can be compared. \n"
);
}
else
{
const ON_wString description = Description();
dump.Print(L"Font description = \"%ls\"\n", static_cast<const wchar_t*>( description ));
dump.PushIndent();
const ON_wString family_name = FamilyName();
dump.Print(L"Family name = \"%ls\"\n", static_cast<const wchar_t*>(family_name ));
const ON_wString en_family_name = FamilyName(ON_Font::NameLocale::English);
if ( en_family_name.IsNotEmpty() && en_family_name != family_name)
dump.Print(L"Family name (English)= \"%ls\"\n", static_cast<const wchar_t*>(en_family_name ));
const ON_wString face_name = FaceName();
dump.Print(L"Face name = \"%ls\"\n", static_cast<const wchar_t*>( face_name ));
const ON_wString en_face_name = FaceName(ON_Font::NameLocale::English);
if ( en_face_name.IsNotEmpty() && en_face_name != face_name)
dump.Print(L"Face name (English)= \"%ls\"\n", static_cast<const wchar_t*>(en_face_name ));
const ON_wString postscript_name = PostScriptName();
dump.Print(L"PostScript name = \"%ls\"\n", static_cast<const wchar_t*>(postscript_name));
const ON_wString en_postscript_name = PostScriptName(ON_Font::NameLocale::English);
if ( en_postscript_name.IsNotEmpty() && en_postscript_name != postscript_name)
dump.Print(L"PostScript name (English)= \"%ls\"\n", static_cast<const wchar_t*>(en_postscript_name ));
const ON_wString quartet_description = this->QuartetDescription();
if (quartet_description.IsNotEmpty())
dump.Print(L"Quartet = %ls\n", static_cast<const wchar_t*>(quartet_description));
const ON_wString windows_logfont_name = WindowsLogfontName();
dump.Print(L"Windows LOGFONT name = \"%ls\"\n", static_cast<const wchar_t*>(windows_logfont_name));
const ON_wString en_windows_logfont_name = WindowsLogfontName(ON_Font::NameLocale::English);
if ( en_windows_logfont_name.IsNotEmpty() && en_windows_logfont_name != windows_logfont_name)
dump.Print(L"Windows LOGFONT name (English)= \"%ls\"\n", static_cast<const wchar_t*>(en_windows_logfont_name ));
const ON_wString rich_text_name = ON_Font::RichTextFontName(this, false);
dump.Print(L"Rich text name = \"%ls\"\n", static_cast<const wchar_t*>(rich_text_name));
if (this->IsManagedSubstitutedFont())
{
const ON_Font* substitute = this->SubstituteFont();
if (nullptr != substitute)
dump.Print(L"Installed substitute = %ls\n", static_cast<const wchar_t*>(substitute->Description()));
}
s = ON_wString::EmptyString;
switch (this->FontOrigin())
{
case ON_Font::Origin::Unset: break;
case ON_Font::Origin::Unknown: s = L"Unknown"; break;
case ON_Font::Origin::WindowsFont: s = L"Windows Font"; break;
case ON_Font::Origin::AppleFont: s = L"Apple Font"; break;
default: s = ON_wString::FormatToString(L"%u", static_cast<unsigned int>(this->FontOrigin())); break;
};
if (s.IsNotEmpty())
{
dump.Print(L"Origin = %ls\n", static_cast<const wchar_t*>(s));
}
s = ON_wString::EmptyString;
const ON_OutlineFigure::Type outline_figure_type = this->OutlineFigureType();
switch (outline_figure_type)
{
case ON_OutlineFigure::Type::Unset: break;
case ON_OutlineFigure::Type::Unknown: break;
case ON_OutlineFigure::Type::SingleStroke: s = L"Engraving - single stroke"; break;
case ON_OutlineFigure::Type::DoubleStroke: s = L"Engraving - double stroke"; break;
case ON_OutlineFigure::Type::Perimeter: s = L"Perimeter"; break;
case ON_OutlineFigure::Type::NotPerimeter: s = L"Not a perimeter"; break;
case ON_OutlineFigure::Type::Mixed: s = L"Mixed"; break;
default: s = ON_wString::FormatToString(L"%u", static_cast<unsigned int>(outline_figure_type)); break;
};
if (s.IsNotEmpty())
{
dump.Print(L"Outline type = %ls\n", static_cast<const wchar_t*>(s));
}
}
if (ON_Font::IsValidPointSize(m_point_size))
{
dump.Print(L"PointSize = %g\n", m_point_size);
}
else
{
dump.Print(L"PointSize = annotation default\n");
}
const ON_FontFaceQuartet q = this->FontQuartet();
const ON_FontFaceQuartet::Member m = q.QuartetMember(this);
switch (m)
{
case ON_FontFaceQuartet::Member::Regular:
dump.Print("Quartet: %ls (Regular member)\n", static_cast<const wchar_t*>(q.QuartetName()));
break;
case ON_FontFaceQuartet::Member::Bold:
dump.Print("Quartet: %ls (Bold member)\n", static_cast<const wchar_t*>(q.QuartetName()));
break;
case ON_FontFaceQuartet::Member::Italic:
dump.Print("Quartet: %ls (Italic member)\n", static_cast<const wchar_t*>(q.QuartetName()));
break;
case ON_FontFaceQuartet::Member::BoldItalic:
dump.Print("Quartet: %ls (Bold-Italic member)\n", static_cast<const wchar_t*>(q.QuartetName()));
break;
case ON_FontFaceQuartet::Member::Unset:
default:
dump.Print("Quartet: None\n");
break;
}
s = ON_Font::WeightToWideString(FontWeight());
if( s.IsEmpty())
s = ON_wString::FormatToString(L"%u", static_cast<unsigned int>(this->FontWeight()));
if (SimulatedWeight())
s += L" (simulated)";
dump.Print(L"Weight = %ls\n", static_cast<const wchar_t*>(s));
s = ON_Font::StretchToWideString(FontStretch());
if( s.IsEmpty())
s = ON_wString::FormatToString(L"%u", static_cast<unsigned int>(this->FontStretch()));
if (SimulatedStretch())
s += L" (simulated)";
dump.Print(L"Stretch = %ls\n", static_cast<const wchar_t*>(s));
s = ON_Font::StyleToWideString(FontStyle());
if( s.IsEmpty())
s = ON_wString::FormatToString(L"%u", static_cast<unsigned int>(this->FontStyle()));
if (SimulatedStyle())
s += L" (simulated)";
dump.Print(L"Style = %ls\n", static_cast<const wchar_t*>(s));
dump.Print(L"Underlined = %ls\n", this->IsUnderlined() ? L"true" : L"false");
dump.Print(L"Strikethrough = %ls\n", this->IsStrikethrough() ? L"true" : L"false");
dump.Print(L"Symbol font = %ls\n", this->IsSymbolFont() ? L"true" : L"false");
const ON_OutlineFigure::Type font_figure_type = this->OutlineFigureType();
if (ON_OutlineFigure::Type::SingleStroke == font_figure_type)
dump.Print(L"Engraving font = Single-stroke\n");
else if (ON_OutlineFigure::Type::DoubleStroke == font_figure_type)
dump.Print(L"Engraving font = Double-stroke\n");
if (
ON_PANOSE1::FamilyKind::Any != m_panose1.PANOSE1FamilyKind()
&& ON_PANOSE1::FamilyKind::NoFit != m_panose1.PANOSE1FamilyKind()
)
{
// PANOSE1 may contains some useful information
m_panose1.Dump(dump);
}
if (false == bTextHash)
{
const ON_wString characteristics_hash = FontCharacteristicsHash().ToString(true);
dump.Print(L"Font characteristics SHA-1 hash = %ls\n", static_cast<const wchar_t*>(characteristics_hash));
unsigned int runtime_sn = RuntimeSerialNumber();
if (runtime_sn >= 1)
{
if (this == &ON_Font::Default)
dump.Print(L"Managed font <%u> (ON_Font::Default)\n", runtime_sn);
else if (IsInstalledFont())
{
dump.Print(L"Managed font <%u> (face installed on device)\n", runtime_sn);
}
else
{
dump.Print(L"Managed font <%u> (face not installed on device)\n", runtime_sn);
}
for (int fm_pass = 0; fm_pass < 1; fm_pass++)
{
const bool bNormalized = (1 == fm_pass);
const ON_FontMetrics fm
= bNormalized
? FontMetrics()
: FontUnitFontMetrics();
dump.Print(bNormalized ? L"Normalized font metrics:" : L"Font metrics:");
if (fm.IsSet())
{
dump.PrintNewLine();
dump.PushIndent();
fm.Dump(dump);
dump.PopIndent();
}
else
{
dump.Print(L" Unset\n");
}
}
}
#if defined(ON_OS_WINDOWS_GDI)
{
const LOGFONT logfont = this->WindowsLogFont(0, nullptr);
ON_Font::DumpLogfont(&logfont, dump);
}
#elif defined (ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE)
{
bool bIsSubstituteFont = false;
CTFontRef apple_font = AppleCTFont(bIsSubstituteFont);
if (bIsSubstituteFont)
dump.Print(L"Apple Core Text font (substitute):\n");
else
dump.Print(L"Apple Core Text font:\n");
dump.PushIndent();
ON_Font::DumpCTFont(apple_font, dump);
dump.PopIndent();
if (nullptr != apple_font && ON_Font::Origin::AppleFont == m_font_origin)
{
if (m_apple_font_weight_trait >= -1.0 && m_apple_font_weight_trait <= 1.0)
dump.Print(L"Apple font weight trait = %g\n", m_apple_font_weight_trait);
if (m_apple_font_width_trait >= -1.0 && m_apple_font_width_trait <= 1.0)
dump.Print(L"Apple font width trait = %g\n", m_apple_font_width_trait);
}
}
#elif defined(OPENNURBS_FREETYPE_SUPPORT)
// Look in opennurbs_system_rumtime.h for the correct place to define OPENNURBS_FREETYPE_SUPPORT.
// Do NOT define OPENNURBS_FREETYPE_SUPPORT here or in your project setting ("makefile").
if (runtime_sn >= 1)
{
// Free Type font details
DumpFreeType(dump);
}
#endif
}
dump.PopIndent();
}
bool ON_FontMetrics::IsUnset() const
{
return (
0 == m_UPM
&& 0 == m_ascent
&& 0 == m_descent
&& 0 == m_line_space
&& 0 == m_ascent_of_capital
&& 0 == m_ascent_of_x
&& 0 == m_strikeout_thickness
&& 0 == m_strikeout_position
&& 0 == m_underscore_thickness
&& 0 == m_underscore_position
);
}
bool ON_FontMetrics::IsSet() const
{
return IsUnset() ? false : true;
}
void ON_FontMetrics::Dump(class ON_TextLog& text_log) const
{
text_log.Print("Units per EM = %d\n", m_UPM);
text_log.Print("Ascent = %d\n", m_ascent);
text_log.Print("Descent = %d\n", m_descent);
text_log.Print("Distance between baselines = %d\n", m_line_space);
text_log.Print("Ascent of capital = %d\n", AscentOfCapital());
text_log.Print("Ascent of x = %d\n", AscentOfx());
text_log.Print("Strikeout thickness = %d\n", m_strikeout_thickness);
text_log.Print("Strikeout position = %d\n", m_strikeout_position);
text_log.Print("Underscore thickness = %d\n", m_underscore_thickness);
text_log.Print("Underscore position = %d\n", m_underscore_position);
}
#if defined(ON_OS_WINDOWS_GDI)
#define INTERNAL_CASE_STRING(c) case c: s = #c; break
static const ON_String Internal_WindowsLogFontCharSet(
BYTE charSet
)
{
ON_String s;
switch (charSet)
{
INTERNAL_CASE_STRING(ANSI_CHARSET);
INTERNAL_CASE_STRING(DEFAULT_CHARSET);
INTERNAL_CASE_STRING(SYMBOL_CHARSET);
INTERNAL_CASE_STRING(SHIFTJIS_CHARSET);
INTERNAL_CASE_STRING(HANGEUL_CHARSET);
INTERNAL_CASE_STRING(GB2312_CHARSET);
INTERNAL_CASE_STRING(CHINESEBIG5_CHARSET);
INTERNAL_CASE_STRING(OEM_CHARSET);
#if(WINVER >= 0x0400)
INTERNAL_CASE_STRING(JOHAB_CHARSET);
INTERNAL_CASE_STRING(HEBREW_CHARSET);
INTERNAL_CASE_STRING(ARABIC_CHARSET);
INTERNAL_CASE_STRING(GREEK_CHARSET);
INTERNAL_CASE_STRING(TURKISH_CHARSET);
INTERNAL_CASE_STRING(VIETNAMESE_CHARSET);
INTERNAL_CASE_STRING(THAI_CHARSET);
INTERNAL_CASE_STRING(EASTEUROPE_CHARSET);
INTERNAL_CASE_STRING(RUSSIAN_CHARSET);
INTERNAL_CASE_STRING(MAC_CHARSET);
INTERNAL_CASE_STRING(BALTIC_CHARSET);
#endif
default:
s = ON_String::FormatToString("%u", (unsigned char)charSet);
break;
}
return s;
}
static const ON_String Internal_WindowsLogFontFamily(
BYTE pitchAndFamily
)
{
ON_String s;
const BYTE family = (0xF0 & pitchAndFamily);
switch (family)
{
INTERNAL_CASE_STRING(FF_DONTCARE);
INTERNAL_CASE_STRING(FF_ROMAN);
INTERNAL_CASE_STRING(FF_SWISS);
INTERNAL_CASE_STRING(FF_MODERN);
INTERNAL_CASE_STRING(FF_SCRIPT);
INTERNAL_CASE_STRING(FF_DECORATIVE);
default:
s = ON_String::FormatToString("%02X", family);
break;
};
return s;
}
static const ON_String Internal_WindowsLogFontQuality(
const BYTE lfQuality
)
{
ON_String s;
switch (lfQuality)
{
INTERNAL_CASE_STRING(DEFAULT_QUALITY);
INTERNAL_CASE_STRING(DRAFT_QUALITY);
INTERNAL_CASE_STRING(PROOF_QUALITY);
#if(WINVER >= 0x0400)
INTERNAL_CASE_STRING(NONANTIALIASED_QUALITY);
INTERNAL_CASE_STRING(ANTIALIASED_QUALITY);
#endif /* WINVER >= 0x0400 */
#if (_WIN32_WINNT >= _WIN32_WINNT_WINXP)
INTERNAL_CASE_STRING(CLEARTYPE_QUALITY);
INTERNAL_CASE_STRING(CLEARTYPE_NATURAL_QUALITY);
#endif
default:
s = ON_String::FormatToString("%u", (unsigned int)lfQuality);
break;
}
return s;
}
static const ON_String Internal_WindowsLogFontOutPrecision(
const BYTE lfOutPrecision
)
{
ON_String s;
switch (lfOutPrecision)
{
INTERNAL_CASE_STRING(OUT_DEFAULT_PRECIS);
INTERNAL_CASE_STRING(OUT_STRING_PRECIS);
INTERNAL_CASE_STRING(OUT_CHARACTER_PRECIS);
INTERNAL_CASE_STRING(OUT_STROKE_PRECIS);
INTERNAL_CASE_STRING(OUT_TT_PRECIS);
INTERNAL_CASE_STRING(OUT_DEVICE_PRECIS);
INTERNAL_CASE_STRING(OUT_RASTER_PRECIS);
INTERNAL_CASE_STRING(OUT_TT_ONLY_PRECIS);
INTERNAL_CASE_STRING(OUT_OUTLINE_PRECIS);
INTERNAL_CASE_STRING(OUT_SCREEN_OUTLINE_PRECIS);
INTERNAL_CASE_STRING(OUT_PS_ONLY_PRECIS);
default:
s = ON_String::FormatToString("%u", (unsigned int)lfOutPrecision);
break;
}
return s;
}
static const ON_String Internal_WindowsLogFontClipPrecision(
const BYTE lfClipPrecision
)
{
ON_String s;
switch (lfClipPrecision)
{
INTERNAL_CASE_STRING(CLIP_DEFAULT_PRECIS);
INTERNAL_CASE_STRING(CLIP_CHARACTER_PRECIS);
INTERNAL_CASE_STRING(CLIP_STROKE_PRECIS);
INTERNAL_CASE_STRING(CLIP_MASK);
INTERNAL_CASE_STRING(CLIP_LH_ANGLES);
INTERNAL_CASE_STRING(CLIP_TT_ALWAYS);
#if (_WIN32_WINNT >= _WIN32_WINNT_LONGHORN)
INTERNAL_CASE_STRING(CLIP_DFA_DISABLE);
#endif
INTERNAL_CASE_STRING(CLIP_EMBEDDED);
default:
s = ON_String::FormatToString("%u", (unsigned int)lfClipPrecision);
break;
}
return s;
}
void ON_Font::DumpLogfont(
const LOGFONT* logfont,
ON_TextLog& text_log
)
{
if (nullptr == logfont)
{
text_log.Print(L"LOGFONT = nullptr\n");
return;
}
text_log.Print(L"LOGFONT\n");
text_log.PushIndent();
ON_wString sFaceName = logfont->lfFaceName;
text_log.Print(L"lfFaceName = %ls\n",static_cast<const wchar_t*>(sFaceName));
text_log.Print("lfHeight = %d\n", logfont->lfHeight);
text_log.Print("lfWidth = %d\n", logfont->lfWidth);
text_log.Print("lfEscapement = %d.%d degrees\n", (logfont->lfEscapement)/10, (logfont->lfEscapement)%10);
text_log.Print("lfOrientation = %d.%d degrees\n", (logfont->lfOrientation)/10, (logfont->lfOrientation)%10);
ON_String s;
switch (logfont->lfWeight)
{
case FW_DONTCARE: s = L"FW_DONTCARE"; break;
case FW_THIN: s = "FW_THIN"; break;
case FW_EXTRALIGHT: s = "FW_EXTRALIGHT"; break; // = FW_ULTRALIGHT
case FW_LIGHT: s = "FW_LIGHT"; break;
case FW_NORMAL: s = "FW_NORMAL"; break; // = FW_REGULAR
case FW_MEDIUM: s = "FW_MEDIUM"; break;
case FW_SEMIBOLD: s = "FW_SEMIBOLD"; break; // = FW_DEMIBOLD
case FW_BOLD: s = "FW_BOLD"; break;
case FW_EXTRABOLD: s = "FW_EXTRABOLD"; break; // = FW_ULTRABOLD
case FW_HEAVY: s = "FW_HEAVY"; break; // = FW_BLACK
default: s = ON_String::EmptyString; break;
}
if (s.IsNotEmpty())
s += " = ";
s += ON_String::FormatToString("%d", logfont->lfWeight);
text_log.Print("lfWeight = %s\n", static_cast<const char*>(s));
text_log.Print("lfItalic = %s\n", logfont->lfItalic ? "true" : "false");
text_log.Print("lfUnderline = %s\n", logfont->lfUnderline ? "true" : "false");
text_log.Print("lfStrikeOut = %s\n", logfont->lfStrikeOut ? "true" : "false");
s = Internal_WindowsLogFontCharSet(logfont->lfCharSet);
text_log.Print("lfCharSet = %s\n", static_cast<const char*>(s));
s = Internal_WindowsLogFontOutPrecision(logfont->lfOutPrecision);
text_log.Print("lfOutPrecision = %s\n", static_cast<const char*>(s));
s = Internal_WindowsLogFontClipPrecision(logfont->lfClipPrecision);
text_log.Print("lfClipPrecision = %s\n", static_cast<const char*>(s));
s = Internal_WindowsLogFontQuality(logfont->lfQuality);
text_log.Print("lfQuality = %s\n", static_cast<const char*>(s));
const unsigned int pitch = (logfont->lfPitchAndFamily & 0x03);
const unsigned int family = (logfont->lfPitchAndFamily & 0xF);
switch (pitch)
{
case DEFAULT_PITCH: s = "DEFAULT_PITCH"; break;
case FIXED_PITCH: s = "FIXED_PITCH"; break;
case VARIABLE_PITCH: s = "VARIABLE_PITCH"; break;
default: s = ON_String::FormatToString("0x%02x", pitch);
};
s += " | ";
s += Internal_WindowsLogFontFamily(logfont->lfPitchAndFamily);
if (s.IsNotEmpty())
s += L" = ";
s += ON_wString::FormatToString(L"0x%02x", (unsigned int)logfont->lfPitchAndFamily);
text_log.Print("lfPitchAndFamily = %s\n", static_cast<const char*>(s));
text_log.PopIndent();
}
void ON_Font::DumpTextMetric(
const TEXTMETRIC* tm,
ON_TextLog& text_log
)
{
if (nullptr == tm)
{
text_log.Print("TEXTMETRIC = nullptr\n");
return;
}
text_log.Print("TEXTMETRIC\n");
text_log.PushIndent();
text_log.Print("tmHeight = %d\n",tm->tmHeight);
text_log.Print("tmAscent = %d\n",tm->tmAscent);
text_log.Print("tmDescent = %d\n",tm->tmDescent);
text_log.Print("tmInternalLeading = %d\n",tm->tmInternalLeading);
text_log.Print("tmExternalLeading = %d\n",tm->tmExternalLeading);
text_log.Print("tmAveCharWidth = %d\n",tm->tmAveCharWidth);
text_log.Print("tmMaxCharWidth = %d\n",tm->tmMaxCharWidth);
text_log.Print("tmWeight = %d\n",tm->tmWeight);
text_log.Print("tmOverhang = %d\n",tm->tmOverhang);
text_log.Print("tmDigitizedAspectX = %d\n",tm->tmDigitizedAspectX);
text_log.Print("tmDigitizedAspectY = %d\n",tm->tmDigitizedAspectY);
text_log.Print(L"tmFirstChar = U+%04X\n",(ON__UINT32)tm->tmFirstChar);
text_log.Print(L"tmLastChar = U+%04X\n",(ON__UINT32)tm->tmLastChar);
text_log.Print(L"tmDefaultChar = U+%04X\n",(ON__UINT32)tm->tmDefaultChar);
text_log.Print(L"tmBreakChar = U+%04X\n",(ON__UINT32)tm->tmBreakChar);
text_log.Print("tmItalic = %s\n",tm->tmItalic?"true":"false");
text_log.Print("tmUnderlined = %s\n",tm->tmUnderlined?"true":"false");
text_log.Print("tmStruckOut = %s\n",tm->tmStruckOut?"true":"false");
ON_String s;
const BYTE pitch = (0x0F & tm->tmPitchAndFamily);
if (0 != (TMPF_FIXED_PITCH & pitch))
{
if (s.IsNotEmpty())
s += " | ";
s += "TMPF_FIXED_PITCH";
}
if (0 != (TMPF_VECTOR & pitch))
{
if (s.IsNotEmpty())
s += " | ";
s += "TMPF_VECTOR";
}
if (0 != (TMPF_TRUETYPE & pitch))
{
if (s.IsNotEmpty())
s += " | ";
s += "TMPF_TRUETYPE";
}
if (0 != (TMPF_DEVICE & pitch))
{
if (s.IsNotEmpty())
s += " | ";
s += "TMPF_DEVICE";
}
if (s.IsNotEmpty())
s += " | ";
s += Internal_WindowsLogFontFamily(tm->tmPitchAndFamily);
if (s.IsNotEmpty())
s += " = ";
s += ON_wString::FormatToString(L"0x%02X", (unsigned int)tm->tmPitchAndFamily);
text_log.Print("tmPitchAndFamily = %s\n", static_cast<const char*>(s));
s = Internal_WindowsLogFontCharSet(tm->tmCharSet);
text_log.Print("tmCharSet = %s\n", static_cast<const char*>(s));
text_log.PopIndent();
}
const ON_wString ON_WindowsDWriteFontInformation::FamilyName() const
{
return m_loc_family_name.IsNotEmpty() ? m_loc_family_name : m_en_family_name;
}
const ON_wString ON_WindowsDWriteFontInformation::FaceName() const
{
return m_loc_face_name.IsNotEmpty() ? m_loc_face_name : m_en_face_name;
}
const ON_wString ON_WindowsDWriteFontInformation::PostScriptName() const
{
return m_loc_postscript_name.IsNotEmpty() ? m_loc_postscript_name : m_en_postscript_name;
}
const ON_wString ON_WindowsDWriteFontInformation::WindowsLogfontName() const
{
return m_loc_gdi_family_name.IsNotEmpty() ? m_loc_gdi_family_name : m_en_gdi_family_name;
}
int ON_WindowsDWriteFontInformation::CompareFamilyName(
const ON_WindowsDWriteFontInformation* lhs,
const ON_WindowsDWriteFontInformation* rhs)
{
if (lhs == rhs) return 0;
if (nullptr == lhs) return 1;
if (nullptr == rhs) return -1;
return ON_wString::CompareOrdinal(lhs->m_loc_family_name, rhs->m_loc_family_name, true);
}
int ON_WindowsDWriteFontInformation::CompareFamilyNameFaceNameWeightStretchStyle(
const ON_WindowsDWriteFontInformation* lhs,
const ON_WindowsDWriteFontInformation* rhs)
{
if (lhs == rhs) return 0;
if (nullptr == lhs) return 1;
if (nullptr == rhs) return -1;
int rc = ON_wString::CompareOrdinal(lhs->FamilyName(), rhs->FamilyName(), true);
if (0 != rc)
return rc;
rc = ON_wString::CompareOrdinal(lhs->FaceName(), rhs->FaceName(), true);
if (0 != rc)
return rc;
rc = lhs->m_weight - rhs->m_weight;
if (0 != rc)
return rc;
rc = lhs->m_stretch - rhs->m_stretch;
if (0 != rc)
return rc;
rc = lhs->m_style - rhs->m_style;
if (0 != rc)
return rc;
return 0;
}
int ON_WindowsDWriteFontInformation::ComparePostScriptName(
const ON_WindowsDWriteFontInformation* lhs,
const ON_WindowsDWriteFontInformation* rhs)
{
if (lhs == rhs) return 0;
if (nullptr == lhs) return 1;
if (nullptr == rhs) return -1;
return ON_wString::CompareOrdinal(lhs->PostScriptName(), rhs->PostScriptName(), true);
}
unsigned char ON_Font::WindowsLogfontCharSetFromLogfont(
const LOGFONT* logfont,
bool bValidateSymbolFont
)
{
for (;;)
{
if (nullptr == logfont)
break;
if (0 == logfont->lfFaceName[0])
break;
const int logfont_capacity = (int)(sizeof(logfont->lfFaceName) / sizeof(logfont->lfFaceName[0]));
if ( ON_wString::EqualOrdinal(L"CityBlueprint", -1, logfont->lfFaceName, logfont_capacity, true))
return ON_Font::WindowsConstants::logfont_symbol_charset;
if ( ON_wString::EqualOrdinal(L"CountryBlueprint", -1, logfont->lfFaceName, logfont_capacity, true))
return ON_Font::WindowsConstants::logfont_symbol_charset;
if (ON_Font::WindowsConstants::logfont_symbol_charset != logfont->lfCharSet)
break;
if (bValidateSymbolFont
&& ON_Font::WindowsConstants::logfont_symbol_charset != ON_Font::WindowsLogfontCharSetFromFaceName(logfont->lfFaceName)
)
break;
return ON_Font::WindowsConstants::logfont_symbol_charset;
}
return ON_Font::WindowsConstants::logfont_default_charset;
}
ON_Font::ON_Font(
ON_Font::FontType font_type,
const class ON_WindowsDWriteFontInformation& dwrite_font_information
)
: m_runtime_serial_number((ON_Font::FontType::ManagedFont == font_type)?++ON_Font::__runtime_serial_number_generator:0)
, m_font_type(font_type)
{
const LOGFONT logfont = dwrite_font_information.m_gdi_interop_logfont;
m_windows_logfont_weight = logfont.lfWeight;
m_font_weight = ON_Font::WeightFromWindowsLogfontWeight(m_windows_logfont_weight);
m_apple_font_weight_trait = ON_Font::AppleFontWeightTraitFromWeight(m_font_weight);
m_point_size = 0.0;
m_font_style = (0 != logfont.lfItalic) ? ON_Font::Style::Italic : ON_Font::Style::Upright;
m_font_stretch = ON_Font::FontStretchFromDWriteStretch(dwrite_font_information.m_stretch,ON_Font::Stretch::Medium);
m_font_bUnderlined = false;
m_font_bStrikethrough = false;
m_logfont_charset = ON_Font::WindowsLogfontCharSetFromLogfont(&logfont,false);
m_font_origin = ON_Font::Origin::WindowsFont;
m_locale_name = dwrite_font_information.m_prefered_locale;
m_loc_postscript_name = dwrite_font_information.m_loc_postscript_name;
m_en_postscript_name = dwrite_font_information.m_en_postscript_name;
m_loc_family_name = dwrite_font_information.m_loc_family_name;
m_en_family_name = dwrite_font_information.m_en_family_name;
m_loc_face_name = dwrite_font_information.m_loc_face_name;
m_en_face_name = dwrite_font_information.m_en_face_name;
m_loc_windows_logfont_name = dwrite_font_information.m_loc_gdi_family_name;
m_en_windows_logfont_name = dwrite_font_information.m_en_gdi_family_name;
for(;;)
{
// For simulated fonts, the DWrite GDI family name is sometimes too long for a valid logfont name.
if (
m_loc_windows_logfont_name.IsNotEmpty() && m_loc_windows_logfont_name.Length() <= 32
&& m_en_windows_logfont_name.IsNotEmpty() && m_en_windows_logfont_name.Length() <= 32
)
{
break;
}
if (0 == dwrite_font_information.m_gdi_interop_logfont.lfFaceName[0])
break;
// logfont_name[] = logfont lfFaceName we are cetain is null terminated.
wchar_t logfont_name[1 + (sizeof(dwrite_font_information.m_gdi_interop_logfont.lfFaceName) / sizeof(dwrite_font_information.m_gdi_interop_logfont.lfFaceName[0]))];
const size_t logfont_name_capacity = (sizeof(logfont_name)/sizeof(logfont_name[0])) - 1;
memcpy(logfont_name, dwrite_font_information.m_gdi_interop_logfont.lfFaceName, logfont_name_capacity*sizeof(logfont_name[0]));
logfont_name[logfont_name_capacity] = 0;
if (m_en_windows_logfont_name.IsEmpty() || m_en_windows_logfont_name.Length() > 32)
m_en_windows_logfont_name = logfont_name;
if (m_loc_windows_logfont_name.IsEmpty() || m_loc_windows_logfont_name.Length() > 32)
m_loc_windows_logfont_name = logfont_name;
break;
}
m_panose1 = dwrite_font_information.m_panose1;
m_outline_figure_type = dwrite_font_information.m_outline_figure_type;
this->SetSimulated(
dwrite_font_information.m_bSimulatedBold,
false,
dwrite_font_information.m_bSimulatedOblique,
dwrite_font_information.m_bSimulatedOther
);
if ( ON_Font::FontType::ManagedFont == font_type || ON_Font::FontType::InstalledFont == font_type )
{
ON_FontGlyphCache* font_cache = ON_FontGlyphCache::New();
#if defined(ON_OS_WINDOWS_GDI)
font_cache->m_dwrite_font = dwrite_font_information.m_dwrite_font;
dwrite_font_information.m_dwrite_font->AddRef();
#endif
if (dwrite_font_information.m_font_metrics.HeightsAreValid())
{
font_cache->SetFontMetrics(dwrite_font_information.m_font_metrics);
}
m_font_glyph_cache = std::shared_ptr<ON_FontGlyphCache>(font_cache);
}
}
#endif
bool ON_Font::Write(
ON_BinaryArchive& file // serialize definition to binary archive
) const
{
if (file.Archive3dmVersion() < 60
|| file.ArchiveOpenNURBSVersion() < ON_TextStyle::binary_archive_opennurbs_version
)
{
ON_WARNING("This font should probably be an ON_TextStyle.");
return WriteV5(
RuntimeSerialNumber(),
ON_nil_uuid,
file
);
}
if (!file.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,6))
return false;
bool rc = false;
for (;;)
{
// version 1.0
unsigned int fc = FontCharacteristicsAsUnsigned();
if (!file.WriteInt(fc))
break;
// Windows LOGFONT.lfFaceName
if ( !file.WriteWideString(WindowsLogfontName()) )
break;
// PostScript name
if (!file.WriteString(PostScriptName()))
break;
// version 1.1 added font_description August 2016
ON_wString obsolete_font_description = PostScriptName();
if (obsolete_font_description.IsEmpty())
obsolete_font_description = FamilyName();
if (obsolete_font_description.IsEmpty())
obsolete_font_description = WindowsLogfontName();
if (!file.WriteString(obsolete_font_description))
break;
// version 1.2 added m_windows_logfont_weight and m_apple_font_weight_trait
if (!file.WriteInt(m_windows_logfont_weight))
break;
if (!file.WriteDouble(m_apple_font_weight_trait))
break;
// version 1.3 added additional m_point_size and m_LOGFONT_* values.
if (!file.WriteDouble(m_point_size))
break;
const bool bOBSOLETEBool = false;
if (!file.WriteBool(bOBSOLETEBool))
break;
// version 1.4 explicit Family name
if (!file.WriteString(FamilyName()))
break;
// version 1.5 explicit names in loc and en and PANOSE1 settings
if (!file.WriteString(m_locale_name))
break;
bool bFontNamesSaved = false;
for(;;)
{
// NEVER commit code with this define set.
//
// This code is used by developers to create files used to test
// code and UI for handing situations when a font is not installed
// on a device.
//
// To create a missing font test .3dm file, you must modify code
// int opennurbs_font.cpp and in opennurbs_text.cpp.
// Search for CREATE_MISSING_FONT_TEST_3DM_FILE to find the locations.
//#define CREATE_MISSING_FONT_TEST_3DM_FILE
#if defined(CREATE_MISSING_FONT_TEST_3DM_FILE)
// All fonts in the Family with MISSING_FONT_TEST_valid_family_name
// will be saved as a family named missing_family_name.
const ON_wString MISSING_FONT_TEST_valid_font_family_name(L"Courier New");
if (MISSING_FONT_TEST_valid_font_family_name == FamilyName() && FaceName().IsNotEmpty())
{
// Write Missing Font names
const ON_wString missing_font_family_name(L"Missing Font Test");
const ON_wString missing_font_face_name(FaceName());
const ON_wString missing_font_logfont_name(missing_font_family_name);
ON_wString missing_font_postscript_name = missing_font_family_name + L"-" + missing_font_face_name;
missing_font_postscript_name.Remove(ON_wString::Space);
// local and english postscript name
if (!file.WriteString(missing_font_postscript_name))
break;
if (!file.WriteString(missing_font_postscript_name))
break;
// local and english logfont name
if (!file.WriteString(missing_font_logfont_name))
break;
if (!file.WriteString(missing_font_logfont_name))
break;
// local and english family name
if (!file.WriteString(missing_font_family_name))
break;
if (!file.WriteString(missing_font_family_name))
break;
// local and english face name
if (!file.WriteString(missing_font_face_name))
break;
if (!file.WriteString(missing_font_face_name))
break;
bFontNamesSaved = true;
break;
}
#endif
// write correct names
if (!file.WriteString(m_loc_postscript_name))
break;
if (!file.WriteString(m_en_postscript_name))
break;
if (!file.WriteString(m_loc_windows_logfont_name))
break;
if (!file.WriteString(m_en_windows_logfont_name))
break;
if (!file.WriteString(m_loc_family_name))
break;
if (!file.WriteString(m_en_family_name))
break;
if (!file.WriteString(m_loc_face_name))
break;
if (!file.WriteString(m_en_face_name))
break;
bFontNamesSaved = true;
break;
}
if (false == bFontNamesSaved)
break; // errors writing font names
if (!m_panose1.Write(file))
break;
// version 1.6 rich text quartet member (regular/bold/italic/bold-italic)
const unsigned char quartet_member_as_unsigned = static_cast<unsigned char>(this->m_quartet_member);
if (!file.WriteByte(1,&quartet_member_as_unsigned))
break;
rc = true;
break;
}
if (!file.EndWrite3dmChunk())
rc = false;
return rc;
}
bool ON_Font::WriteV5(
int V5_font_index,
ON_UUID V5_font_id,
ON_BinaryArchive& file // serialize definition to binary archive
) const
{
bool rc = file.Write3dmChunkVersion(1, 2);
while(rc)
{
rc = file.Write3dmReferencedComponentIndex(ON_ModelComponent::Type::TextStyle,V5_font_index);
if(!rc)
break;
// Mac Rhino 5 uses the V5 "m_font_description" field to store Apple font names because
// there was no other appropriate place to save the information in a V5 file format.
ON_wString description = PostScriptName();
if (description.IsEmpty())
description = FamilyName();
if (description.IsEmpty())
description = WindowsLogfontName();
rc = file.WriteString(description);
if(!rc)
break;
{
// 18 October 2002 Dale Lear:
// This is a result of a bug in old code.
// The Windows LOGFONT.lfFaceName was written as an array of 64 unsigned shorts.
unsigned short sh[64];
memset(sh, 0, sizeof(sh));
const ON_wString logfont_name = WindowsLogfontName();
const int logfont_name_len = logfont_name.Length();
const wchar_t* logfont_name_str = static_cast<const wchar_t*>(logfont_name);
int i;
for(i = 0; i < 64 && i < logfont_name_len; i++)
sh[i] = (unsigned short)(logfont_name_str[i]);
rc = file.WriteShort(64, sh);
if(!rc) break;
}
// 1.1 additions
int windows_logfont_weight = ON_Font::WindowsLogfontWeightFromWeight(m_font_weight);
if (file.Archive3dmVersion() < 60)
{
// V5 and earlier files had 4 permitted weights
// Light = 300
// Normal = 400
// Medium = 500
// Bold = 700
if ( windows_logfont_weight <= 0 )
windows_logfont_weight = 400;
if ( windows_logfont_weight < 350 )
windows_logfont_weight = 300;
else if ( windows_logfont_weight < 450 )
windows_logfont_weight = 400;
else if ( windows_logfont_weight < 600 )
windows_logfont_weight = 500;
else if ( windows_logfont_weight <= 1000 )
windows_logfont_weight = 700;
else
windows_logfont_weight = 400;
}
rc = file.WriteInt(static_cast<int>(windows_logfont_weight));
if(!rc) break;
rc = file.WriteInt(ON_Font::Style::Italic == m_font_style);
if(!rc) break;
rc = file.WriteDouble(ON_FontMetrics::DefaultLineFeedRatio);
if(!rc) break;
// 1.2 addition
rc = file.WriteUuid(V5_font_id);
if(!rc) break;
break;
}
return rc;
}
bool ON_Font::Read(
ON_BinaryArchive& file // restore definition from binary archive
)
{
// On September 16, 2015 the "V5" ON_Font was split into
// ON_TextStyle (a document object) and
// ON_Font (a current runtime resource)
*this = ON_Font::Unset;
ON__UINT32 typecode = 0;
ON__INT64 big_value = 0;
if (file.Archive3dmVersion() < 60
|| file.ArchiveOpenNURBSVersion() < ON_TextStyle::binary_archive_opennurbs_version
|| (file.PeekAt3dmBigChunkType(&typecode,&big_value) && 1 == typecode)
)
{
// Dale Lear 2025 May 8 - RH-87126
// Some older version files are triggering this warning when
// override dimstyles are read. It is true V5 files had a text style table,
// but we didn't expect to encounter override dimstyles in these old
// files. It's not clear to me how these files come into existence,
// but this warning seems to be doing more harm than good. In the xase in the bug,
// the older version files read correctly. When V8 and V9 files are saved, they appear
// to be getting saved correctly.
//
// ON_WARNING("Should probably be reading an ON_TextStyle");
int font_index = -1;
ON_UUID font_id = ON_nil_uuid;
return ReadV5(
file,
&font_index,
&font_id
);
}
int major_verision = 0;
int minor_verision = 0;
if (!file.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK, &major_verision, &minor_verision))
{
*this = ON_Font::Default;
return false;
}
bool rc = false;
for (;;)
{
if ( 1 != major_verision )
break;
unsigned int fc = 0;
if (!file.ReadInt(&fc))
break;
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(
fc,
font_weight,
font_stretch,
font_style,
bUnderlined,
bStrikethrough
);
ON_wString windows_logfont_name;
if ( !file.ReadWideString(windows_logfont_name) )
break;
ON_wString postscript_name;
if (!file.ReadString(postscript_name))
break;
if (ON::RuntimeEnvironment::Windows == file.ArchiveRuntimeEnvironment())
{
// Dale Lear - August 16, 2016.
// The value of m_postscript_font_name is damaged in many archives written by Windows Rhino.
// It has values like "Font 01". So, I'm going to clean it
// during read and we will get it right going forward.
// This value is not saved in V5 files.
const unsigned int broken_apple_font_name_version = ON_VersionNumberConstruct(6, 0, 2016, 8, 18, 0);
const unsigned int archive_version = file.Archive3dmVersion();
const unsigned int archive_opennurbs_version = file.ArchiveOpenNURBSVersion();
if (
archive_version < 60
|| (60 == archive_version && archive_opennurbs_version <= broken_apple_font_name_version)
)
{
postscript_name = ON_wString::EmptyString;
}
}
const ON_Font* installed_font = ON_Font::InstalledFontList().FromNames(
postscript_name,
windows_logfont_name,
windows_logfont_name,
nullptr,
font_weight,
font_stretch,
font_style,
false,
true
);
if (nullptr != installed_font)
{
*this = *installed_font;
this->m_font_bUnderlined = bUnderlined;
this->m_font_bStrikethrough = bStrikethrough;
}
else
{
// Either the information in the .3dm file is junk or the font is not installed on this device.
if (windows_logfont_name.IsNotEmpty())
SetWindowsLogfontName(windows_logfont_name);
else
*this = ON_Font::Default;
Internal_SetFontCharacteristicsFromUnsigned(fc);
if (postscript_name.IsNotEmpty())
{
m_loc_postscript_name = postscript_name;
m_en_postscript_name = postscript_name;
}
}
if (minor_verision <= 0)
{
rc = true;
break;
}
// version 1.1 added m_font_description August 2016
ON_wString obsolete_font_description;
if ( !file.ReadString(obsolete_font_description) )
break;
if (minor_verision <= 1)
{
// m_windows_logfont_weight and m_apple_font_weight_trait
// are set above in the call to SetFontCharacteristicsFromUnsigned().
rc = true;
break;
}
// version 1.2 added m_windows_logfont_weight and m_apple_font_weight_trait
int windows_logfont_weight = 0;
if (!file.ReadInt(&windows_logfont_weight))
break;
const bool bSetLogfontWeight
=
#if defined(ON_RUNTIME_WIN)
(nullptr == installed_font)
#else
(ON::RuntimeEnvironment::Windows == file.ArchiveRuntimeEnvironment())
#endif
;
if (bSetLogfontWeight)
m_windows_logfont_weight = windows_logfont_weight;
double apple_font_weight_trait = 0.0;
if (!file.ReadDouble(&apple_font_weight_trait))
break;
const bool bSetAppleFontWeightTrait
=
#if defined(ON_RUNTIME_WIN)
(ON::RuntimeEnvironment::Apple == file.ArchiveRuntimeEnvironment())
#else
(nullptr == installed_font)
#endif
;
if (bSetAppleFontWeightTrait)
m_apple_font_weight_trait = apple_font_weight_trait;
if (minor_verision <= 2)
{
rc = true;
break;
}
// version 1.3 added additional m_point_size and m_LOGFONT_* values.
if (!file.ReadDouble(&m_point_size))
break;
bool bOBSOLETE_Bool = false;
if (!file.ReadBool(&bOBSOLETE_Bool))
break;
if (bOBSOLETE_Bool)
{
unsigned char obsolete_c = 0;
if (!file.ReadChar(&obsolete_c))
break;
if (!file.ReadChar(&obsolete_c))
break;
if (!file.ReadChar(&obsolete_c))
break;
if (!file.ReadChar(&obsolete_c))
break;
int obsolete_i = 0;
if (!file.ReadInt(&obsolete_i))
break;
if (!file.ReadInt(&obsolete_i))
break;
if (!file.ReadInt(&obsolete_i))
break;
if (!file.ReadInt(&obsolete_i))
break;
}
if (minor_verision <= 3)
{
rc = true;
break;
}
// version 1.4 added explicit Family name
ON_wString family_name;
if (!file.ReadString(family_name))
break;
if (nullptr == installed_font)
{
m_loc_family_name = family_name;
m_en_family_name = family_name;
}
if (minor_verision <= 4)
{
rc = true;
break;
}
// version 1.5 explicit names in loc and en and PANOSE1 settings
if (!file.ReadString(m_locale_name))
break;
if (!file.ReadString(m_loc_postscript_name))
break;
if (!file.ReadString(m_en_postscript_name))
break;
if (!file.ReadString(m_loc_windows_logfont_name))
break;
if (!file.ReadString(m_en_windows_logfont_name))
break;
if (!file.ReadString(m_loc_family_name))
break;
if (!file.ReadString(m_en_family_name))
break;
if (!file.ReadString(m_loc_face_name))
break;
if (!file.ReadString(m_en_face_name))
break;
if (!m_panose1.Read(file))
break;
if (minor_verision <= 5)
{
rc = true;
break;
}
// version 1.6 rich text quartet member (regular/bold/italic/bold-italic)
unsigned char quartet_member_as_unsigned = static_cast<unsigned char>(this->m_quartet_member);
if (!file.ReadByte(1,&quartet_member_as_unsigned))
break;
this->m_quartet_member = ON_FontFaceQuartet::MemberFromUnsigned(quartet_member_as_unsigned);
if (minor_verision <= 6)
{
rc = true;
break;
}
rc = true;
break;
}
if (
false == rc
|| (WindowsLogfontName().IsEmpty()
&& PostScriptName().IsEmpty()
&& FamilyName().IsEmpty())
)
{
*this = ON_Font::Default;
}
if (!file.EndRead3dmChunk())
rc = false;
return rc;
}
bool ON_Font::ReadV5(
ON_BinaryArchive& file, // restore definition from binary archive
int* V5_font_index,
ON_UUID* V5_font_id
)
{
*this = ON_Font::Unset;
if ( nullptr != V5_font_index )
*V5_font_index = -1;
if ( nullptr != V5_font_id )
*V5_font_id = ON_nil_uuid;
int major_version = 0;
int minor_version = 0;
if (!file.Read3dmChunkVersion(&major_version, &minor_version))
{
*this = ON_Font::Default;
return false;
}
bool rc = false;
ON_wString windows_logfont_name;
ON_wString postscript_name;
ON_Font::Weight font_weight = ON_Font::Weight::Normal;
ON_Font::Stretch font_stretch = ON_Font::Stretch::Unset;
ON_Font::Style font_style = ON_Font::Style::Upright;
int logfont_weight = 0;
for (;;)
{
if ( 1 != major_version )
break;
int i;
if (!file.ReadInt(&i))
break;
if ( nullptr != V5_font_index )
*V5_font_index = i;
ON_wString corrupt_information_font_description;
if (!file.ReadString(corrupt_information_font_description))
break;
if ( false == ON_Font::IsNotAppleFontName(corrupt_information_font_description) )
{
if (ON::RuntimeEnvironment::Apple == file.ArchiveRuntimeEnvironment())
{
// Files written by Mac Rhino 5 for Mac have the Apple font name stored in this string.
postscript_name = corrupt_information_font_description;
}
else if ( file.ArchiveOpenNURBSVersion() > ON_VersionNumberConstruct(6, 2, 2018, 2, 23, 0) )
{
postscript_name = corrupt_information_font_description;
}
}
// 18 October 2002 Dale Lear:
// This code deals with an old bug.
// The Windows LOGFONT.lfFaceName was written as an array of 64 unsigned shorts.
unsigned short sh[64];
if (!file.ReadShort(64, sh))
break;
wchar_t facename[65];
for(i = 0; i < 64; i++)
facename[i] = sh[i];
facename[64] = 0;
windows_logfont_name = facename;
m_logfont_charset = ON_Font::WindowsLogfontCharSetFromFaceName(WindowsLogfontName());
if (minor_version >= 1)
{
if (!file.ReadInt(&logfont_weight))
break;
if (logfont_weight >= 100 && logfont_weight <= 1000)
{
font_weight = ON_Font::WeightFromWindowsLogfontWeight(logfont_weight);
}
int bItalic = 0;
if (!file.ReadInt(&bItalic))
break;
if ( 0 != bItalic )
font_style = ON_Font::Style::Italic;
double obsolete_linefeed_ratio = ON_FontMetrics::DefaultLineFeedRatio;
if (!file.ReadDouble(&obsolete_linefeed_ratio))
break;
if (minor_version >= 2)
{
ON_UUID uuid = ON_nil_uuid;
if (!file.ReadUuid(uuid))
break;
if ( nullptr != V5_font_id )
*V5_font_id = uuid;
}
}
rc = true;
break;
}
const ON_Font* installed_font = ON_Font::InstalledFontList().FromNames(
postscript_name,
windows_logfont_name,
nullptr,
nullptr,
font_weight,
font_stretch,
font_style,
false,
true
);
if (nullptr != installed_font)
{
*this = *installed_font;
}
else if (rc)
{
SetWindowsLogfontName(windows_logfont_name);
Internal_SetFontWeightTrio(
font_weight,
logfont_weight,
ON_UNSET_VALUE,
false
);
m_loc_postscript_name = postscript_name;
m_en_postscript_name = m_loc_postscript_name;
m_loc_family_name = ON_Font::FamilyNameFromDirtyName(m_loc_windows_logfont_name);
m_en_family_name = ON_Font::FamilyNameFromDirtyName(m_en_windows_logfont_name);
}
else
{
*this = ON_Font::Default;
}
return rc;
}
unsigned int ON_Font::RuntimeSerialNumber() const
{
return m_runtime_serial_number;
}
unsigned int ON_Font::ManagedFontSerialNumber() const
{
if (0 != m_runtime_serial_number)
return m_runtime_serial_number;
const ON_Font* mananged_font = this->ManagedFont();
return (nullptr == mananged_font) ? 0 : mananged_font->RuntimeSerialNumber();
}
#if defined(ON_OS_WINDOWS_GDI)
static
int CALLBACK ON__IsSymbolCharSetFontFaceNameHelper(ENUMLOGFONTEX* lf, NEWTEXTMETRICEX* tm, DWORD font_type, LPARAM)
{
// If the fontname in the logfont structure has
// a corresponding symbol font on the system,
// set the lfCharSet member to SYMBOL_CHARSET,
// otherwise DEFAULT_CHARSET
// The input logfont structure may be modified.
return 7;
}
#endif
unsigned char ON_Font::WindowsLogfontCharSetFromFaceName(
const wchar_t* face_name
)
{
unsigned char logfont_charset = ON_Font::WindowsConstants::logfont_default_charset;
if( nullptr != face_name && face_name[0] > ON_wString::Space )
{
// Problem fonts
if (ON_wString::EqualOrdinal(L"CityBlueprint", -1, face_name, -1, true))
return ON_Font::WindowsConstants::logfont_symbol_charset;
if (ON_wString::EqualOrdinal(L"CountryBlueprint", -1, face_name, -1, true))
return ON_Font::WindowsConstants::logfont_symbol_charset;
#if defined(ON_OS_WINDOWS_GDI)
HDC font_hdc = ON_Font::CreateWindowsLogfontDeviceContext();
if(nullptr != font_hdc)
{
// See if there is a font with this facename that has the symbol charset
LOGFONT logfont;
memset(&logfont, 0, sizeof(logfont));
for(int i = 0; i < LF_FACESIZE && face_name[i]; i++)
logfont.lfFaceName[i] = face_name[i];
// Dale lear - set logfont.lfOutPrecision May 2017
logfont.lfOutPrecision = ON_Font::WindowsConstants::logfont_out_precis;
// Is it a SYMBOL_CHARSET font?
logfont.lfCharSet = ON_Font::WindowsConstants::logfont_symbol_charset;
if (7 == ::EnumFontFamiliesEx(font_hdc, &logfont, (FONTENUMPROC)ON__IsSymbolCharSetFontFaceNameHelper, 0, 0))
{
// Yes, this facename is a "symbol font"
logfont_charset = ON_Font::WindowsConstants::logfont_symbol_charset;
}
ON_Font::DeleteWindowsLogfontDeviceContext(font_hdc);
}
#endif
}
return logfont_charset;
}
const ON_wString& ON_Font::FontDescription() const
{
return
m_loc_postscript_name.IsNotEmpty()
? m_loc_postscript_name
: m_en_postscript_name;
}
bool ON_Font::SetFontDescriptionForExperts(
const wchar_t* ignored_parameter
)
{
// OBSOLETE V6 function.
return true;
}
const wchar_t* ON_Font::FontDescriptionAsPointer() const
{
return static_cast<const wchar_t*>(PostScriptName());
}
const ON_wString ON_Font::Description(
) const
{
return Description(ON_Font::NameLocale::LocalizedFirst, ON_wString::HyphenMinus, ON_wString::Space, true);
}
const ON_wString ON_Font::Description(
ON_Font::NameLocale name_local,
wchar_t family_separator,
wchar_t weight_width_slope_separator,
bool bIncludeUndelinedAndStrikethrough
) const
{
// Do not change bIncludeNotOnDevice to false.
// If you have a situation where you do not want the
// missing font description, then use the override that
// has bIncludeNotOnDevice as a parameter.
const bool bIncludeNotOnDevice = true;
return this->Description(
name_local,
family_separator,
weight_width_slope_separator,
bIncludeUndelinedAndStrikethrough,
bIncludeNotOnDevice
);
}
const ON_wString ON_Font::Description(
ON_Font::NameLocale name_local,
wchar_t family_separator,
wchar_t weight_width_slope_separator,
bool bIncludeUndelinedAndStrikethrough,
bool bIncludeNotOnDevice
) const
{
ON_wString description;
if (bIncludeNotOnDevice && this->IsManagedSubstitutedFont())
{
description += ON_wString(L"[Not on device] ");
}
ON_wString family_name = FamilyName();
family_name.TrimLeftAndRight();
ON_wString logfont_name = WindowsLogfontName();
logfont_name.TrimLeftAndRight();
if (family_name.IsEmpty() && logfont_name.IsNotEmpty())
{
// LOGFONT (width-weight-slant)
description += logfont_name;
const ON_wString wws = this->WidthWeightSlantDescription();
if (wws.IsNotEmpty())
{
description += ON_wString(L" (");
description += wws;
description += ON_wString(L")");
}
}
else if (family_name.IsNotEmpty() )
{
// family face
description += family_name;
ON_wString face_name = FaceName();
face_name.TrimLeftAndRight();
if ( face_name.IsNotEmpty() )
{
description += ON_wString(L" ");
description += face_name;
}
}
else
{
ON_wString postscript_name = PostScriptName();
postscript_name.TrimLeftAndRight();
if (postscript_name.IsNotEmpty())
description += postscript_name;
}
if (description.IsEmpty())
description = this->WidthWeightSlantDescription();
if (description.IsNotEmpty())
{
const bool bUnderlined = IsUnderlined();
const bool bStrikethrough = IsStrikethrough();
if (bUnderlined && bStrikethrough)
description += ON_wString(L" (underlined,strikethrough)");
else if (bUnderlined)
description += ON_wString(L" (underlined)");
else if (bStrikethrough)
description += ON_wString(L" (strikethrough)");
}
return description;
}
double ON_Font::LinefeedRatio() const
{
return ON_FontMetrics::DefaultLineFeedRatio;
}
bool ON_Font::HasUnsetProperties(
bool bCheckFamilyName,
bool bCheckPostScriptName
) const
{
if (ON_Font::Style::Unset == m_font_style)
return true;
if (ON_Font::Weight::Unset == m_font_weight)
return true;
if (ON_Font::Stretch::Unset == m_font_stretch)
return true;
if (bCheckFamilyName && FamilyName().IsEmpty() && WindowsLogfontName().IsEmpty())
return true;
if (bCheckPostScriptName && PostScriptName().IsEmpty())
return true;
return false;
}
unsigned int ON_Font::SetUnsetProperties(
const ON_Font& source,
bool bUpdateDescription
)
{
if (false == ON_FONT_MODIFICATION_PERMITTED)
return 0;
unsigned int changed_property_count = 0;
if (ON_Font::Weight::Unset == m_font_weight
&& ON_Font::Weight::Unset != source.m_font_weight
)
{
m_windows_logfont_weight = source.m_windows_logfont_weight;
m_apple_font_weight_trait = source.m_apple_font_weight_trait;
m_font_weight = source.m_font_weight;
changed_property_count++;
}
if (ON_Font::Style::Unset == m_font_style
&& ON_Font::Style::Unset == source.m_font_style
)
{
m_font_style = source.m_font_style;
changed_property_count++;
}
if (ON_Font::Stretch::Unset == m_font_stretch
&& ON_Font::Stretch::Unset != source.m_font_stretch
)
{
m_font_stretch = source.m_font_stretch;
changed_property_count++;
}
const bool bEqualWeightStretch
= ON_Font::Weight::Unset != m_font_weight
&& ON_Font::Stretch::Unset != m_font_stretch
&& m_font_weight == source.m_font_weight
&& m_font_stretch == source.m_font_stretch
;
const bool bEqualWeightStretchStyle
= bEqualWeightStretch
&& ON_Font::Style::Unset != m_font_style
&& m_font_style == source.m_font_style;
bool bSameFamily = ON_Font::EqualFontFamily(this, &source);
bool bCopyFamilyName = false;
bool bCopyLOGFONTFaceName = false;
bool bCopyPostScriptName = false;
if ( FamilyName().IsEmpty() && WindowsLogfontName().IsEmpty() && PostScriptName().IsEmpty() )
{
bCopyFamilyName = source.FamilyName().IsNotEmpty();
bCopyLOGFONTFaceName = source.WindowsLogfontName().IsNotEmpty();
bCopyPostScriptName = source.PostScriptName().IsNotEmpty();
}
else if ( bSameFamily )
{
// at least one of m_family_name or m_face_name is set.
bCopyFamilyName = FamilyName().IsEmpty();
// If "this" and source have the same m_family_name[], weight, and stretch,
// then copy source.m_face_name.
//
// NOTE WELL:
// Both "this" and source must have the same weight and stretch because
// is is common for LOGFONT name to be used to distigush between weight
// and/or stretch in the same family.
// Generally, this situation is a result of LOGFONT GDI restrictions
// and occures when a family has more than the four standard faces
// (regular, bold, italic, bold-italic).
// Example:
// Family = "Arial"
// LOGFONTs face names include "Arial", "Arial Narrow" (different stretch), "Arial Black" (different weight)
// Family = "Segoe UI"
// Many LOGFONTs with various weights and stretches.
bCopyLOGFONTFaceName
= source.WindowsLogfontName().IsNotEmpty()
&& bEqualWeightStretch
;
bCopyPostScriptName =
source.PostScriptName().IsNotEmpty()
&& bEqualWeightStretchStyle
;
}
if (bCopyFamilyName)
{
m_loc_family_name = source.m_loc_family_name;
m_en_family_name = source.m_en_family_name;
m_loc_face_name = source.m_loc_face_name;
m_en_face_name = source.m_en_face_name;
changed_property_count++;
}
if (bCopyLOGFONTFaceName)
{
m_loc_windows_logfont_name = source.m_loc_windows_logfont_name;
m_en_windows_logfont_name = source.m_en_windows_logfont_name;
changed_property_count++;
}
// The PostScript name identifies the complete face (family, weight, stretch, style).
if ( bCopyPostScriptName)
{
m_loc_postscript_name = source.m_loc_postscript_name;
m_en_postscript_name = source.m_en_postscript_name;
changed_property_count++;
}
if (changed_property_count > 0)
{
m_simulated = 0;
m_managed_installed_font_and_bits = 0;
Internal_AfterModification();
}
return changed_property_count;
}
bool ON_Font::SetFontFaceName(
const wchar_t* windows_logfont_name
)
{
return SetWindowsLogfontName(windows_logfont_name);
}
bool ON_Font::SetFamilyName(
const wchar_t* family_name
)
{
ON_wString local_family_name = ON_Font::FamilyNameFromDirtyName(family_name);
local_family_name.TrimLeftAndRight();
family_name = local_family_name;
if ( ON_wString::EqualOrdinal(family_name, FamilyName(), false) )
{
return true;
}
if ( false == ON_FONT_MODIFICATION_PERMITTED )
return false;
Internal_ClearName(true, true, true, true);
m_logfont_charset = ON_Font::WindowsConstants::logfont_default_charset;
m_loc_family_name = family_name;
m_en_family_name = m_loc_family_name;
Internal_AfterModification();
return true;
}
bool ON_Font::SetWindowsLogfontName(
const wchar_t* windows_logfont_name
)
{
if (false == ON_Font::IsValidFaceName(windows_logfont_name))
return false;
const ON_Font::Weight font_weight = ON_Font::Weight::Normal;
const ON_Font::Style font_style = ON_Font::Style::Upright;
const ON_Font::Stretch font_stretch = ON_Font::Stretch::Medium;
const ON_Font* installed_font =
ON_Font::InstalledFontList().FromNames(
nullptr,
windows_logfont_name,
nullptr,
nullptr,
font_weight,
font_stretch,
font_style,
false,
false
);
if (nullptr != installed_font)
{
*this = *installed_font;
}
else
{
const unsigned char logfont_charset = ON_Font::WindowsLogfontCharSetFromFaceName(windows_logfont_name);
const ON_wString family_name = ON_Font::FamilyNameFromDirtyName(windows_logfont_name);
if (
ON_wString::EqualOrdinal(windows_logfont_name, m_loc_windows_logfont_name, false)
&& family_name == m_loc_family_name
&& font_weight == m_font_weight
&& font_style == m_font_style
&& font_stretch == m_font_stretch
&& logfont_charset == m_logfont_charset
&& ON_Font::Origin::Unknown == m_font_origin
)
{
return true;
}
if (false == ON_FONT_MODIFICATION_PERMITTED)
return false;
// TODO - better support for generating PostScript names from LOGFONT.lfFaceNames
// This code at least handles the 4 most common names passed to this setter.
if (ON_wString::EqualOrdinal(L"Arial", windows_logfont_name, true))
m_loc_postscript_name = L"ArialMT";
else if (ON_wString::EqualOrdinal(L"Segoe UI", windows_logfont_name, true)
|| ON_wString::EqualOrdinal(L"SegoeUI", windows_logfont_name, true))
{
m_loc_postscript_name = L"SegoeUI";
}
else if (ON_wString::EqualOrdinal(L"Helvetica Neue", windows_logfont_name, true)
|| ON_wString::EqualOrdinal(L"HelveticaNeue", windows_logfont_name, true))
m_loc_postscript_name = L"HelveticaNeue";
else if (ON_wString::EqualOrdinal(L"Helvetica", windows_logfont_name, true))
m_loc_postscript_name = L"Helvetica";
else
m_loc_postscript_name = ON_wString::EmptyString;
m_en_postscript_name = m_loc_postscript_name;
m_loc_family_name = family_name;
m_en_family_name = m_loc_family_name;
m_loc_windows_logfont_name = windows_logfont_name;
m_en_windows_logfont_name = m_loc_windows_logfont_name;
m_logfont_charset = logfont_charset;
m_font_weight = font_weight;
m_font_style = font_style;
m_font_stretch = font_stretch;
m_font_origin = ON_Font::Origin::Unknown;
}
Internal_AfterModification();
return true;
}
const wchar_t* ON_Font::FontFaceName() const
{
// Was never a "real" face name.
// Always returned a value from or used in a Windows GDI LOGFONT.lfFaceName[].
return
m_loc_windows_logfont_name.IsNotEmpty()
? static_cast<const wchar_t*>(m_loc_windows_logfont_name)
: static_cast<const wchar_t*>(m_en_windows_logfont_name);
}
const wchar_t* ON_Font::WindowsLogfontNameAsPointer() const
{
return
m_loc_windows_logfont_name.IsNotEmpty()
? static_cast<const wchar_t*>(m_loc_windows_logfont_name)
: static_cast<const wchar_t*>(m_en_windows_logfont_name);
}
ON_Font::Weight ON_Font::FontWeight() const
{
return m_font_weight;
}
bool ON_Font::IsLight() const
{
const int font_weight = ON_Font::WindowsLogfontWeightFromWeight(m_font_weight);
const int normal_weight = ON_Font::WindowsLogfontWeightFromWeight(ON_Font::Weight::Normal);
return (font_weight < normal_weight && m_font_weight != ON_Font::Weight::Unset);
}
bool ON_Font::IsNormalWeight() const
{
return (ON_Font::Weight::Normal == m_font_weight || ON_Font::Weight::Medium == m_font_weight);
}
bool ON_Font::IsBold() const
{
return ON_Font::IsBoldWeight(m_font_weight);
}
bool ON_Font::IsItalic() const
{
return (ON_Font::Style::Italic == m_font_style);
}
bool ON_Font::IsUpright() const
{
return (ON_Font::Style::Upright == m_font_style);
}
bool ON_Font::IsItalicOrOblique() const
{
return (ON_Font::Style::Italic == m_font_style || ON_Font::Style::Oblique == m_font_style);
}
bool ON_Font::IsOblique()
{
return (ON_Font::Style::Oblique == m_font_style);
}
void ON_Font::Internal_AfterModification(
)
{
// This occures when a characteristic like PostScriptName(), weight, facename, style is changed
// on a font that was original set from complete platform information.
// It means the ON_Font may or may not have valid settings in platform specific
// fields. In particular the Apple font name is typically no longer valid.
if (ON_Font::Origin::Unset != m_font_origin && ON_Font::Origin::Unknown != m_font_origin)
m_font_origin = ON_Font::Origin::Unknown;
m_panose1 = ON_PANOSE1::Zero;
m_simulated = 0;
m_font_glyph_cache.reset();
}
void ON_Font::Internal_ClearAllNames()
{
Internal_ClearName(true, true, true, true);
}
void ON_Font::Internal_ClearName(
bool bClearFamilyName,
bool bClearFaceName,
bool bClearPostScriptName,
bool bClearWindowsLogfontName
)
{
if (bClearFamilyName)
{
m_loc_family_name = ON_wString::EmptyString;
m_en_family_name = ON_wString::EmptyString;
}
if (bClearFaceName)
{
m_loc_face_name = ON_wString::EmptyString;
m_en_face_name = ON_wString::EmptyString;
}
if (bClearPostScriptName)
{
m_loc_postscript_name = ON_wString::EmptyString;
m_en_postscript_name = ON_wString::EmptyString;
}
if (bClearWindowsLogfontName)
{
m_loc_windows_logfont_name = ON_wString::EmptyString;
m_en_windows_logfont_name = ON_wString::EmptyString;
m_quartet_member = ON_FontFaceQuartet::Member::Unset;
}
}
bool ON_Font::Internal_SetFontWeightTrio(
ON_Font::Weight font_weight,
int windows_logfont_weight,
double apple_font_weight_trait,
bool bUpdateFontDescription
)
{
if (ON_Font::Weight::Unset == font_weight)
{
if (windows_logfont_weight > 0 && windows_logfont_weight <= 1000)
{
font_weight = ON_Font::WeightFromWindowsLogfontWeight(windows_logfont_weight);
}
else if (-1.0 <= apple_font_weight_trait && apple_font_weight_trait <= 1.0)
{
font_weight = ON_Font::WeightFromAppleFontWeightTrait(apple_font_weight_trait);
}
}
font_weight = ON_Font::FontWeightFromUnsigned((unsigned int)static_cast<unsigned char>(font_weight));
if (ON_Font::Weight::Unset == font_weight)
return false;
if (false == (1 <= windows_logfont_weight && windows_logfont_weight <= 1000))
windows_logfont_weight = ON_Font::WindowsLogfontWeightFromWeight(font_weight);
if (false == (-1.0 <= apple_font_weight_trait && apple_font_weight_trait <= 1.0) )
apple_font_weight_trait = ON_Font::AppleFontWeightTraitFromWeight(font_weight);
if (
font_weight != m_font_weight
|| false == (apple_font_weight_trait == m_apple_font_weight_trait) // handles nans correctly
|| windows_logfont_weight != m_windows_logfont_weight
)
{
if (false == ON_FONT_MODIFICATION_PERMITTED)
return false;
if (font_weight != m_font_weight)
{
m_font_weight = font_weight;
}
if (false == (apple_font_weight_trait == m_apple_font_weight_trait))
m_apple_font_weight_trait = apple_font_weight_trait;
if (windows_logfont_weight != m_windows_logfont_weight)
m_windows_logfont_weight = windows_logfont_weight;
Internal_AfterModification();
}
return true;
}
bool ON_Font::SetFontWeight(ON_Font::Weight font_weight)
{
return Internal_SetFontWeightTrio(
font_weight,
-1,
ON_UNSET_VALUE,
font_weight != m_font_weight
);
}
bool ON_Font::SetWindowsLogfontWeight(
int windows_logfont_weight
)
{
const ON_Font::Weight font_weight = ON_Font::WeightFromWindowsLogfontWeight(windows_logfont_weight);
return Internal_SetFontWeightTrio(
font_weight,
windows_logfont_weight,
ON_UNSET_VALUE,
windows_logfont_weight != m_windows_logfont_weight
);
}
int ON_Font::WindowsLogfontWeight() const
{
return
(100 <= m_windows_logfont_weight && m_windows_logfont_weight <= 1000)
? m_windows_logfont_weight
: ON_Font::WindowsLogfontWeightFromWeight(FontWeight());
}
bool ON_Font::SetAppleWeightOfFont(
int apple_weight_of_font
)
{
const ON_Font::Weight font_weight = ON_Font::WeightFromAppleWeightOfFont(apple_weight_of_font);
double apple_font_weight_trait = ON_Font::AppleFontWeightTraitFromWeight(font_weight);
if (0 <= apple_weight_of_font && apple_weight_of_font <= 9)
{
apple_font_weight_trait = (1.0 - apple_weight_of_font) / 7.5;
if (apple_font_weight_trait < -1.0)
apple_font_weight_trait = -1.0;
else if (apple_font_weight_trait > 1.0)
apple_font_weight_trait = 1.0;
}
return Internal_SetFontWeightTrio(
font_weight,
-1,
apple_font_weight_trait,
PostScriptName().IsEmpty()
);
}
int ON_Font::AppleWeightOfFont() const
{
return WindowsLogfontWeight() / 100;
}
bool ON_Font::SetAppleFontWeightTrait(
double apple_font_weight_trait
)
{
if (false == ON_IsValid(apple_font_weight_trait))
return false; // nan or unset value
const ON_Font::Weight font_weight = ON_Font::WeightFromAppleFontWeightTrait(apple_font_weight_trait);
return Internal_SetFontWeightTrio(
font_weight,
-1,
apple_font_weight_trait,
PostScriptName().IsEmpty()
);
}
double ON_Font::AppleFontWeightTrait() const
{
// old function that cannot be changed because it would break the SDK.
return
(m_apple_font_weight_trait >= -1.0 && m_apple_font_weight_trait <= 1.0)
? m_apple_font_weight_trait
: ON_Font::AppleFontWeightTraitFromWeight(this->FontWeight());
}
double ON_Font::AppleFontWeightTraitEx() const
{
return
ON_Font::Origin::AppleFont == FontOrigin()
&& m_apple_font_weight_trait >= -1.0
&& m_apple_font_weight_trait <= 1.0
? m_apple_font_weight_trait
: ON_UNSET_VALUE;
}
double ON_Font::AppleFontWidthTrait() const
{
return
ON_Font::Origin::AppleFont == FontOrigin()
&& m_apple_font_width_trait >= -1.0
&& m_apple_font_width_trait <= 1.0
? m_apple_font_width_trait
: ON_UNSET_VALUE;
}
bool ON_Font::IsValidPointSize(
double point_size
)
{
return (point_size > 0.0 && point_size < 1.0e10);
}
double ON_Font::PointSize() const
{
return m_point_size;
}
bool ON_Font::SetPointSize(
double point_size
)
{
double x
= ON_Font::IsValidPointSize(point_size)
? point_size
: 0.0;
if (!(m_point_size == x))
{
if ( false == ON_FONT_MODIFICATION_PERMITTED )
return false;
m_point_size = x;
// Do not call Internal_AfterModification() here - the source of the fundamental font definition is the same.
m_font_characteristics_hash = ON_SHA1_Hash::ZeroDigest;
}
return true;
}
ON_Font::Style ON_Font::FontStyle() const
{
return m_font_style;
}
bool ON_Font::SetFontStyle(
ON_Font::Style font_style
)
{
if (m_font_style != font_style)
{
if ( false == ON_FONT_MODIFICATION_PERMITTED )
return false;
m_font_style = font_style;
Internal_AfterModification();
}
return true;
}
ON_Font::Stretch ON_Font::FontStretch() const
{
return m_font_stretch;
}
bool ON_Font::SetFontStretch(
ON_Font::Stretch font_stretch
)
{
if (m_font_stretch != font_stretch)
{
if ( false == ON_FONT_MODIFICATION_PERMITTED )
return false;
m_font_stretch = font_stretch;
Internal_AfterModification();
}
return true;
}
bool ON_Font::IsUnderlined() const
{
return m_font_bUnderlined;
}
bool ON_Font::SetUnderlined(bool bUnderlined)
{
if( m_font_bUnderlined != (bUnderlined?true:false))
{
if ( false == ON_FONT_MODIFICATION_PERMITTED )
return false;
m_font_bUnderlined = (bUnderlined?true:false);
m_font_characteristics_hash = ON_SHA1_Hash::ZeroDigest;
}
return true;
}
bool ON_Font::IsStrikethrough() const
{
return m_font_bStrikethrough;
}
bool ON_Font::SetStrikethrough(bool bStrikethrough)
{
if(m_font_bStrikethrough != (bStrikethrough?true:false))
{
if ( false == ON_FONT_MODIFICATION_PERMITTED )
return false;
m_font_bStrikethrough = (bStrikethrough?true:false);
m_font_characteristics_hash = ON_SHA1_Hash::ZeroDigest;
}
return true;
}
bool ON_Font::IsSymbolFont() const
{
return (ON_Font::WindowsConstants::logfont_symbol_charset == m_logfont_charset);
}
const ON_PANOSE1 ON_Font::PANOSE1() const
{
return m_panose1;
}
void ON_Font::SetPANOSE1(
ON_PANOSE1 panose1
)
{
m_panose1 = panose1;
}
unsigned char ON_Font::LogfontCharSet() const
{
return m_logfont_charset;
}
bool ON_Font::SetLogfontCharSet(unsigned char logfont_charset)
{
if (ON_Font::WindowsConstants::logfont_symbol_charset != logfont_charset)
logfont_charset = ON_Font::WindowsConstants::logfont_default_charset;
if(logfont_charset != m_logfont_charset)
{
if ( false == ON_FONT_MODIFICATION_PERMITTED )
return false;
m_logfont_charset = logfont_charset;
}
return true;
}
const ON_FontMetrics& ON_Font::FontMetrics() const
{
class ON_FontGlyphCache* font_cache = FontGlyphCache(true);
if (nullptr != font_cache)
return font_cache->m_normalized_metrics;
return ON_FontMetrics::Unset;
}
const ON_FontMetrics& ON_Font::FontUnitFontMetrics() const
{
class ON_FontGlyphCache* font_cache = FontGlyphCache(true);
if (nullptr != font_cache)
return font_cache->m_font_unit_metrics;
return ON_FontMetrics::Unset;
}
double ON_Font::FontUnitToNormalizedScale() const
{
class ON_FontGlyphCache* font_cache = FontGlyphCache(true);
if (nullptr != font_cache)
return font_cache->m_font_unit_to_normalized_scale;
return 1.0;
}
double ON_Font::NormalizedToFontUnitScale() const
{
class ON_FontGlyphCache* font_cache = FontGlyphCache(true);
if (nullptr != font_cache)
return font_cache->m_normalized_to_font_unit_scale;
return 1.0;
}
int ON_Font::HeightOfI() const
{
return FontMetrics().AscentOfCapital();
}
int ON_Font::HeightOfLinefeed() const
{
return FontMetrics().LineSpace();
}
double ON_Font::HeightScale(double text_height) const
{
return FontMetrics().GlyphScale(text_height);
}
int ON_Font::GetStrikeoutSize() const
{
return FontMetrics().StrikeoutThickness();
}
int ON_Font::GetStrikeoutPosition() const
{
return FontMetrics().StrikeoutPosition();
}
int ON_Font::GetUnderscoreSize() const
{
return FontMetrics().UnderscoreThickness();
}
int ON_Font::GetUnderscorePosition() const
{
return FontMetrics().UnderscorePosition();
}
const class ON_SHA1_Hash& ON_Font::FontCharacteristicsHash() const
{
if (m_font_characteristics_hash.IsZeroDigest())
{
ON_SHA1 sha1;
sha1.AccumulateUnsigned64(sizeof(*this));
// Even on Apple OS, we need to accumulate the WindowsLogfontName()
// because it is what is used for the fake regular/bold/italic/bold-italic
// user interface the Rhino uses on Apple.
ON_SHA1_Hash string_hash;
const ON_wString windows_logfont_name = WindowsLogfontName();
if ( windows_logfont_name.IsNotEmpty() )
{
string_hash = ON_SHA1_Hash::StringHash(windows_logfont_name);
sha1.AccumulateSubHash(string_hash);
}
sha1.AccumulateUnsigned8(static_cast<ON__UINT8>(m_font_weight));
sha1.AccumulateUnsigned8(static_cast<ON__UINT8>(m_font_style));
sha1.AccumulateUnsigned8(static_cast<ON__UINT8>(m_font_stretch));
sha1.AccumulateBool(m_font_bUnderlined);
sha1.AccumulateBool(m_font_bStrikethrough);
sha1.AccumulateUnsigned8(static_cast<ON__UINT8>(m_logfont_charset));
const ON_wString family_name = FamilyName();
if (family_name.IsNotEmpty())
{
string_hash = ON_SHA1_Hash::StringHash(family_name);
sha1.AccumulateSubHash(string_hash);
}
if (m_point_size > 0)
{
sha1.AccumulateDouble(m_point_size);
}
#if defined(ON_RUNTIME_WIN)
sha1.AccumulateInteger32(m_windows_logfont_weight);
#endif
#if defined(ON_RUNTIME_APPLE)
// The PostScript name is not hashed on Windows because
// it is not predictable with simulated fonts.
// The PostScript name is critical on Apple OS and
// is the most reliable way to uniquely identify
// a font on Apple OS.
sha1.AccumulateDouble(m_apple_font_weight_trait);
const ON_wString postscript_name = PostScriptName();
if (postscript_name.IsNotEmpty())
{
string_hash = ON_SHA1_Hash::StringHash(postscript_name);
sha1.AccumulateSubHash(string_hash);
}
#endif
m_font_characteristics_hash = sha1.Hash();
}
return m_font_characteristics_hash;
}
int ON_Font::CompareFontCharacteristicsForExperts(
bool bComparePlatformSpecificCharacteristics,
bool bIgnoreUnsetCharacteristics,
const ON_Font& lhs,
const ON_Font& rhs
)
{
int i = 0;
int j = 0;
for (;;)
{
const ON_wString lhs_family_name = lhs.FamilyName();
const ON_wString rhs_family_name = rhs.FamilyName();
const bool bCompareFamilyName =
false == bIgnoreUnsetCharacteristics
|| (0 != lhs_family_name.IsNotEmpty() && 0 != rhs_family_name.IsNotEmpty());
if (bCompareFamilyName)
{
i = ON_wString::CompareOrdinal(lhs_family_name, rhs_family_name, true);
j = 0;
if ( i != j )
break;
}
const ON_wString lhs_logfont_name = lhs.WindowsLogfontName();
const ON_wString rhs_logfont_name = rhs.WindowsLogfontName();
const bool bCompareWindowsLogfontName =
false == bIgnoreUnsetCharacteristics
|| (lhs_logfont_name.IsNotEmpty() && rhs_logfont_name.IsNotEmpty());
if (bCompareWindowsLogfontName)
{
i = ON_wString::CompareOrdinal(lhs_logfont_name, rhs_logfont_name, true);
j = 0;
if ( i != j )
break;
}
const ON_Font::Weight lhs_weight = lhs.m_font_weight;
const ON_Font::Weight rhs_weight = rhs.m_font_weight;
const bool bCompareWeight =
false == bIgnoreUnsetCharacteristics
|| (ON_Font::Weight::Unset != lhs_weight && ON_Font::Weight::Unset != rhs_weight);
if (bCompareWeight)
{
i = ON_Font::WindowsLogfontWeightFromWeight(lhs_weight);
j = ON_Font::WindowsLogfontWeightFromWeight(rhs_weight);
if (i != j)
break;
}
const ON_Font::Style lhs_style = lhs.m_font_style;
const ON_Font::Style rhs_style = rhs.m_font_style;
const bool bCompareStyle =
false == bIgnoreUnsetCharacteristics
|| (ON_Font::Style::Unset != lhs_style && ON_Font::Style::Unset != rhs_style);
if (bCompareStyle)
{
i = static_cast<int>(lhs_style);
j = static_cast<int>(rhs_style);
if (i != j)
break;
}
const ON_Font::Stretch lhs_stretch = lhs.m_font_stretch;
const ON_Font::Stretch rhs_stretch = rhs.m_font_stretch;
const bool bCompareStretch =
false == bIgnoreUnsetCharacteristics
|| (ON_Font::Stretch::Unset != lhs_stretch && ON_Font::Stretch::Unset != rhs_stretch);
if (bCompareStretch)
{
i = static_cast<int>(lhs_stretch);
j = static_cast<int>(rhs_stretch);
if (i != j)
break;
}
i = lhs.m_font_bUnderlined ? 1 : 0;
j = rhs.m_font_bUnderlined ? 1 : 0;
if ( i != j )
break;
i = lhs.m_font_bStrikethrough ? 1 : 0;
j = rhs.m_font_bStrikethrough ? 1 : 0;
if ( i != j )
break;
const double lhs_point_size = ON_Font::IsValidPointSize(lhs.m_point_size) ? lhs.m_point_size : 0.0;
const double rhs_point_size = ON_Font::IsValidPointSize(rhs.m_point_size) ? rhs.m_point_size : 0.0;
j = 0;
if (lhs_point_size < rhs_point_size)
i = -1;
else if (rhs_point_size < lhs_point_size)
i = 1;
else
i = 0;
if (i != j)
break;
if (false == bComparePlatformSpecificCharacteristics)
break;
#if defined(ON_RUNTIME_WIN)
i = (ON_Font::WindowsConstants::logfont_symbol_charset == lhs.m_logfont_charset) ? 1 : 0;
j = (ON_Font::WindowsConstants::logfont_symbol_charset == rhs.m_logfont_charset) ? 1 : 0;
if ( i != j )
break;
#endif
#if defined(ON_RUNTIME_APPLE)
const ON_wString lhs_postscript_name = lhs.PostScriptName();
const ON_wString rhs_postscript_name = rhs.PostScriptName();
const bool bComparePostScriptName =
false == bIgnoreUnsetCharacteristics
|| (lhs_postscript_name.IsNotEmpty() && rhs_postscript_name.IsNotEmpty());
if (bComparePostScriptName)
{
i = ON_wString::CompareOrdinal(lhs_postscript_name, rhs_postscript_name, false);
if (0 == i)
{
if (lhs.m_apple_font_weight_trait < rhs.m_apple_font_weight_trait)
i = -1;
else if (lhs.m_apple_font_weight_trait > rhs.m_apple_font_weight_trait)
i = 1;
}
j = 0;
if (i != j)
break;
}
#endif
break;
}
if (i < j)
return -1;
if (i > j)
return 1;
return 0;
}
int ON_Font::CompareFontCharacteristics(
const ON_Font& lhs,
const ON_Font& rhs
)
{
const bool bComparePlatformSpecificCharacteristics = true;
const bool bIgnoreUnsetCharacteristics = false;
return ON_Font::CompareFontCharacteristicsForExperts(
bComparePlatformSpecificCharacteristics,
bIgnoreUnsetCharacteristics,
lhs,
rhs
);
}
bool ON_Font::EqualFontCharacteristics(
const ON_Font& a,
const ON_Font& b
)
{
return (0 == ON_Font::CompareFontCharacteristics(a,b));
}
ON_Font::Origin ON_Font::FontOriginFromUnsigned(
unsigned int unsigned_font_origin
)
{
switch (unsigned_font_origin)
{
ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Origin::Unset);
ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Origin::Unknown);
ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Origin::WindowsFont);
ON_ENUM_FROM_UNSIGNED_CASE(ON_Font::Origin::AppleFont);
};
ON_ERROR("Invalid unsigned_font_origin value.");
return ON_Font::Origin::Unset;
}
ON_Font::Origin ON_Font::FontOrigin() const
{
return m_font_origin;
}
void ON_Font::SetFontOrigin(
ON_Font::Origin font_origin
)
{
m_font_origin = font_origin;
}
bool ON_Font::IsSimulated() const
{
return 0 != m_simulated;
}
bool ON_Font::SimulatedWeight() const
{
return (0 != (m_simulated & 2));
}
bool ON_Font::SimulatedStretch() const
{
return (0 != (m_simulated & 4));
}
bool ON_Font::SimulatedStyle() const
{
return (0 != (m_simulated & 8));
}
void ON_Font::SetSimulated(
bool bSimulatedWeight,
bool bSimulatedStretch,
bool bSimulatedStyle,
bool bSimulatedOther
)
{
ON__UINT8 s = 0;
if (bSimulatedWeight)
s |= 2;
if (bSimulatedStretch)
s |= 4;
if (bSimulatedStyle)
s |= 8;
if (bSimulatedOther)
s |= 1;
m_simulated = s;
}
#if defined(ON_RUNTIME_WIN)
static int ON_InternalVerticalPixelScale(
HDC hdc,
bool bDCtoLP,
int y0
)
{
int y1 = -1;
for (;;)
{
POINT origin{ 0,0 };
POINT pt{ 0, y0 };
if (bDCtoLP)
{
if (0 == ::DPtoLP(hdc, &pt, 1))
break;
if (0 == ::DPtoLP(hdc, &origin, 1))
break;
}
else
{
if (0 == ::LPtoDP(hdc, &pt, 1))
break;
if (0 == ::LPtoDP(hdc, &origin, 1))
break;
}
y1 = std::abs(pt.y - origin.y);
break;
}
return y1;
}
bool ON_Font::GetWindowsDeviceToLogicalHeightScales(
HDC hdc,
double* device_to_logical_scale,
double* logical_to_device_scale
)
{
if (nullptr != device_to_logical_scale)
*device_to_logical_scale = 1.0;
if (nullptr != logical_to_device_scale)
*logical_to_device_scale = 1.0;
int device_coordinate = 0;
int logical_coordinate = 0;
// see which is larger, device or logical
const int thousand = 1000;
const int device_to_logical_1000x = ON_InternalVerticalPixelScale(hdc, true, thousand);
const int logical_to_device_1000x = ON_InternalVerticalPixelScale(hdc, false, thousand);
if (device_to_logical_1000x <= 0 && logical_to_device_1000x <= 0)
{
ON_ERROR("DPtoLP(hdc, ...) and LPtoDP(hdc, ...) failed.");
return false;
}
if (thousand == device_to_logical_1000x && thousand == logical_to_device_1000x)
{
// vertical logical pixels are the same size as device pixels
device_coordinate = 1;
logical_coordinate = 1;
}
else
{
// bDCtoLP = true if logical pixels are smaller than device pixels
const bool bDCtoLP = (device_to_logical_1000x >= logical_to_device_1000x);
const int s1000 = bDCtoLP ? device_to_logical_1000x : logical_to_device_1000x;
int y0 = 0;
int y1 = 0;
const int s1024 = ON_InternalVerticalPixelScale(hdc, bDCtoLP, 1024);
for (y0 = 1; y0 <= 100; y0++)
{
y1 = ON_InternalVerticalPixelScale(hdc, bDCtoLP, y0);
if (y0*s1000 == y1*thousand || y0*s1024 == y1*1024)
{
break;
}
y1 = 0;
}
if (0 == y1)
{
y0 = thousand;
y1 = s1000;
}
if (bDCtoLP)
{
device_coordinate = y0;
logical_coordinate = y1;
}
else
{
device_coordinate = y1;
logical_coordinate = y0;
}
}
if (nullptr != device_to_logical_scale)
{
*device_to_logical_scale = ((double)logical_coordinate) / ((double)device_coordinate);
}
if (nullptr != logical_to_device_scale)
{
*logical_to_device_scale = ((double)device_coordinate) / ((double)logical_coordinate);
}
return true;
}
HDC ON_Font::CreateWindowsLogfontDeviceContext()
{
return ::CreateCompatibleDC(nullptr);
}
void ON_Font::DeleteWindowsLogfontDeviceContext(
HDC hdc
)
{
if ( nullptr != hdc )
::DeleteDC(hdc);
}
bool ON_Font::GetWindowsTextMetrics(
int map_mode,
HDC hdc,
const LOGFONT& logfont,
TEXTMETRIC& textmetric
)
{
bool rc = false;
TEXTMETRIC tm;
memset(&tm, 0, sizeof(tm));
textmetric = tm;
// need to get TEXTMETRICS tmInternalLeading
const bool bValidMapModeParameter = (map_mode >= MM_MIN && map_mode <= MM_MAX);
const bool bNeedDeviceContext = (false == bValidMapModeParameter || MM_TEXT == map_mode);
HDC font_hdc = nullptr;
const int savedDC
= (nullptr != hdc)
? ::SaveDC(hdc)
: 0;
HFONT hfont = 0;
for (;;)
{
if (nullptr == hdc)
{
font_hdc = ON_Font::CreateWindowsLogfontDeviceContext();
hdc = font_hdc;
if (nullptr == hdc)
break;
}
const int hdc_mm = ::GetMapMode(hdc);
int mm
= bValidMapModeParameter
? map_mode
: ((nullptr != hdc) ? hdc_mm : 0);
const bool bValidMM = (mm >= MM_MIN && mm <= MM_MAX);
if (false == bValidMM)
{
if (ON_Font::MAP_MODE_ZERO_ERROR_SUPPRESS != map_mode)
{
ON_ERROR("GetMapMode(hdc) failed.");
}
break;
}
if (hdc_mm != mm && bValidMM)
{
::SetMapMode(hdc, mm);
}
hfont = ::CreateFontIndirect(&logfont);
if (0 == hfont)
break;
::SelectObject(hdc, hfont);
if (0 == ::GetTextMetrics(hdc, &tm))
{
if (ON_Font::MAP_MODE_ZERO_ERROR_SUPPRESS != map_mode)
{
ON_ERROR("GetTextMetrics(hdc,...) failed.");
}
break;
}
textmetric = tm;
rc = true;
break;
}
if (nullptr != font_hdc)
ON_Font::DeleteWindowsLogfontDeviceContext(font_hdc);
else if ( nullptr != hdc)
::RestoreDC(hdc, savedDC);
if (0 != hfont)
::DeleteObject(hfont);
return rc;
}
int ON_Font::WindowsLogfontCharacterHeight(
int map_mode,
HDC hdc,
const LOGFONT& logfont
)
{
if (logfont.lfHeight <= 0)
return logfont.lfHeight;
int logfont_lfHeight = 0;
for (;;)
{
TEXTMETRIC tm;
memset(&tm, 0, sizeof(tm));
bool bHaveTextMetric = ON_Font::GetWindowsTextMetrics(
map_mode,
hdc,
logfont,
tm
);
if (false == bHaveTextMetric)
{
if (ON_Font::MAP_MODE_ZERO_ERROR_SUPPRESS != map_mode)
{
ON_ERROR("ON_Font::GetWindowsTextMetrics...) failed.");
}
break;
}
const int cellHeight = tm.tmAscent + tm.tmDescent;
const int characterHeight = cellHeight - tm.tmInternalLeading;
if (cellHeight <= 0 || characterHeight <= 0)
{
ON_ERROR("tm.tmInternalLeading >= cellHeight.");
break;
}
// logfont_lfHeight is negative to indicate it is character height.
// (positive values are cell height)
logfont_lfHeight = -characterHeight;
break;
}
return logfont_lfHeight;
}
int ON_Font::WindowsLogfontCellHeight(
int map_mode,
HDC hdc,
const LOGFONT& logfont
)
{
if (logfont.lfHeight >= 0)
return logfont.lfHeight;
int logfont_lfHeight = 0;
for (;;)
{
TEXTMETRIC tm;
memset(&tm, 0, sizeof(tm));
bool bHaveTextMetric = ON_Font::GetWindowsTextMetrics(
map_mode,
hdc,
logfont,
tm
);
if (false == bHaveTextMetric)
{
ON_ERROR("ON_Font::GetWindowsTextMetrics...) failed.");
break;
}
const int cellHeight = tm.tmAscent + tm.tmDescent;
if (cellHeight <= 0)
{
ON_ERROR("tm.tmAscent + tm.tmDescent < 0.");
break;
}
// logfont_lfHeight is positive to indicate it is cell height.
// (negative values are character height)
logfont_lfHeight = cellHeight;
break;
}
return logfont_lfHeight;
}
int ON_Font::WindowsLogfontCharacterHeightFromPointSize(
int map_mode,
HDC hdc,
double point_size
)
{
int logfont_lfHeight = 0;
const bool bValidMapModeParameter = (map_mode >= MM_MIN && map_mode <= MM_MAX);
const bool bNeedDeviceContext = (false == bValidMapModeParameter || MM_TEXT == map_mode);
HDC font_hdc = nullptr;
for (;;)
{
if (0 == point_size)
break;
if (!(point_size > 0.0 && point_size < 21474836.47))
{
ON_ERROR("Invalid point_size parameter.");
break;
}
if (bNeedDeviceContext && nullptr == hdc)
{
font_hdc = ON_Font::CreateWindowsLogfontDeviceContext();
hdc = font_hdc;
if (nullptr == hdc)
break;
}
const int mm
= bValidMapModeParameter
? map_mode
: ((nullptr != hdc) ? ::GetMapMode(hdc) : 0);
if (mm < MM_MIN || mm > MM_MAX)
{
ON_ERROR("GetMapMode(hdc) failed.");
break;
}
double points_to_lfHeight = 0.0;
switch (mm)
{
case MM_TEXT:
// Each logical unit is mapped to one device pixel. Positive x is to the right; positive y is down.
if(nullptr != hdc)
{
const int device_pixels_per_logical_inch = ::GetDeviceCaps(hdc, LOGPIXELSY);
if (device_pixels_per_logical_inch <= 0)
{
ON_ERROR("GetDeviceCaps(hdc, LOGPIXELSY) failed.");
break;
}
double device_to_logical_scale = 1.0;
if (false == ON_Font::GetWindowsDeviceToLogicalHeightScales(hdc, &device_to_logical_scale, nullptr) )
{
ON_ERROR("ON_Font::GetWindowsDeviceToLogicalHeightScales(hdc, ...) failed.");
break;
}
// MM_TEXT points_to_lfHeight units
// = ((device pixel height)/(logical inch)) * (logical pixel height) / ( (points/inch)*(device pixel height) )
// = ( (logical pixel height) / points ) * (inch / (logical inch))
points_to_lfHeight = ((static_cast<double>(device_pixels_per_logical_inch)*device_to_logical_scale)) / 72.0;
}
break;
case MM_LOMETRIC:
// Each logical unit is mapped to 0.1 millimeter. Positive x is to the right; positive y is up.
// (254 tenths of a mm/inch)/(72 points/inch)
points_to_lfHeight = 254.0/72.0;
break;
case MM_HIMETRIC:
// Each logical unit is mapped to 0.01 millimeter. Positive x is to the right; positive y is up.
// (2540 hundredths of a mm/inch)/(72 points/inch)
points_to_lfHeight = 2540.0/72.0;
break;
case MM_LOENGLISH:
// Each logical unit is mapped to 0.01 inch. Positive x is to the right; positive y is up.
// (100 hundredths of a inch/inch)/(72 points/inch)
points_to_lfHeight = 1.0/0.72;
break;
case MM_HIENGLISH:
// Each logical unit is mapped to 0.001 inch. Positive x is to the right; positive y is up.
// (1000 thousandths of a inch/inch)/(72 points/inch)
points_to_lfHeight = 1.0/0.072;
break;
case MM_TWIPS:
// Each logical unit is mapped to one twentieth of a printer's point (1/1440 inch, also called a "twip"). Positive x is to the right; positive y is up.
// 20 twips / point
points_to_lfHeight = 20.0;
break;
case MM_ISOTROPIC:
// Logical units are mapped to arbitrary units with equally scaled axes;
// that is, one unit along the x-axis is equal to one unit along the y-axis.
// Use the SetWindowExtEx and SetViewportExtEx functions to specify the units
// and the orientation of the axes. Graphics device interface makes adjustments
// as necessary to ensure the x and y units remain the same size.
// (When the windows extent is set, the viewport will be adjusted to keep
// the units isotropic).
ON_ERROR("GetMapMode(hdc) returned MM_ISOTROPIC mapping mode.");
break;
case MM_ANISOTROPIC:
// Logical units are mapped to arbitrary units with arbitrarily scaled axes.
// Use the SetWindowExtEx and SetViewportExtEx functions to specify the units,
// orientation, and scaling required.
ON_ERROR("GetMapMode(hdc) returned MM_ANISOTROPIC mapping mode.");
break;
default:
ON_ERROR("GetMapMode(hdc) returned undocumented mapping mode.");
}
if (0 != points_to_lfHeight)
{
logfont_lfHeight = -static_cast<int>(fabs(points_to_lfHeight*point_size + 0.5));
if (0 == logfont_lfHeight)
logfont_lfHeight = -1;
}
break;
}
if (nullptr != font_hdc)
{
ON_Font::DeleteWindowsLogfontDeviceContext(font_hdc);
}
return logfont_lfHeight;
}
double ON_Font::PointSizeFromWindowsLogfontCharacterHeight(
int map_mode,
HDC hdc,
int logfont_character_height
)
{
double point_size = 0.0;
const bool bValidMapModeParameter = (map_mode >= MM_MIN && map_mode <= MM_MAX);
const bool bNeedDeviceContext = (false == bValidMapModeParameter || MM_TEXT == map_mode);
HDC font_hdc = 0;
// Windows LOGFONT.lfHeight is negative when
// the value is a character height. This
// function allow the user to pass in a positive value.
const int logfont_lfHeight
= (logfont_character_height <= 0)
? logfont_character_height
: -logfont_character_height;
for (;;)
{
if (logfont_lfHeight >= 0)
break;
if (bNeedDeviceContext && nullptr == hdc )
{
font_hdc = ON_Font::CreateWindowsLogfontDeviceContext();
hdc = font_hdc;
if (nullptr == hdc)
break;
}
const int mm
= bValidMapModeParameter
? map_mode
: ((nullptr != hdc) ? ::GetMapMode(hdc) : 0);
if (mm < MM_MIN || mm > MM_MAX)
{
ON_ERROR("GetMapMode(hdc) failed.");
break;
}
double lfHeight_to_points = 0.0;
switch (mm)
{
case MM_TEXT:
// Each logical unit is mapped to one device pixel. Positive x is to the right; positive y is down.
if(nullptr != hdc)
{
const int device_pixels_per_logical_inch = ::GetDeviceCaps(hdc, LOGPIXELSY);
if (device_pixels_per_logical_inch <= 0)
{
ON_ERROR("GetDeviceCaps(hdc, LOGPIXELSY) failed.");
break;
}
double logical_to_device_scale = 1.0;
if (false == ON_Font::GetWindowsDeviceToLogicalHeightScales(hdc, nullptr, &logical_to_device_scale))
{
ON_ERROR("ON_Font::GetWindowsDeviceToLogicalHeightScales(hdc, ...) failed.");
break;
}
// MM_TEXT lfHeight_to_points units
// = ( (points/inch) * (device pixel height) ) / ( ((device pixel height)/(logical inch)) * (logical pixel height) )
// = ( points / (logical pixel height) ) * ( (logical inch)/inch )
lfHeight_to_points = (72.0*logical_to_device_scale) / static_cast<double>(device_pixels_per_logical_inch);
}
break;
case MM_LOMETRIC:
// Each logical unit is mapped to 0.1 millimeter. Positive x is to the right; positive y is up.
// 72 points/inch / (254 tenths of a mm/inch)
lfHeight_to_points = 72.0 / 254.0;
break;
case MM_HIMETRIC:
// Each logical unit is mapped to 0.01 millimeter. Positive x is to the right; positive y is up.
// 72 points/inch / (2540 hundredths of a mm/inch)
lfHeight_to_points = 72.0 / 2540.0;
break;
case MM_LOENGLISH:
// Each logical unit is mapped to 0.01 inch. Positive x is to the right; positive y is up.
// 72 points/inch / (100 hundredths of a inch/inch)
lfHeight_to_points = 0.72;
break;
case MM_HIENGLISH:
// Each logical unit is mapped to 0.001 inch. Positive x is to the right; positive y is up.
// 72 points/inch / (1000 thousandths of a inch/inch)
lfHeight_to_points = 0.072;
break;
case MM_TWIPS:
// Each logical unit is mapped to one twentieth of a printer's point (1/1440 inch, also called a "twip"). Positive x is to the right; positive y is up.
// 1 point / 20 twips
lfHeight_to_points = 1.0/20.0;
break;
case MM_ISOTROPIC:
// Logical units are mapped to arbitrary units with equally scaled axes;
// that is, one unit along the x-axis is equal to one unit along the y-axis.
// Use the SetWindowExtEx and SetViewportExtEx functions to specify the units
// and the orientation of the axes. Graphics device interface makes adjustments
// as necessary to ensure the x and y units remain the same size.
// (When the windows extent is set, the viewport will be adjusted to keep
// the units isotropic).
ON_ERROR("GetMapMode(hdc) returned MM_ISOTROPIC mapping mode.");
break;
case MM_ANISOTROPIC:
// Logical units are mapped to arbitrary units with arbitrarily scaled axes.
// Use the SetWindowExtEx and SetViewportExtEx functions to specify the units,
// orientation, and scaling required.
ON_ERROR("GetMapMode(hdc) returned MM_ANISOTROPIC mapping mode.");
break;
default:
ON_ERROR("GetMapMode(hdc) returned undocumented mapping mode.");
}
point_size = fabs(lfHeight_to_points*static_cast<double>(logfont_lfHeight));
for (;;)
{
// Because LOGFONT lfHeight is an integer and point_size is typically
// an integer and sometimes a multiple of 0.5, the following rouding
// is performed so point_size will round trip.
double i0 = floor(point_size);
if (i0 == point_size)
break;
if (lfHeight_to_points < 0.5 && 2.0*point_size == floor(2.0*point_size))
break;
double i1 = ceil(point_size);
if (i1 >= 1.0)
{
if (i0 >= 1.0 && fabs(i0 - point_size) < fabs(i1 - point_size) && fabs(i0 - point_size) < lfHeight_to_points)
{
if (logfont_lfHeight == ON_Font::WindowsLogfontCharacterHeightFromPointSize(map_mode, hdc, i0))
{
point_size = i0;
break;
}
}
else if (fabs(i1 - point_size) < fabs(i0 - point_size) && fabs(i1 - point_size) < lfHeight_to_points)
{
if (logfont_lfHeight == ON_Font::WindowsLogfontCharacterHeightFromPointSize(map_mode, hdc, i1))
{
point_size = i1;
break;
}
}
if (lfHeight_to_points < 0.25 && fabs(point_size - 0.5*(i0 + i1)) < lfHeight_to_points)
{
if (logfont_lfHeight == ON_Font::WindowsLogfontCharacterHeightFromPointSize(map_mode, hdc, 0.5*(i0 + i1)))
{
point_size = 0.5*(i0 + i1);
break;
}
}
}
break;
}
break;
}
if (nullptr != font_hdc)
{
ON_Font::DeleteWindowsLogfontDeviceContext(font_hdc);
}
return point_size;
}
bool ON_Font::SetFromWindowsLogFont(
int map_mode,
HDC hdc,
const LOGFONT& dirty_logfont
)
{
if ( false == ON_FONT_MODIFICATION_PERMITTED )
return false;
*this = ON_Font::Unset;
const bool bUnderlined = (0 != dirty_logfont.lfUnderline) ? true : false;
const bool bStrikethrough = (0 != dirty_logfont.lfStrikeOut) ? true : false;
LOGFONT logfont = dirty_logfont;
logfont.lfWidth = 0;
logfont.lfEscapement = 0;
logfont.lfOrientation = 0;
logfont.lfUnderline = 0;
logfont.lfStrikeOut = 0;
logfont.lfCharSet = ON_Font::WindowsLogfontCharSetFromLogfont(&logfont, true);
logfont.lfOutPrecision = ON_Font::WindowsConstants::logfont_out_precis;
logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
logfont.lfQuality = ON_Font::WindowsConstants::logfont_quality;
logfont.lfPitchAndFamily = ON_Font::WindowsConstants::logfont_pitch_and_family;
int face_name_capacity = (int)((sizeof(logfont.lfFaceName) / sizeof(logfont.lfFaceName[0])));
for (int i = 0; i < face_name_capacity; i++)
{
if (0 == logfont.lfFaceName[i])
{
for (i++; i < face_name_capacity; i++)
logfont.lfFaceName[i] = 0;
}
}
logfont.lfFaceName[face_name_capacity-1] = 0;
// Calculate point size
// If logfont.lfHeight > 0, +lfHeight = cell height.
// If logfont.lfHeight < 0, -lfHeight = character height = cell height - TEXTMETRIC.tmInternalLeading.
// PointSize() calculations require a character height.
const int logfont_lfHeight
= (0 != logfont.lfHeight)
? ON_Font::WindowsLogfontCharacterHeight(map_mode, hdc, logfont)
: 0;
const double point_size
= (logfont_lfHeight < 0)
? ON_Font::PointSizeFromWindowsLogfontCharacterHeight(map_mode, hdc, logfont_lfHeight)
: 0.0;
logfont.lfHeight = ON_Font::Constants::AnnotationFontCellHeight;
// TODO - use DWrite gdi interop to get an IDWriteFont and use it to set the ON_Font.
const ON_Font::Weight font_weight = ON_Font::WeightFromWindowsLogfontWeight(logfont.lfWeight);
const ON_Font::Style font_style = (0 != logfont.lfItalic) ? ON_Font::Style::Italic : ON_Font::Style::Upright;
const ON_Font::Stretch font_stretch = ON_Font::Stretch::Medium;
const double linefeed_ratio = ON_FontMetrics::DefaultLineFeedRatio;
bool rc = SetFontCharacteristics(
point_size,
logfont.lfFaceName,
font_weight,
font_style,
font_stretch,
bUnderlined,
bStrikethrough,
linefeed_ratio,
logfont.lfCharSet
);
if (rc)
{
rc = Internal_SetFontWeightTrio(
font_weight,
logfont.lfWeight,
ON_UNSET_VALUE,
false
);
}
// Do last because other setters sometimes unset the orgin.
SetFontOrigin(ON_Font::Origin::WindowsFont);
return rc;
}
const LOGFONT ON_Font::WindowsLogFont(
int map_mode,
HDC hdc
) const
{
LOGFONT logfont;
memset(&logfont, 0, sizeof(LOGFONT));
logfont.lfHeight
= (m_point_size > 0.0)
? ON_Font::WindowsLogfontCharacterHeightFromPointSize(map_mode,hdc,m_point_size)
: ON_Font::Constants::AnnotationFontCellHeight;
logfont.lfWidth = 0;
logfont.lfEscapement = 0;
logfont.lfOrientation = 0;
const ON_Font::Weight font_weight_from_windows_logfont_weight = ON_Font::WeightFromWindowsLogfontWeight(m_windows_logfont_weight);
logfont.lfWeight
= (m_windows_logfont_weight > 0 && m_windows_logfont_weight <= 1000 && font_weight_from_windows_logfont_weight == m_font_weight)
? m_windows_logfont_weight
: ON_Font::WindowsLogfontWeightFromWeight(m_font_weight);
logfont.lfItalic = (ON_Font::Style::Italic == m_font_style) ? 1 : 0;
logfont.lfUnderline = m_font_bUnderlined ? 1 : 0;
logfont.lfStrikeOut = m_font_bStrikethrough ? 1 : 0;
logfont.lfCharSet = m_logfont_charset;
logfont.lfOutPrecision = ON_Font::WindowsConstants::logfont_out_precis;
logfont.lfClipPrecision = 0;
logfont.lfQuality = ON_Font::WindowsConstants::logfont_quality;
logfont.lfPitchAndFamily = ON_Font::WindowsConstants::logfont_pitch_and_family;
const ON_wString windows_logfont_name = WindowsLogfontName();
const int windows_logfont_name_len = windows_logfont_name.Length();
const wchar_t* s = static_cast<const wchar_t*>(windows_logfont_name);
for(int i = 0; i < windows_logfont_name_len && i < LF_FACESIZE; i++)
logfont.lfFaceName[i] = s[i];
logfont.lfFaceName[LF_FACESIZE - 1] = 0;
return logfont;
}
const MAT2 ON_Font::WindowsFontMat2() const
{
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;
double eM11 = 1.0;
switch (FontStretch())
{
case ON_Font::Stretch::Ultracondensed:
eM11 = 0.2;
break;
case ON_Font::Stretch::Extracondensed:
eM11 = 0.3;
break;
case ON_Font::Stretch::Condensed:
eM11 = 0.5;
break;
case ON_Font::Stretch::Semicondensed:
eM11 = 0.75;
break;
case ON_Font::Stretch::Medium:
eM11 = 1.0;
break;
case ON_Font::Stretch::Semiexpanded:
eM11 = 1.25;
break;
case ON_Font::Stretch::Expanded:
eM11 = 1.5;
break;
case ON_Font::Stretch::Extraexpanded:
eM11 = 2.0;
break;
case ON_Font::Stretch::Ultraexpanded:
eM11 = 3.0;
break;
}
if (eM11 > 0.0 && 1.0 != eM11)
{
FIXED fx = { 1, 0 };
double ffrac = fmod(eM11, 1.0);
if (ffrac != ffrac)
ffrac = 0.0;
fx.fract = static_cast<unsigned short>(floor(65535.0 * ffrac));
fx.value = static_cast<short>(eM11 - ffrac);
mat2.eM11 = fx;
}
return mat2;
}
#endif
void ON_Font::DestroyFontGlyphCache()
{
m_font_glyph_cache.reset();
}
class ON_FontGlyphCache* ON_Font::FontGlyphCache(
bool bCreateIfMissing
) const
{
ON_FontGlyphCache* font_cache = m_font_glyph_cache.get();
if(nullptr == font_cache)
{
// get the static ON_Font that matches this one
const ON_Font* managed_font = this->ManagedFont();
if(nullptr != managed_font)
{
// The first call to ON_Font::GetManagedFont() adds
// &ON_Font::Default and calculates its font metric
// and glyph information. The this != managed_font
// text below prevents a silly copy in this case
// and in other unanticipated cases.
if ( this != managed_font)
m_font_glyph_cache = managed_font->m_font_glyph_cache;
font_cache = m_font_glyph_cache.get();
}
}
return font_cache;
}
// static
void ON_Font::GetRunBounds(
const ON_Font& font,
const wchar_t* text,
double font_height_pixels,
ON::TextHorizontalAlignment horizontal_alignment,
ON::TextVerticalAlignment vertical_alignment,
ON_2dPoint& bounds_min,
ON_2dPoint& bounds_max,
int& line_count
)
{
line_count = 0;
bounds_min = ON_2dPoint::Origin;
bounds_max = ON_2dPoint::Origin;
ON_wString run = text;
int textlength = run.Length();
if(textlength < 1)
return;
const ON_Font* managed_font = font.ManagedFont();
if (nullptr == managed_font)
managed_font = &ON_Font::Default;
const ON_FontMetrics& fm = managed_font->FontMetrics();
const int height_of_I = fm.AscentOfCapital();
const int height_of_LF = fm.LineSpace();
ON_TextBox text_box;
line_count = ON_FontGlyph::GetGlyphListBoundingBox(text, managed_font, text_box);
if ( line_count < 0 || false == text_box.IsSet())
return;
bounds_min.x = text_box.m_bbmin.i;
bounds_min.y = text_box.m_bbmin.j;
bounds_max.x = text_box.m_bbmax.i;
bounds_max.y = text_box.m_bbmax.j;
// in text_box coordinate system (0.0) = left most baseline point of first line.
const ON_2dPoint first_line_basepoint = ON_2dPoint::Origin;
const ON_2dPoint last_line_basepoint(first_line_basepoint.x, -(line_count - 1)*height_of_LF);
ON_2dVector offset = ON_2dVector::ZeroVector;
switch (horizontal_alignment)
{
case ON::TextHorizontalAlignment::Left:
offset.x = -first_line_basepoint.x;
break;
case ON::TextHorizontalAlignment::Center:
// Text command uses m_max_basepoint (not bbmax)
offset.x = -first_line_basepoint.x - 0.5*text_box.m_max_basepoint.i;
break;
case ON::TextHorizontalAlignment::Right:
// Text command uses m_max_basepoint (not bbmax)
offset.x = -first_line_basepoint.x - text_box.m_max_basepoint.i;
break;
default:
break;
}
switch (vertical_alignment)
{
case ON::TextVerticalAlignment::Top:
// (*,0) is at the top of an "I" on the first line.
offset.y = -first_line_basepoint.y - height_of_I;
break;
case ON::TextVerticalAlignment::MiddleOfTop:
// (*,0) is at the middle of an "I" on the first line.
offset.y = -first_line_basepoint.y - 0.5*height_of_I;
break;
case ON::TextVerticalAlignment::BottomOfTop:
// (*,0) is at the baseline of the first line.
offset.y = -first_line_basepoint.y;
break;
case ON::TextVerticalAlignment::Middle:
{
// (*,0) is the vertical middle of lines (not vertical middle of glyph bounding box)
double h = (line_count - 1)*height_of_LF + height_of_I;
offset.y = -last_line_basepoint.y + 0.5*h;
}
break;
case ON::TextVerticalAlignment::MiddleOfBottom:
// (*,0) is at the middle of an "I" on the last line.
offset.y = -last_line_basepoint.y - 0.5*height_of_I;
break;
case ON::TextVerticalAlignment::Bottom:
// (*,0) is at the basline of the last line.
offset.y = -last_line_basepoint.y;
break;
case ON::TextVerticalAlignment::BottomOfBoundingBox:
// (*,0) is on the bottom the the glyphs' bounding box
offset.y = -bounds_min.y;
break;
default:
break;
}
bounds_min += offset;
bounds_max += offset;
// scale is applied last.
const double scale = font_height_pixels / (double)ON_Font::Constants::AnnotationFontCellHeight;
if (scale > 0.0)
{
bounds_min.x = scale * text_box.m_bbmin.i;
bounds_max.x = scale * text_box.m_bbmax.i;
bounds_min.y = scale * text_box.m_bbmin.j;
bounds_max.y = scale * text_box.m_bbmax.j;
}
}
const ON_FontGlyph* ON_Font::Internal_ManagedCodePointGlyph(
ON__UINT32 unicode_code_point,
bool bCreateIfMissing,
bool bFindSubstitutes
) const
{
if ( false == ON_IsValidUnicodeCodePoint(unicode_code_point) )
{
// valid UNICODE codepoint values are <= 0x10FFFF
ON_ERROR("invalid codepoint parameter.");
return nullptr;
}
if (false == IsManagedFont())
{
ON_ERROR("this->IsManagedFont() must be true.");
return nullptr;
}
// First see if we already have the glyph in the cache
ON_FontGlyphCache* font_cache = FontGlyphCache(true);
ON_GlyphMap* glyph_map
= (nullptr == font_cache)
? nullptr
: font_cache->m_glyphmap.get();
if (nullptr == glyph_map)
{
ON_ERROR("Unable to create ON_FontGlyphCache.");
return nullptr;
}
const ON_FontGlyph* managed_glyph = glyph_map->FindGlyph(unicode_code_point);
if (nullptr != managed_glyph)
return managed_glyph;
if (false == bCreateIfMissing)
return nullptr;
ON_FontGlyph glyph;
glyph.SetCodePoint(this, unicode_code_point);
// Call external function to get glyph details
ON_TextBox font_unit_glyph_box = ON_TextBox::Unset;
const unsigned int font_glyph_index = ON_ManagedFonts::GetGlyphMetricsInFontDesignUnits(this, unicode_code_point, font_unit_glyph_box);
if (0 != font_glyph_index)
{
if (font_unit_glyph_box.IsSet())
{
glyph.m_font_unit_glyph_bbox = font_unit_glyph_box;
// Save a normalized box as well.
const double normalize_scale = this->FontUnitToNormalizedScale();
ON_TextBox normalized_glyph_box = font_unit_glyph_box;
if (normalize_scale > 0.0)
normalized_glyph_box = ON_TextBox::Scale(font_unit_glyph_box, normalize_scale);
glyph.m_normalized_glyph_bbox = normalized_glyph_box;
}
glyph.Internal_SetFontGlyphIndex(font_glyph_index);
bFindSubstitutes = false;
}
return glyph_map->InsertGlyph(glyph);
}
#if defined(ON_RUNTIME_WIN)
static bool ON_Internal_WindowsGetGlyphMetricsEx(
const ON_Font* font,
int logfont_height,
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 (nullptr == font)
return false;
font = font->ManagedFont();
if (nullptr == font)
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 = font->WindowsLogFont(0,nullptr);
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;
}
GLYPHMETRICS glm;
memset(&glm, 0, sizeof(glm));
MAT2 mat2 = font->WindowsFontMat2();
const DWORD cb_size = ::GetGlyphOutlineW(font_hdc, glyphindex[0], GGO_NATIVE | GGO_GLYPH_INDEX, &glm, 0, nullptr, &mat2);
if (GDI_ERROR == cb_size)
break;
////bool ul = false, so = false;
////int ulsize = 0, ulpos = 0, sosize = 0, sopos = 0;
////HGDIOBJ hfont = GetCurrentObject(hdc, OBJ_FONT);
////LOGFONT lf = { 0 };
////if (sizeof(lf) == GetObject(hfont, sizeof(lf), &lf))
////{
//// ul = lf.lfUnderline ? true : false;
//// so = lf.lfStrikeOut ? true : false;
////}
////if (ul || so)
////{
//// OUTLINETEXTMETRIC otm = { 0 };
//// if (0 != ::GetOutlineTextMetrics(hdc, sizeof(OUTLINETEXTMETRIC), &otm))
//// {
//// sosize = otm.otmsStrikeoutSize;
//// sopos = otm.otmsStrikeoutPosition;
//// ulsize = otm.otmsUnderscoreSize;
//// ulpos = otm.otmsUnderscorePosition;
//// }
////}
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();
}
bool ON_WindowsGetGlyphMetrics(
const ON_Font* font,
ON__UINT32 unicode_code_point,
class ON_TextBox& font_unit_glyph_box
)
{
return ON_Internal_WindowsGetGlyphMetricsEx(font, 0, unicode_code_point, font_unit_glyph_box);
}
#endif
void ON_Font::SetCustomMeasurementFunctions(
ON_GetGlyphMetricsFuncType measureGlyphFunc,
ON_GetFontMetricsFuncType metricsFunc
)
{
ON_Font::Internal_CustomGetGlyphMetricsFunc = measureGlyphFunc;
ON_Font::Internal_CustomGetFontMetricsFunc = metricsFunc;
}
unsigned int ON_ManagedFonts::GetGlyphMetricsInFontDesignUnits(
const class ON_Font* font,
ON__UINT32 unicode_code_point,
class ON_TextBox& glyph_metrics_in_font_design_units
)
{
unsigned int glyph_index = 0;
const ON_FontGlyph g(font, unicode_code_point);
if (nullptr != ON_Font::Internal_CustomGetGlyphMetricsFunc)
{
glyph_index = ON_Font::Internal_CustomGetGlyphMetricsFunc(&g, glyph_metrics_in_font_design_units);
if (0 != glyph_index)
return glyph_index;
}
#if defined(ON_OS_WINDOWS_GDI)
glyph_index = ON_WindowsDWriteGetGlyphMetrics(&g, glyph_metrics_in_font_design_units);
#elif defined(ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE)
glyph_index = ON_AppleFontGetGlyphMetrics(&g, glyph_metrics_in_font_design_units);
#elif defined(OPENNURBS_FREETYPE_SUPPORT)
// Look in opennurbs_system_rumtime.h for the correct place to define OPENNURBS_FREETYPE_SUPPORT.
// Do NOT define OPENNURBS_FREETYPE_SUPPORT here or in your project setting ("makefile").
glyph_index = ON_FreeTypeGetGlyphMetrics(&g, glyph_metrics_in_font_design_units);
#endif
if (0 != glyph_index)
return glyph_index;
glyph_metrics_in_font_design_units = ON_TextBox::Unset;
return 0;
}
bool ON_ManagedFonts::GetFontMetricsInFontDesignUnits(
const ON_Font* font,
ON_FontMetrics& font_metrics_in_font_design_units
)
{
if (nullptr == font)
{
font_metrics_in_font_design_units = ON_FontMetrics::LastResortMetrics;
return false;
}
const ON_FontGlyphCache* font_glyph_cache = font->m_font_glyph_cache.get();
if (nullptr != font_glyph_cache && font_glyph_cache->m_font_unit_metrics.HeightsAreValid())
{
if (font_glyph_cache->m_font_unit_metrics.HeightsAreValid())
{
font_metrics_in_font_design_units = font_glyph_cache->m_font_unit_metrics;
return true;
}
}
ON_FontMetrics fm(ON_FontMetrics::Unset);
for (;;)
{
if (nullptr != ON_Font::Internal_CustomGetFontMetricsFunc)
{
ON_Font::Internal_CustomGetFontMetricsFunc(font, fm);
if (fm.AscentDescentAndUPMAreValid())
break;
}
#if defined(ON_OS_WINDOWS_GDI)
ON_WindowsDWriteGetFontMetrics(font, fm);
#elif defined(ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE)
ON_AppleFontGetFontMetrics(font, fm);
#elif defined(OPENNURBS_FREETYPE_SUPPORT)
// Look in opennurbs_system_rumtime.h for the correct place to define OPENNURBS_FREETYPE_SUPPORT.
// Do NOT define OPENNURBS_FREETYPE_SUPPORT here or in your project setting ("makefile").
ON_FreeTypeGetFontMetrics(font, fm);
#endif
break;
}
if (false == fm.AscentDescentAndUPMAreValid())
{
font_metrics_in_font_design_units = ON_FontMetrics::LastResortMetrics;
return false;
}
if (fm.HeightsAreValid() && (fm.AscentOfCapital() > 0 || fm.Ascent() <= 0))
{
font_metrics_in_font_design_units = fm;
return true;
}
const int UPM = fm.UPM();
const int ascent = fm.Ascent();
const int descent = fm.Descent();
const int line_space =
(fm.LineSpace() >= (ascent - descent) && fm.LineSpace() < 0xFFFF)
? fm.LineSpace()
: (int)ceil(ON_FontMetrics::DefaultLineFeedRatio*(ascent - descent));
const int ascent_of_capital = fm.AscentOfCapital();
const int ascent_of_x = fm.AscentOfx();
fm.SetHeights(ascent, descent, UPM, line_space);
fm.SetAscentOfCapital((ascent_of_capital > 0 && ascent_of_capital <= ascent) ? ascent_of_capital : ascent);
fm.SetAscentOfx((ascent_of_x > 0 && ascent_of_x <= ascent) ? ascent_of_x : 0);
font_metrics_in_font_design_units
= (fm.HeightsAreValid())
? fm
: ON_FontMetrics::LastResortMetrics;
return false;
}
void ON_ManagedFonts::Internal_SetFakeWindowsLogfontName(
const ON_Font * font,
const ON_wString fake_loc_logfont_name,
const ON_wString fake_en_logfont_name
)
{
if (nullptr == font)
return;
const_cast<ON_Font * >(font)->m_loc_windows_logfont_name = fake_loc_logfont_name;
const_cast<ON_Font * >(font)->m_en_windows_logfont_name = fake_en_logfont_name;
}
void ON_ManagedFonts::Internal_SetFakeWindowsLogfontNames(
ON_SimpleArray<const ON_Font*>& device_list
)
{
// Windows divides font families (which can have many faces)
// into LOGFONT subsets identified by a logfont name.
// A LOGFONT subset can have at most 4 faces and typically
// they are regular/bold/italic/bold-italic variations of
// a face and all have matching stretch.
//
// This all goes back to the 1970's and early user interfaces
// that selected fonts based on a name, bold=true/false, italic=true/false
// style inteface. This type of archaic font selection interface is way
// too simple to accomodate the rich font families are currently in use.
// Newer font families can have dozens of faces with severeal weights,
// several widths, and an upright and italic version of each of these.
// However, that does not prevent applications like Rhino 6/7
// from attempting to use the inadequate and archic bold/italic font
// selection interface to select a font from a rich list of modern fonts.
//
// This function is used on non Windows platforms to partition
// the installed font families into subsets with the same
// width and set a unique fake LOGFONT name. Later sorting will
// choose up to four faces from each subset to use as the
// regular/bold/italic/bold-italic representatives.
// Assign a fake logfont name.
const unsigned int font_count = device_list.UnsignedCount();
for (unsigned int i = 0; i < font_count; ++i)
{
const ON_Font* f0 = device_list[i];
if (nullptr == f0)
continue;
ON_wString fake_loc_logfont_name = ON_Font::FakeWindowsLogfontNameFromFamilyAndPostScriptNames(
f0->FamilyName(ON_Font::NameLocale::LocalizedFirst),
f0->PostScriptName(ON_Font::NameLocale::LocalizedFirst)
);
if (fake_loc_logfont_name.IsEmpty())
continue;
ON_wString fake_en_logfont_name = ON_Font::FakeWindowsLogfontNameFromFamilyAndPostScriptNames(
f0->FamilyName(ON_Font::NameLocale::English),
f0->PostScriptName(ON_Font::NameLocale::English)
);
if (fake_en_logfont_name.IsEmpty())
fake_en_logfont_name = fake_loc_logfont_name;
else if (false == fake_loc_logfont_name.EqualOrdinal(fake_en_logfont_name, true))
{
const bool bLocalFaceIsFamilyName = fake_loc_logfont_name.EqualOrdinal(f0->FamilyName(ON_Font::NameLocale::LocalizedFirst), true);
const bool bEnglishFaceIsFamilyName = fake_en_logfont_name.EqualOrdinal(f0->FamilyName(ON_Font::NameLocale::English), true);
if (bLocalFaceIsFamilyName && false == bEnglishFaceIsFamilyName)
fake_loc_logfont_name = fake_en_logfont_name;
else if (bEnglishFaceIsFamilyName && false == bLocalFaceIsFamilyName)
fake_en_logfont_name = fake_loc_logfont_name;
}
ON_ManagedFonts::Internal_SetFakeWindowsLogfontName(
f0,
fake_loc_logfont_name,
fake_en_logfont_name
);
}
// Sort device_list by font family and current fake LOGFONT name
device_list.QuickSort(ON_FontList::CompareFamilyAndWindowsLogfontName);
ON_SimpleArray<const ON_Font*> quartet_candidates(128);
bool bSortAgain = false;
for (unsigned int i = 0; i < font_count; ++i)
{
const ON_Font* f0 = device_list[i];
if (nullptr == f0)
continue;
quartet_candidates.SetCount(0);
quartet_candidates.Append(f0);
while( (i + 1) < font_count )
{
const ON_Font* f = device_list[i + 1];
if (nullptr == f || 0 != ON_FontList::CompareFamilyAndWindowsLogfontName(&f0, &f))
break;
quartet_candidates.Append(f);
++i;
}
const unsigned int quartet_candidate_count = quartet_candidates.UnsignedCount();
if (quartet_candidate_count < 2)
continue;
// members in a fake LOGFONT quartet should have the same stretch
quartet_candidates.QuickSort(ON_FontList::CompareStretch);
// See which subset of family gets to use the family name as
// the fake logfont name.
const int medium_stretch = (int)((unsigned)(ON_Font::Stretch::Medium));
ON_Font::Stretch stretch0 = quartet_candidates[0]->FontStretch();
int delta_stretch = abs(medium_stretch - ((int)((unsigned)(stretch0))));
bool bNeedToModifyFakeName = false;
for (unsigned int j = 0; j < quartet_candidate_count; ++j)
{
const ON_Font::Stretch stretch = quartet_candidates[j]->FontStretch();
int ds = abs(medium_stretch - ((int)((unsigned)(stretch))));
if (ds < delta_stretch)
{
stretch0 = stretch;
delta_stretch = ds;
bNeedToModifyFakeName = true;
}
}
if (false == bNeedToModifyFakeName)
continue; // we cannot use stretch to distinguish between family members
// If a family member's stretch is not stretch0, modify its names.
for (unsigned int j = 0; j < quartet_candidate_count; ++j)
{
const ON_Font* f = quartet_candidates[j];
const ON_Font::Stretch stretch = f->FontStretch();
if (stretch == stretch0)
continue;
ON_wString suffix = L" (";
suffix += ON_Font::StretchToWideString(stretch);
if (suffix.Length() < 3)
continue;
suffix += L")";
const ON_wString loc_name = f->m_loc_windows_logfont_name+suffix;
const ON_wString en_name = f->m_en_windows_logfont_name+suffix;
ON_ManagedFonts::Internal_SetFakeWindowsLogfontName(f,loc_name,en_name);
bSortAgain = true;
}
}
if (bSortAgain)
device_list.QuickSort(ON_FontList::CompareFamilyAndWindowsLogfontName);
}
const ON_FontList& ON_ManagedFonts::InstalledFonts()
{
if (0 == List.m_installed_fonts.Count())
{
ON_MemoryAllocationTracking disable_tracking(false);
ON_SimpleArray<const ON_Font*> device_list;
#if defined(ON_OS_WINDOWS_GDI)
ON_ManagedFonts::Internal_GetWindowsInstalledFonts(device_list);
#elif defined (ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE)
ON_ManagedFonts::Internal_GetAppleInstalledCTFonts(device_list);
ON_ManagedFonts::Internal_SetFakeWindowsLogfontNames(device_list);
#endif
if (device_list.Count() > 0)
{
// Cull exact duplicates which occur in fonts like SLF..., Noto Emoji", ...
const int count = device_list.Count();
ON_SimpleArray<int> index(count);
for (int i = 0; i < count; ++i)
index.Append(i);
ON_Sort(ON::sort_algorithm::quick_sort, index.Array(), device_list.Array(), device_list.Count(), sizeof(ON__UINT_PTR), (int (*)(const void*, const void*))ON_FontList::CompareFontCharacteristicsHash);
int j0 = index[0];
bool bCullNulls = false;
for (int i = 1; i < count; ++i)
{
int j1 = index[i];
if (j0 == j1)
continue;
const ON_Font* f0 = device_list[j0];
const ON_Font* f1 = device_list[j1];
if (0 == ON_FontList::CompareFontCharacteristicsHash(&f0, &f1))
{
// duplicate font - keep the first one the platform delivered.
if (j0 < j1)
{
device_list[j1] = nullptr;
delete const_cast<ON_Font*>(f1);
bCullNulls = true;
}
else if (j0 > j1)
{
device_list[j0] = nullptr;
delete const_cast<ON_Font*>(f0);
j0 = j1;
bCullNulls = true;
}
}
else
j0 = j1;
}
if (bCullNulls)
{
int count1 = 0;
for (int i = 0; i < count; ++i)
{
const ON_Font* f = device_list[i];
if (nullptr == f)
continue;
device_list[count1++] = f;
}
device_list.SetCount(count1);
}
List.m_installed_fonts.AddFonts(device_list);
List.m_installed_fonts.Internal_UpdateSortedLists();
}
}
if (List.m_installed_fonts.Count() > 0)
{
// Calling List.m_installed_fonts.QuartetList() sets ON_Font::m_quartet_member
// for all installled font on Apple and for damaged installed fonts on Windows.
// See comments near the end of ON_FontList::QuartetList().
List.m_installed_fonts.QuartetList();
}
return List.m_installed_fonts;
}