* adding native font descriptor serialization = v2 * remove common xml prefix from serialized string * Update src/osx/carbon/font.cpp Co-authored-by: VZ <vz-github@zeitlins.org> * Update src/osx/carbon/font.cpp Co-authored-by: VZ <vz-github@zeitlins.org> * static string via accessor * striping off xml preamble unconditionally if we use a different format in the future, we will have to increase our version number * applying italic directly to the font descriptor thus preserving attributes in the native font descriptor which we don’t store explicitly yet * Adding support for preserving font width Although we don’t express this in the public API yet, we try to preserve eg condensed, when changing the font width * Adding reference, bug fix double checked the font weight constants, Co-authored-by: VZ <vz-github@zeitlins.org>
1276 lines
33 KiB
C++
1276 lines
33 KiB
C++
/////////////////////////////////////////////////////////////////////////////
|
|
// Name: src/osx/carbon/font.cpp
|
|
// Purpose: wxFont class
|
|
// Author: Stefan Csomor
|
|
// Modified by:
|
|
// Created: 1998-01-01
|
|
// Copyright: (c) Stefan Csomor
|
|
// Licence: wxWindows licence
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "wx/wxprec.h"
|
|
|
|
#include "wx/font.h"
|
|
|
|
#ifndef WX_PRECOMP
|
|
#include "wx/string.h"
|
|
#include "wx/utils.h"
|
|
#include "wx/intl.h"
|
|
#include "wx/gdicmn.h"
|
|
#include "wx/log.h"
|
|
#include "wx/math.h"
|
|
#endif
|
|
|
|
#include "wx/fontutil.h"
|
|
#include "wx/graphics.h"
|
|
#include "wx/settings.h"
|
|
#include "wx/tokenzr.h"
|
|
|
|
#include "wx/osx/private.h"
|
|
#include "wx/osx/private/available.h"
|
|
|
|
#include <map>
|
|
#include <string>
|
|
|
|
#include <float.h> // for FLT_MAX
|
|
|
|
#define TRACE_CTFONT "ctfont"
|
|
|
|
class WXDLLEXPORT wxFontRefData : public wxGDIRefData
|
|
{
|
|
public:
|
|
wxFontRefData(const wxFontInfo& info = wxFontInfo());
|
|
|
|
wxFontRefData(const wxFontRefData& data);
|
|
|
|
wxFontRefData(const wxNativeFontInfo& info)
|
|
: m_info(info)
|
|
{
|
|
}
|
|
|
|
wxFontRefData(CTFontRef font);
|
|
|
|
double GetFractionalPointSize() const { return m_info.GetFractionalPointSize(); }
|
|
|
|
wxFontFamily GetFamily() const { return m_info.GetFamily(); }
|
|
|
|
wxFontStyle GetStyle() const { return m_info.GetStyle(); }
|
|
|
|
int GetNumericWeight() const { return m_info.GetNumericWeight(); }
|
|
|
|
bool GetUnderlined() const { return m_info.GetUnderlined(); }
|
|
|
|
bool GetStrikethrough() const { return m_info.GetStrikethrough(); }
|
|
|
|
wxString GetFaceName() const { return m_info.GetFaceName(); }
|
|
|
|
wxFontEncoding GetEncoding() const { return m_info.GetEncoding(); }
|
|
|
|
bool IsFixedWidth() const;
|
|
|
|
CTFontRef OSXGetCTFont() const;
|
|
|
|
CFDictionaryRef OSXGetCTFontAttributes() const;
|
|
|
|
CGFontRef OSXGetCGFont() const;
|
|
|
|
const wxNativeFontInfo& GetNativeFontInfo() const;
|
|
|
|
void SetFractionalPointSize(double size)
|
|
{
|
|
if (GetFractionalPointSize() != size)
|
|
{
|
|
m_info.SetFractionalPointSize(size);
|
|
Free();
|
|
}
|
|
}
|
|
|
|
void SetFamily(wxFontFamily family)
|
|
{
|
|
if (m_info.GetFamily() != family)
|
|
{
|
|
m_info.SetFamily(family);
|
|
Free();
|
|
}
|
|
}
|
|
|
|
void SetStyle(wxFontStyle style)
|
|
{
|
|
if (m_info.GetStyle() != style)
|
|
{
|
|
m_info.SetStyle(style);
|
|
Free();
|
|
}
|
|
}
|
|
|
|
void SetNumericWeight(int weight)
|
|
{
|
|
if (m_info.GetNumericWeight() != weight)
|
|
{
|
|
m_info.SetNumericWeight(weight);
|
|
Free();
|
|
}
|
|
}
|
|
|
|
void SetStrikethrough(bool s)
|
|
{
|
|
if (m_info.GetStrikethrough() != s)
|
|
{
|
|
m_info.SetStrikethrough(s);
|
|
Free();
|
|
}
|
|
}
|
|
|
|
void SetUnderlined(bool u)
|
|
{
|
|
if (m_info.GetUnderlined() != u)
|
|
{
|
|
m_info.SetUnderlined(u);
|
|
Free();
|
|
}
|
|
}
|
|
|
|
void SetFaceName(const wxString& facename)
|
|
{
|
|
if (m_info.GetFaceName() != facename)
|
|
{
|
|
m_info.SetFaceName(facename);
|
|
Free();
|
|
}
|
|
}
|
|
|
|
void SetEncoding(wxFontEncoding encoding)
|
|
{
|
|
if (m_info.GetEncoding() != encoding)
|
|
{
|
|
m_info.SetEncoding(encoding);
|
|
Free();
|
|
}
|
|
}
|
|
|
|
void Free();
|
|
|
|
void Alloc();
|
|
protected:
|
|
void SetFont(CTFontRef font);
|
|
void AllocIfNeeded() const;
|
|
|
|
wxCFRef<CTFontRef> m_ctFont;
|
|
wxCFMutableDictionaryRef m_ctFontAttributes;
|
|
wxCFRef<CGFontRef> m_cgFont;
|
|
wxNativeFontInfo m_info;
|
|
};
|
|
|
|
// ============================================================================
|
|
// implementation
|
|
// ============================================================================
|
|
|
|
namespace
|
|
{
|
|
const int kCTWeightsCount = 12;
|
|
static CGFloat gCTWeights[kCTWeightsCount] = {
|
|
-1.000, // 0
|
|
-0.800, // 100, NSFontWeightUltraLight
|
|
-0.600, // 200, NSFontWeightThin
|
|
-0.400, // 300, NSFontWeightLight
|
|
0.000, // 400, NSFontWeightRegular
|
|
0.230, // 500, NSFontWeightMedium
|
|
0.300, // 600, NSFontWeightSemibold
|
|
0.400, // 700, NSFontWeightBold
|
|
0.560, // 800, NSFontWeightHeavy
|
|
0.620, // 900, NSFontWeightBlack
|
|
0.750, // 1000
|
|
};
|
|
|
|
int CTWeightToWX(CGFloat weight)
|
|
{
|
|
for (int i = 0; i < kCTWeightsCount - 1; ++i)
|
|
{
|
|
if ( (weight - gCTWeights[i]) < (gCTWeights[i+1]-weight) )
|
|
return i * 100;
|
|
}
|
|
return 1000;
|
|
}
|
|
|
|
CGFloat WXWeightToCT(int w)
|
|
{
|
|
if (w < 0)
|
|
w = 0;
|
|
else if (w > 1000)
|
|
w = 1000;
|
|
|
|
return gCTWeights[w / 100];
|
|
}
|
|
|
|
wxString FamilyToFaceName(wxFontFamily family)
|
|
{
|
|
wxString faceName;
|
|
|
|
switch (family)
|
|
{
|
|
case wxFONTFAMILY_DEFAULT:
|
|
faceName = wxT("Lucida Grande");
|
|
break;
|
|
|
|
case wxFONTFAMILY_SCRIPT:
|
|
case wxFONTFAMILY_ROMAN:
|
|
case wxFONTFAMILY_DECORATIVE:
|
|
faceName = wxT("Times");
|
|
break;
|
|
|
|
case wxFONTFAMILY_SWISS:
|
|
faceName = wxT("Helvetica");
|
|
break;
|
|
|
|
case wxFONTFAMILY_MODERN:
|
|
case wxFONTFAMILY_TELETYPE:
|
|
faceName = wxT("Courier");
|
|
break;
|
|
|
|
default:
|
|
faceName = wxT("Times");
|
|
break;
|
|
}
|
|
|
|
return faceName;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// wxFontRefData
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#define M_FONTDATA ((wxFontRefData*)m_refData)
|
|
|
|
wxFontRefData::wxFontRefData(const wxFontRefData& data)
|
|
: wxGDIRefData()
|
|
, m_info(data.m_info)
|
|
{
|
|
m_ctFont = data.m_ctFont;
|
|
m_ctFontAttributes = data.m_ctFontAttributes;
|
|
m_cgFont = data.m_cgFont;
|
|
}
|
|
|
|
wxFontRefData::wxFontRefData(const wxFontInfo& info)
|
|
{
|
|
m_info.Init();
|
|
|
|
if ( info.HasFaceName() )
|
|
SetFaceName(info.GetFaceName());
|
|
else
|
|
SetFamily(info.GetFamily());
|
|
|
|
m_info.SetSizeOrDefault(info.GetFractionalPointSize());
|
|
SetNumericWeight(info.GetNumericWeight());
|
|
SetStyle(info.GetStyle());
|
|
SetUnderlined(info.IsUnderlined());
|
|
SetStrikethrough(info.IsStrikethrough());
|
|
SetEncoding(info.GetEncoding());
|
|
}
|
|
|
|
void wxFontRefData::Free()
|
|
{
|
|
m_ctFont.reset();
|
|
m_ctFontAttributes.reset();
|
|
m_cgFont.reset();
|
|
}
|
|
|
|
wxFontRefData::wxFontRefData(CTFontRef font)
|
|
{
|
|
SetFont(font);
|
|
m_info.InitFromFont(font);
|
|
}
|
|
|
|
void wxFontRefData::SetFont(CTFontRef font)
|
|
{
|
|
m_ctFont.reset(wxCFRetain(font));
|
|
|
|
wxCFMutableDictionaryRef dict;
|
|
dict.SetValue(kCTFontAttributeName, m_ctFont.get());
|
|
dict.SetValue(kCTForegroundColorFromContextAttributeName, kCFBooleanTrue);
|
|
|
|
m_ctFontAttributes = dict;
|
|
|
|
m_cgFont = CTFontCopyGraphicsFont(m_ctFont, NULL);
|
|
}
|
|
|
|
static const CGAffineTransform kSlantTransform = CGAffineTransformMake(1, 0, tan(wxDegToRad(11)), 1, 0, 0);
|
|
|
|
namespace
|
|
{
|
|
|
|
struct CachedFontEntry
|
|
{
|
|
CachedFontEntry()
|
|
{
|
|
used = false;
|
|
}
|
|
|
|
wxCFRef<CTFontRef> font;
|
|
wxCFMutableDictionaryRef fontAttributes;
|
|
wxCFRef<CGFontRef> cgFont;
|
|
bool used;
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
void wxFontRefData::AllocIfNeeded() const
|
|
{
|
|
if (!m_ctFont)
|
|
const_cast<wxFontRefData *>(this)->Alloc();
|
|
|
|
}
|
|
|
|
void wxFontRefData::Alloc()
|
|
{
|
|
wxCHECK_RET(m_info.GetPointSize() > 0, wxT("Point size should not be zero."));
|
|
|
|
// use font caching, we cache a font with a certain size and a font with just any size for faster creation
|
|
wxString lookupnameNoSize = wxString::Format("%s_%d_%d", m_info.GetPostScriptName(), (int)m_info.GetStyle(), m_info.GetNumericWeight());
|
|
|
|
wxString lookupnameWithSize = wxString::Format("%s_%d_%d_%.2f", m_info.GetPostScriptName(), (int)m_info.GetStyle(), m_info.GetNumericWeight(), m_info.GetFractionalPointSize());
|
|
|
|
static std::map<wxString, CachedFontEntry> fontcache;
|
|
|
|
CachedFontEntry& entryWithSize = fontcache[lookupnameWithSize];
|
|
if (entryWithSize.used)
|
|
{
|
|
m_ctFont = entryWithSize.font;
|
|
m_ctFontAttributes = entryWithSize.fontAttributes;
|
|
}
|
|
else
|
|
{
|
|
CachedFontEntry& entryNoSize = fontcache[lookupnameNoSize];
|
|
if ( entryNoSize.used )
|
|
{
|
|
m_ctFont = CTFontCreateCopyWithAttributes(entryNoSize.font, m_info.GetPointSize(), NULL, NULL);
|
|
m_ctFontAttributes = entryNoSize.fontAttributes.CreateCopy();
|
|
m_ctFontAttributes.SetValue(kCTFontAttributeName,m_ctFont.get());
|
|
m_cgFont = CTFontCopyGraphicsFont(m_ctFont, NULL);
|
|
entryWithSize.font = m_ctFont;
|
|
entryWithSize.cgFont = m_cgFont;
|
|
entryWithSize.fontAttributes = m_ctFontAttributes;
|
|
entryWithSize.used = true;
|
|
}
|
|
else
|
|
{
|
|
// emulate slant if necessary, the font descriptor itself carries that information,
|
|
// while the weight can only be determined properly from the generated font itself
|
|
const CGAffineTransform* remainingTransform = NULL;
|
|
if ( m_info.GetStyle() != wxFONTSTYLE_NORMAL && m_info.GetCTSlant(m_info.GetCTFontDescriptor()) < 0.01 )
|
|
remainingTransform = &kSlantTransform;
|
|
|
|
wxCFRef<CTFontRef> font = CTFontCreateWithFontDescriptor(m_info.GetCTFontDescriptor(), m_info.GetPointSize(), remainingTransform);
|
|
|
|
// emulate weigth if necessary
|
|
int difference = m_info.GetNumericWeight() - CTWeightToWX(wxNativeFontInfo::GetCTWeight(font));
|
|
|
|
SetFont(font); // Sets m_ctFont, m_ctFontAttributes, m_cgFont
|
|
if ( difference != 0 )
|
|
{
|
|
if ( difference > 0 )
|
|
{
|
|
// TODO: find better heuristics to determine target stroke width
|
|
CGFloat width = 0;
|
|
width = -1.0 * (1 + (difference / 100));
|
|
m_ctFontAttributes.SetValue(kCTStrokeWidthAttributeName, width);
|
|
}
|
|
else
|
|
{
|
|
// we cannot emulate lighter fonts
|
|
}
|
|
}
|
|
|
|
entryWithSize.font = m_ctFont;
|
|
entryWithSize.cgFont = m_cgFont;
|
|
entryWithSize.fontAttributes = m_ctFontAttributes;
|
|
entryWithSize.used = true;
|
|
|
|
entryNoSize.font = m_ctFont;
|
|
entryNoSize.fontAttributes = m_ctFontAttributes;
|
|
// no reason to copy cgFont as will have to be regenerated anyway
|
|
entryNoSize.used = true;
|
|
}
|
|
}
|
|
m_cgFont.reset(CTFontCopyGraphicsFont(m_ctFont, NULL));
|
|
}
|
|
|
|
bool wxFontRefData::IsFixedWidth() const
|
|
{
|
|
CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(m_ctFont);
|
|
return (traits & kCTFontMonoSpaceTrait) != 0;
|
|
}
|
|
|
|
CTFontRef wxFontRefData::OSXGetCTFont() const
|
|
{
|
|
AllocIfNeeded();
|
|
return m_ctFont;
|
|
}
|
|
|
|
CFDictionaryRef wxFontRefData::OSXGetCTFontAttributes() const
|
|
{
|
|
AllocIfNeeded();
|
|
return m_ctFontAttributes;
|
|
}
|
|
|
|
CGFontRef wxFontRefData::OSXGetCGFont() const
|
|
{
|
|
AllocIfNeeded();
|
|
return m_cgFont;
|
|
}
|
|
|
|
const wxNativeFontInfo& wxFontRefData::GetNativeFontInfo() const
|
|
{
|
|
AllocIfNeeded();
|
|
return m_info;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// wxFont
|
|
// ----------------------------------------------------------------------------
|
|
|
|
bool wxFont::Create(const wxNativeFontInfo& info)
|
|
{
|
|
UnRef();
|
|
|
|
m_refData = new wxFontRefData(info);
|
|
RealizeResource();
|
|
|
|
return true;
|
|
}
|
|
|
|
wxFont::wxFont(wxOSXSystemFont font)
|
|
{
|
|
wxASSERT(font != wxOSX_SYSTEM_FONT_NONE);
|
|
CTFontUIFontType uifont = kCTFontSystemFontType;
|
|
switch (font)
|
|
{
|
|
case wxOSX_SYSTEM_FONT_NORMAL:
|
|
uifont = kCTFontSystemFontType;
|
|
break;
|
|
case wxOSX_SYSTEM_FONT_BOLD:
|
|
uifont = kCTFontEmphasizedSystemFontType;
|
|
break;
|
|
case wxOSX_SYSTEM_FONT_SMALL:
|
|
uifont = kCTFontSmallSystemFontType;
|
|
break;
|
|
case wxOSX_SYSTEM_FONT_SMALL_BOLD:
|
|
uifont = kCTFontSmallEmphasizedSystemFontType;
|
|
break;
|
|
case wxOSX_SYSTEM_FONT_MINI:
|
|
uifont = kCTFontMiniSystemFontType;
|
|
break;
|
|
case wxOSX_SYSTEM_FONT_MINI_BOLD:
|
|
uifont = kCTFontMiniEmphasizedSystemFontType;
|
|
break;
|
|
case wxOSX_SYSTEM_FONT_LABELS:
|
|
uifont = kCTFontLabelFontType;
|
|
break;
|
|
case wxOSX_SYSTEM_FONT_VIEWS:
|
|
uifont = kCTFontViewsFontType;
|
|
break;
|
|
case wxOSX_SYSTEM_FONT_FIXED:
|
|
uifont = kCTFontUIFontUserFixedPitch;
|
|
default:
|
|
break;
|
|
}
|
|
wxCFRef<CTFontRef> ctfont(CTFontCreateUIFontForLanguage(uifont, 0.0, NULL));
|
|
m_refData = new wxFontRefData(ctfont);
|
|
}
|
|
|
|
#if wxOSX_USE_COCOA
|
|
|
|
wxFont::wxFont(WX_NSFont nsfont)
|
|
{
|
|
m_refData = new wxFontRefData((CTFontRef)nsfont);
|
|
}
|
|
|
|
#endif
|
|
|
|
wxFont::wxFont(CTFontRef font)
|
|
{
|
|
m_refData = new wxFontRefData(font);
|
|
}
|
|
|
|
wxFont::wxFont(const wxString& fontdesc)
|
|
{
|
|
wxNativeFontInfo info;
|
|
if (info.FromString(fontdesc))
|
|
(void)Create(info);
|
|
}
|
|
|
|
wxFont::wxFont(const wxFontInfo& info)
|
|
{
|
|
m_refData = new wxFontRefData(info);
|
|
|
|
if ( info.IsUsingSizeInPixels() )
|
|
SetPixelSize(info.GetPixelSize());
|
|
}
|
|
|
|
wxFont::wxFont(int size,
|
|
int family,
|
|
int style,
|
|
int weight,
|
|
bool underlined,
|
|
const wxString& face,
|
|
wxFontEncoding encoding)
|
|
{
|
|
(void)Create(size, (wxFontFamily)family, (wxFontStyle)style,
|
|
(wxFontWeight)weight, underlined, face, encoding);
|
|
}
|
|
|
|
bool wxFont::Create(int pointSize,
|
|
wxFontFamily family,
|
|
wxFontStyle style,
|
|
wxFontWeight weight,
|
|
bool underlined,
|
|
const wxString& faceName,
|
|
wxFontEncoding encoding)
|
|
{
|
|
m_refData = new wxFontRefData(InfoFromLegacyParams(pointSize, family,
|
|
style, weight, underlined,
|
|
faceName, encoding));
|
|
|
|
return true;
|
|
}
|
|
|
|
wxFont::~wxFont()
|
|
{
|
|
}
|
|
|
|
void wxFont::DoSetNativeFontInfo(const wxNativeFontInfo& info)
|
|
{
|
|
UnRef();
|
|
|
|
m_refData = new wxFontRefData(info);
|
|
}
|
|
|
|
bool wxFont::RealizeResource()
|
|
{
|
|
return OSXGetCTFont();
|
|
}
|
|
|
|
void wxFont::SetEncoding(wxFontEncoding encoding)
|
|
{
|
|
AllocExclusive();
|
|
|
|
M_FONTDATA->SetEncoding(encoding);
|
|
}
|
|
|
|
wxGDIRefData* wxFont::CreateGDIRefData() const
|
|
{
|
|
return new wxFontRefData;
|
|
}
|
|
|
|
wxGDIRefData* wxFont::CloneGDIRefData(const wxGDIRefData* data) const
|
|
{
|
|
return new wxFontRefData(*static_cast<const wxFontRefData*>(data));
|
|
}
|
|
|
|
void wxFont::SetFractionalPointSize(double pointSize)
|
|
{
|
|
AllocExclusive();
|
|
|
|
M_FONTDATA->SetFractionalPointSize(pointSize);
|
|
}
|
|
|
|
void wxFont::SetFamily(wxFontFamily family)
|
|
{
|
|
AllocExclusive();
|
|
|
|
M_FONTDATA->SetFamily(family);
|
|
}
|
|
|
|
void wxFont::SetStyle(wxFontStyle style)
|
|
{
|
|
AllocExclusive();
|
|
|
|
M_FONTDATA->SetStyle(style);
|
|
}
|
|
|
|
void wxFont::SetNumericWeight(int weight)
|
|
{
|
|
AllocExclusive();
|
|
|
|
M_FONTDATA->SetNumericWeight(weight);
|
|
}
|
|
|
|
bool wxFont::SetFaceName(const wxString& faceName)
|
|
{
|
|
AllocExclusive();
|
|
|
|
M_FONTDATA->SetFaceName(faceName);
|
|
|
|
return wxFontBase::SetFaceName(faceName);
|
|
}
|
|
|
|
void wxFont::SetUnderlined(bool underlined)
|
|
{
|
|
AllocExclusive();
|
|
|
|
M_FONTDATA->SetUnderlined(underlined);
|
|
}
|
|
|
|
void wxFont::SetStrikethrough(bool strikethrough)
|
|
{
|
|
AllocExclusive();
|
|
|
|
M_FONTDATA->SetStrikethrough(strikethrough);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// accessors
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// TODO: insert checks everywhere for M_FONTDATA == NULL!
|
|
|
|
double wxFont::GetFractionalPointSize() const
|
|
{
|
|
wxCHECK_MSG(IsOk(), 0, wxT("invalid font"));
|
|
|
|
return M_FONTDATA->GetFractionalPointSize();
|
|
}
|
|
|
|
wxSize wxFont::GetPixelSize() const
|
|
{
|
|
#if wxUSE_GRAPHICS_CONTEXT
|
|
// TODO: consider caching the value
|
|
wxGraphicsContext* dc = wxGraphicsContext::CreateFromNative((CGContextRef)NULL);
|
|
dc->SetFont(*this, *wxBLACK);
|
|
wxDouble width, height = 0;
|
|
dc->GetTextExtent(wxT("g"), &width, &height, NULL, NULL);
|
|
delete dc;
|
|
return wxSize((int)width, (int)height);
|
|
#else
|
|
return wxFontBase::GetPixelSize();
|
|
#endif
|
|
}
|
|
|
|
bool wxFont::IsFixedWidth() const
|
|
{
|
|
wxCHECK_MSG(IsOk(), false, wxT("invalid font"));
|
|
|
|
// cast away constness otherwise lazy font resolution is not possible
|
|
const_cast<wxFont*>(this)->RealizeResource();
|
|
|
|
return M_FONTDATA->IsFixedWidth();
|
|
}
|
|
|
|
wxFontFamily wxFont::DoGetFamily() const
|
|
{
|
|
return M_FONTDATA->GetFamily();
|
|
}
|
|
|
|
wxFontStyle wxFont::GetStyle() const
|
|
{
|
|
wxCHECK_MSG(IsOk(), wxFONTSTYLE_MAX, wxT("invalid font"));
|
|
|
|
return M_FONTDATA->GetStyle();
|
|
}
|
|
|
|
int wxFont::GetNumericWeight() const
|
|
{
|
|
wxCHECK_MSG(IsOk(), wxFONTWEIGHT_MAX, wxT("invalid font"));
|
|
|
|
return M_FONTDATA->GetNumericWeight();
|
|
}
|
|
|
|
bool wxFont::GetUnderlined() const
|
|
{
|
|
wxCHECK_MSG(IsOk(), false, wxT("invalid font"));
|
|
|
|
return M_FONTDATA->GetUnderlined();
|
|
}
|
|
|
|
bool wxFont::GetStrikethrough() const
|
|
{
|
|
wxCHECK_MSG(IsOk(), false, wxT("invalid font"));
|
|
|
|
return M_FONTDATA->GetStrikethrough();
|
|
}
|
|
|
|
wxString wxFont::GetFaceName() const
|
|
{
|
|
wxCHECK_MSG(IsOk(), wxEmptyString, wxT("invalid font"));
|
|
|
|
return M_FONTDATA->GetFaceName();
|
|
}
|
|
|
|
wxFontEncoding wxFont::GetEncoding() const
|
|
{
|
|
wxCHECK_MSG(IsOk(), wxFONTENCODING_DEFAULT, wxT("invalid font"));
|
|
|
|
return M_FONTDATA->GetEncoding();
|
|
}
|
|
|
|
CTFontRef wxFont::OSXGetCTFont() const
|
|
{
|
|
wxCHECK_MSG(IsOk(), 0, wxT("invalid font"));
|
|
return M_FONTDATA->OSXGetCTFont();
|
|
}
|
|
|
|
CFDictionaryRef wxFont::OSXGetCTFontAttributes() const
|
|
{
|
|
wxCHECK_MSG(IsOk(), 0, wxT("invalid font"));
|
|
return M_FONTDATA->OSXGetCTFontAttributes();
|
|
}
|
|
|
|
#if wxOSX_USE_COCOA_OR_CARBON
|
|
|
|
CGFontRef wxFont::OSXGetCGFont() const
|
|
{
|
|
wxCHECK_MSG(IsOk(), 0, wxT("invalid font"));
|
|
return M_FONTDATA->OSXGetCGFont();
|
|
}
|
|
|
|
#endif
|
|
|
|
const wxNativeFontInfo* wxFont::GetNativeFontInfo() const
|
|
{
|
|
return IsOk() ? &(M_FONTDATA->GetNativeFontInfo()) : NULL;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// wxNativeFontInfo
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/* from Core Text Manual Common Operations */
|
|
|
|
void wxNativeFontInfo::Init()
|
|
{
|
|
m_descriptor.reset();
|
|
|
|
m_underlined = false;
|
|
m_strikethrough = false;
|
|
m_encoding = wxFONTENCODING_UTF8;
|
|
|
|
m_ctWeight = 0.0;
|
|
m_ctWidth = 0.0;
|
|
m_style = wxFONTSTYLE_NORMAL;
|
|
m_ctSize = 0.0;
|
|
m_family = wxFONTFAMILY_DEFAULT;
|
|
|
|
m_familyName.clear();
|
|
m_postScriptName.clear();
|
|
}
|
|
|
|
void wxNativeFontInfo::Init(const wxNativeFontInfo& info)
|
|
{
|
|
Init();
|
|
|
|
m_descriptor = info.m_descriptor;
|
|
|
|
m_underlined = info.m_underlined;
|
|
m_strikethrough = info.m_strikethrough;
|
|
m_encoding = info.m_encoding;
|
|
|
|
m_ctWeight = info.m_ctWeight;
|
|
m_ctWidth = info.m_ctWidth;
|
|
m_style = info.m_style;
|
|
m_ctSize = info.m_ctSize;
|
|
m_family = info.m_family;
|
|
|
|
m_familyName = info.m_familyName;
|
|
m_postScriptName = info.m_postScriptName;
|
|
}
|
|
|
|
void wxNativeFontInfo::InitFromFont(CTFontRef font)
|
|
{
|
|
Init();
|
|
|
|
InitFromFontDescriptor(CTFontCopyFontDescriptor(font) );
|
|
}
|
|
|
|
void wxNativeFontInfo::InitFromFontDescriptor(CTFontDescriptorRef desc)
|
|
{
|
|
Init();
|
|
|
|
m_descriptor.reset(wxCFRetain(desc));
|
|
|
|
m_ctWeight = GetCTWeight(desc);
|
|
m_ctWidth = GetCTwidth(desc);
|
|
m_style = GetCTSlant(desc) > 0.01 ? wxFONTSTYLE_ITALIC : wxFONTSTYLE_NORMAL;
|
|
wxCFTypeRef(CTFontDescriptorCopyAttribute(desc, kCTFontSizeAttribute)).GetValue(m_ctSize, CGFloat(0.0));
|
|
|
|
// determine approximate family
|
|
|
|
CTFontSymbolicTraits symbolicTraits;
|
|
wxCFDictionaryRef traits((CFDictionaryRef)CTFontDescriptorCopyAttribute(desc, kCTFontTraitsAttribute));
|
|
traits.GetValue(kCTFontSymbolicTrait).GetValue((int32_t*)&symbolicTraits, 0);
|
|
|
|
if (symbolicTraits & kCTFontMonoSpaceTrait)
|
|
m_family = wxFONTFAMILY_TELETYPE;
|
|
else
|
|
{
|
|
uint32_t stylisticClass = symbolicTraits & kCTFontClassMaskTrait;
|
|
|
|
if (stylisticClass == kCTFontSansSerifClass)
|
|
m_family = wxFONTFAMILY_SWISS;
|
|
else if (stylisticClass == kCTFontScriptsClass)
|
|
m_family = wxFONTFAMILY_SCRIPT;
|
|
else if (stylisticClass == kCTFontOrnamentalsClass)
|
|
m_family = wxFONTFAMILY_DECORATIVE;
|
|
else if (stylisticClass == kCTFontSymbolicClass)
|
|
m_family = wxFONTFAMILY_DECORATIVE;
|
|
else
|
|
m_family = wxFONTFAMILY_ROMAN;
|
|
}
|
|
|
|
wxCFTypeRef(CTFontDescriptorCopyAttribute(m_descriptor, kCTFontFamilyNameAttribute)).GetValue(m_familyName);
|
|
}
|
|
|
|
void wxNativeFontInfo::Free()
|
|
{
|
|
m_descriptor.reset();
|
|
}
|
|
|
|
CTFontDescriptorRef wxNativeFontInfo::GetCTFontDescriptor() const
|
|
{
|
|
if ( !m_descriptor )
|
|
const_cast<wxNativeFontInfo *>(this)->CreateCTFontDescriptor();
|
|
|
|
return m_descriptor;
|
|
}
|
|
|
|
void wxNativeFontInfo::RealizeResource() const
|
|
{
|
|
(void) GetCTFontDescriptor();
|
|
}
|
|
|
|
void wxNativeFontInfo::CreateCTFontDescriptor()
|
|
{
|
|
CTFontDescriptorRef descriptor = NULL;
|
|
wxCFMutableDictionaryRef attributes;
|
|
|
|
// build all attributes that define our font.
|
|
|
|
|
|
if ( m_postScriptName.empty() )
|
|
{
|
|
wxString fontname = m_familyName;
|
|
if ( fontname.empty() )
|
|
fontname = FamilyToFaceName(m_family);
|
|
|
|
CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, wxCFStringRef(fontname));
|
|
}
|
|
else
|
|
{
|
|
CFDictionaryAddValue(attributes, kCTFontNameAttribute, wxCFStringRef(m_postScriptName));
|
|
}
|
|
|
|
wxCFMutableDictionaryRef traits;
|
|
if ( m_style != wxFONTSTYLE_NORMAL )
|
|
traits.SetValue(kCTFontSymbolicTrait, kCTFontItalicTrait);
|
|
|
|
traits.SetValue(kCTFontWeightTrait,m_ctWeight);
|
|
traits.SetValue(kCTFontWidthTrait,m_ctWidth);
|
|
|
|
attributes.SetValue(kCTFontTraitsAttribute,traits.get());
|
|
attributes.SetValue(kCTFontSizeAttribute, m_ctSize);
|
|
|
|
// Create the font descriptor with our attributes
|
|
descriptor = CTFontDescriptorCreateWithAttributes(attributes);
|
|
wxASSERT(descriptor != NULL);
|
|
|
|
m_descriptor = descriptor;
|
|
|
|
wxCFTypeRef(CTFontDescriptorCopyAttribute(m_descriptor, kCTFontFamilyNameAttribute)).GetValue(m_familyName);
|
|
|
|
#if wxDEBUG_LEVEL >= 2
|
|
// for debugging: show all different font names
|
|
wxCFRef<CTFontRef> font = CTFontCreateWithFontDescriptor(m_descriptor, 12, NULL);
|
|
wxString familyname;
|
|
wxCFTypeRef(CTFontDescriptorCopyAttribute(m_descriptor, kCTFontFamilyNameAttribute)).GetValue(familyname);
|
|
wxLogTrace(TRACE_CTFONT,"****** CreateCTFontDescriptor ******");
|
|
wxLogTrace(TRACE_CTFONT,"Descriptor FontFamilyName: %s",familyname.c_str());
|
|
|
|
wxString name;
|
|
wxCFTypeRef(CTFontDescriptorCopyAttribute(m_descriptor, kCTFontNameAttribute)).GetValue(name);
|
|
wxLogTrace(TRACE_CTFONT,"Descriptor FontName: %s",name.c_str());
|
|
|
|
wxString display;
|
|
wxCFTypeRef(CTFontDescriptorCopyAttribute(m_descriptor, kCTFontDisplayNameAttribute)).GetValue(display);
|
|
wxLogTrace(TRACE_CTFONT,"Descriptor DisplayName: %s",display.c_str());
|
|
|
|
wxString style;
|
|
wxCFTypeRef(CTFontDescriptorCopyAttribute(m_descriptor, kCTFontStyleNameAttribute)).GetValue(style);
|
|
wxLogTrace(TRACE_CTFONT,"Descriptor StyleName: %s",style.c_str());
|
|
|
|
wxString psname;
|
|
wxCFTypeRef(CTFontCopyPostScriptName(font)).GetValue(psname);
|
|
wxLogTrace(TRACE_CTFONT,"Created Font PostScriptName: %s",psname.c_str());
|
|
|
|
wxString fullname;
|
|
wxCFTypeRef(CTFontCopyFullName(font)).GetValue(fullname);
|
|
wxLogTrace(TRACE_CTFONT,"Created Font FullName: %s",fullname.c_str());
|
|
|
|
wxLogTrace(TRACE_CTFONT,"************************************");
|
|
#endif
|
|
}
|
|
|
|
// Core Text Helpers
|
|
|
|
CGFloat wxNativeFontInfo::GetCTWeight(CTFontRef font)
|
|
{
|
|
CGFloat weight;
|
|
CFTypeRef fonttraitstype = CTFontCopyAttribute(font, kCTFontTraitsAttribute);
|
|
wxCFDictionaryRef traits((CFDictionaryRef)fonttraitstype);
|
|
traits.GetValue(kCTFontWeightTrait).GetValue(&weight, CGFloat(0.0));
|
|
return weight;
|
|
}
|
|
|
|
CGFloat wxNativeFontInfo::GetCTWeight(CTFontDescriptorRef descr)
|
|
{
|
|
CGFloat weight;
|
|
CFTypeRef fonttraitstype = CTFontDescriptorCopyAttribute(descr, kCTFontTraitsAttribute);
|
|
wxCFDictionaryRef traits((CFDictionaryRef)fonttraitstype);
|
|
traits.GetValue(kCTFontWeightTrait).GetValue(&weight, CGFloat(0.0));
|
|
return weight;
|
|
}
|
|
|
|
CGFloat wxNativeFontInfo::GetCTwidth(CTFontDescriptorRef descr)
|
|
{
|
|
CGFloat weight;
|
|
CFTypeRef fonttraitstype = CTFontDescriptorCopyAttribute(descr, kCTFontTraitsAttribute);
|
|
wxCFDictionaryRef traits((CFDictionaryRef)fonttraitstype);
|
|
traits.GetValue(kCTFontWidthTrait).GetValue(&weight, CGFloat(0.0));
|
|
return weight;
|
|
}
|
|
|
|
CGFloat wxNativeFontInfo::GetCTSlant(CTFontDescriptorRef descr)
|
|
{
|
|
CGFloat slant;
|
|
CFTypeRef fonttraitstype = CTFontDescriptorCopyAttribute(descr, kCTFontTraitsAttribute);
|
|
wxCFDictionaryRef traits((CFDictionaryRef)fonttraitstype);
|
|
traits.GetValue(kCTFontSlantTrait).GetValue(&slant, CGFloat(0.0));
|
|
return slant;
|
|
}
|
|
|
|
// recipe taken from
|
|
// https://developer.apple.com/library/archive/documentation/StringsTextFonts/Conceptual/CoreText_Programming/FontOperations/FontOperations.html
|
|
|
|
// common prefix of plist serializiation, gets removed and readded
|
|
|
|
static const wxString& GetPListPrefix()
|
|
{
|
|
static const wxString s_plistPrefix = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE plist PUBLIC "
|
|
"\"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">";
|
|
return s_plistPrefix;
|
|
}
|
|
|
|
bool wxNativeFontInfo::FromString(const wxString& s)
|
|
{
|
|
double d;
|
|
long l, version;
|
|
|
|
Init();
|
|
|
|
wxStringTokenizer tokenizer(s, wxT(";"));
|
|
|
|
wxString token = tokenizer.GetNextToken();
|
|
if ( !token.ToLong(&l) )
|
|
return false;
|
|
version = l;
|
|
|
|
if ( version == 0 || version == 1 )
|
|
{
|
|
token = tokenizer.GetNextToken();
|
|
if ( !token.ToCDouble(&d) )
|
|
return false;
|
|
if ( d < 0 || d > FLT_MAX )
|
|
return false;
|
|
#ifdef __LP64__
|
|
// CGFloat is just double in this case.
|
|
m_ctSize = d;
|
|
#else // !__LP64__
|
|
m_ctSize = static_cast<CGFloat>(d);
|
|
#endif // __LP64__/!__LP64__
|
|
|
|
token = tokenizer.GetNextToken();
|
|
if ( !token.ToLong(&l) )
|
|
return false;
|
|
m_family = (wxFontFamily)l;
|
|
|
|
token = tokenizer.GetNextToken();
|
|
if ( !token.ToLong(&l) )
|
|
return false;
|
|
m_style = (wxFontStyle)l;
|
|
|
|
token = tokenizer.GetNextToken();
|
|
if ( !token.ToLong(&l) )
|
|
return false;
|
|
m_ctWeight = WXWeightToCT(wxFont::ConvertFromLegacyWeightIfNecessary(l));
|
|
|
|
token = tokenizer.GetNextToken();
|
|
if ( !token.ToLong(&l) )
|
|
return false;
|
|
m_underlined = l != 0;
|
|
|
|
if ( version == 0L )
|
|
{
|
|
m_strikethrough = false;
|
|
}
|
|
else
|
|
{
|
|
token = tokenizer.GetNextToken();
|
|
if ( !token.ToLong(&l) )
|
|
return false;
|
|
m_strikethrough = l != 0;
|
|
}
|
|
|
|
// this works correctly via fallback even if this is (backwards compatibility) a font family name
|
|
SetPostScriptName( tokenizer.GetNextToken() );
|
|
|
|
RealizeResource();
|
|
|
|
#ifndef __WXMAC__
|
|
if( !m_familyName )
|
|
return false;
|
|
#endif
|
|
|
|
token = tokenizer.GetNextToken();
|
|
if ( !token.ToLong(&l) )
|
|
return false;
|
|
m_encoding = (wxFontEncoding)l;
|
|
return true;
|
|
}
|
|
else if ( version == 2 )
|
|
{
|
|
token = tokenizer.GetNextToken();
|
|
if ( !token.ToLong(&l) )
|
|
return false;
|
|
bool underlined = l != 0;
|
|
|
|
token = tokenizer.GetNextToken();
|
|
if ( !token.ToLong(&l) )
|
|
return false;
|
|
bool strikethrough = l != 0;
|
|
|
|
token = tokenizer.GetNextToken();
|
|
if ( !token.ToLong(&l) )
|
|
return false;
|
|
wxFontEncoding encoding = (wxFontEncoding)l;
|
|
|
|
wxString xml = tokenizer.GetString();
|
|
xml = GetPListPrefix()+xml;
|
|
wxCFStringRef plist(xml);
|
|
wxCFDataRef listData(CFStringCreateExternalRepresentation(kCFAllocatorDefault,plist,kCFStringEncodingUTF8,0));
|
|
wxCFDictionaryRef attributes((CFDictionaryRef) CFPropertyListCreateWithData(kCFAllocatorDefault, listData, 0, NULL, NULL));
|
|
CTFontDescriptorRef descriptor = NULL;
|
|
if (attributes != NULL)
|
|
descriptor = CTFontDescriptorCreateWithAttributes(attributes);
|
|
if (descriptor != NULL)
|
|
{
|
|
InitFromFontDescriptor(descriptor);
|
|
m_underlined = underlined;
|
|
m_strikethrough = strikethrough;
|
|
m_encoding = encoding;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
wxString wxNativeFontInfo::ToString() const
|
|
{
|
|
wxString s;
|
|
|
|
// version 2 is a streamed property list of the font descriptor as recommended by Apple
|
|
// prefixed by the attributes that are non-native to the native font ref like underline, strikethrough etc.
|
|
wxCFDictionaryRef attributes(CTFontDescriptorCopyAttributes(GetCTFontDescriptor()));
|
|
|
|
if (attributes != NULL)
|
|
{
|
|
CFPropertyListFormat format = kCFPropertyListXMLFormat_v1_0;
|
|
if (CFPropertyListIsValid(attributes, format))
|
|
{
|
|
wxCFDataRef listData(CFPropertyListCreateData(kCFAllocatorDefault, attributes, format, 0, NULL));
|
|
wxCFStringRef cfString( CFStringCreateFromExternalRepresentation( kCFAllocatorDefault, listData, kCFStringEncodingUTF8) );
|
|
wxString xml = cfString.AsString();
|
|
xml.Replace("\r",wxEmptyString,true);
|
|
xml.Replace("\t",wxEmptyString,true);
|
|
xml = xml.Mid(xml.Find("<plist"));
|
|
|
|
s.Printf("%d;%d;%d;%d;%s",
|
|
2, // version
|
|
GetUnderlined(),
|
|
GetStrikethrough(),
|
|
(int)GetEncoding(),
|
|
xml);
|
|
}
|
|
}
|
|
|
|
if ( s.empty() )
|
|
{
|
|
// fallback to version 1
|
|
s.Printf(wxT("%d;%s;%d;%d;%d;%d;%d;%s;%d"),
|
|
1, // version
|
|
wxString::FromCDouble(GetFractionalPointSize()),
|
|
GetFamily(),
|
|
(int)GetStyle(),
|
|
GetNumericWeight(),
|
|
GetUnderlined(),
|
|
GetStrikethrough(),
|
|
GetPostScriptName().GetData(),
|
|
(int)GetEncoding());
|
|
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
double wxNativeFontInfo::GetFractionalPointSize() const
|
|
{
|
|
return m_ctSize;
|
|
}
|
|
|
|
wxFontStyle wxNativeFontInfo::GetStyle() const
|
|
{
|
|
return m_style;
|
|
}
|
|
|
|
int wxNativeFontInfo::GetNumericWeight() const
|
|
{
|
|
return CTWeightToWX(m_ctWeight /* GetCTWeight(m_descriptor)*/);
|
|
}
|
|
|
|
bool wxNativeFontInfo::GetUnderlined() const
|
|
{
|
|
return m_underlined;
|
|
}
|
|
|
|
wxString wxNativeFontInfo::GetPostScriptName() const
|
|
{
|
|
// return user-set PostScript name as-is
|
|
if ( !m_postScriptName.empty() )
|
|
return m_postScriptName;
|
|
|
|
// if not explicitly set, obtain it from the font descriptor
|
|
wxString ps;
|
|
wxCFTypeRef(CTFontDescriptorCopyAttribute(GetCTFontDescriptor(), kCTFontNameAttribute)).GetValue(ps);
|
|
|
|
if ( WX_IS_MACOS_AVAILABLE(10, 16) )
|
|
{
|
|
// the PostScript names reported in macOS start with a dot for System Fonts, this has to be corrected
|
|
// otherwise round-trips are not possible, resulting in a Times Fallback, therefore we replace these with
|
|
// their official PostScript Name
|
|
wxString rest;
|
|
if ( ps.StartsWith(".SFNS", &rest) )
|
|
return "SFPro" + rest;
|
|
}
|
|
|
|
return ps;
|
|
}
|
|
|
|
wxString wxNativeFontInfo::GetFaceName() const
|
|
{
|
|
return m_familyName;
|
|
}
|
|
|
|
wxFontFamily wxNativeFontInfo::GetFamily() const
|
|
{
|
|
return m_family;
|
|
}
|
|
|
|
wxFontEncoding wxNativeFontInfo::GetEncoding() const
|
|
{
|
|
return m_encoding;
|
|
}
|
|
|
|
bool wxNativeFontInfo::GetStrikethrough() const
|
|
{
|
|
return m_strikethrough;
|
|
}
|
|
|
|
// changing the font descriptor
|
|
|
|
void wxNativeFontInfo::SetFractionalPointSize(double pointsize)
|
|
{
|
|
if (GetFractionalPointSize() != pointsize)
|
|
{
|
|
m_ctSize = pointsize;
|
|
|
|
if ( m_descriptor)
|
|
{
|
|
wxCFMutableDictionaryRef attributes;
|
|
attributes.SetValue(kCTFontSizeAttribute, wxCFNumberRef((CGFloat)pointsize));
|
|
m_descriptor.reset(CTFontDescriptorCreateCopyWithAttributes(m_descriptor, attributes));
|
|
}
|
|
}
|
|
}
|
|
|
|
void wxNativeFontInfo::SetStyle(wxFontStyle style_)
|
|
{
|
|
bool formerIsItalic = GetStyle() != wxFONTSTYLE_NORMAL;
|
|
bool newIsItalic = style_ != wxFONTSTYLE_NORMAL;
|
|
|
|
m_style = style_;
|
|
|
|
if (formerIsItalic != newIsItalic)
|
|
{
|
|
if ( m_descriptor )
|
|
{
|
|
if ( m_style != wxFONTSTYLE_NORMAL )
|
|
m_descriptor = CTFontDescriptorCreateCopyWithSymbolicTraits(m_descriptor, kCTFontItalicTrait, kCTFontItalicTrait);
|
|
else
|
|
m_descriptor = CTFontDescriptorCreateCopyWithSymbolicTraits(m_descriptor, 0, kCTFontItalicTrait);
|
|
}
|
|
}
|
|
}
|
|
|
|
void wxNativeFontInfo::SetNumericWeight(int weight)
|
|
{
|
|
int formerWeight = GetNumericWeight();
|
|
if (formerWeight != weight)
|
|
{
|
|
Free();
|
|
m_ctWeight = WXWeightToCT(weight);
|
|
}
|
|
}
|
|
|
|
void wxNativeFontInfo::SetUnderlined(bool underlined)
|
|
{
|
|
m_underlined = underlined;
|
|
}
|
|
|
|
bool wxNativeFontInfo::SetFaceName(const wxString& facename)
|
|
{
|
|
if (GetFaceName() != facename)
|
|
{
|
|
Free();
|
|
m_familyName = facename;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxNativeFontInfo::SetPostScriptName(const wxString& postScriptName)
|
|
{
|
|
if (m_postScriptName != postScriptName)
|
|
{
|
|
Free();
|
|
m_postScriptName = postScriptName;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void wxNativeFontInfo::SetFamily(wxFontFamily family)
|
|
{
|
|
Free();
|
|
m_familyName.clear();
|
|
m_family = family;
|
|
}
|
|
|
|
void wxNativeFontInfo::SetEncoding(wxFontEncoding encoding)
|
|
{
|
|
wxUnusedVar(encoding);
|
|
}
|
|
|
|
void wxNativeFontInfo::SetStrikethrough(bool strikethrough)
|
|
{
|
|
m_strikethrough = strikethrough;
|
|
}
|