2021-02-13 17:34:22 +01:00
/*
* This file is part of OpenTTD .
* OpenTTD is free software ; you can redistribute it and / or modify it under the terms of the GNU General Public License as published by the Free Software Foundation , version 2.
* OpenTTD is distributed in the hope that it will be useful , but WITHOUT ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE .
* See the GNU General Public License for more details . You should have received a copy of the GNU General Public License along with OpenTTD . If not , see < http : //www.gnu.org/licenses/>.
*/
/** @file font_win32.cpp Functions related to font handling on Win32. */
# include "../../stdafx.h"
# include "../../debug.h"
# include "../../blitter/factory.hpp"
# include "../../core/alloc_func.hpp"
# include "../../core/math_func.hpp"
2023-05-16 21:50:41 +02:00
# include "../../core/mem_func.hpp"
2023-04-19 22:47:36 +02:00
# include "../../error_func.h"
2021-02-13 17:34:22 +01:00
# include "../../fileio_func.h"
# include "../../fontdetection.h"
# include "../../fontcache.h"
2022-09-15 20:57:10 +02:00
# include "../../fontcache/truetypefontcache.h"
2021-02-13 17:34:22 +01:00
# include "../../string_func.h"
# include "../../strings_func.h"
# include "../../zoom_func.h"
2022-09-15 20:57:10 +02:00
# include "font_win32.h"
2021-02-13 17:34:22 +01:00
# include "../../table/control_codes.h"
# include <windows.h>
# include <shlobj.h> /* SHGetFolderPath */
# include "os/windows/win32.h"
# undef small // Say what, Windows?
# include "safeguards.h"
2023-01-14 19:16:14 +01:00
struct EFCParam {
FontCacheSettings * settings ;
LOCALESIGNATURE locale ;
MissingGlyphSearcher * callback ;
std : : vector < std : : wstring > fonts ;
2021-02-13 17:34:22 +01:00
2023-01-14 19:16:14 +01:00
bool Add ( const std : : wstring_view & font ) {
for ( const auto & entry : this - > fonts ) {
if ( font . compare ( entry ) = = 0 ) return false ;
2021-02-13 17:34:22 +01:00
}
2023-01-14 19:16:14 +01:00
this - > fonts . emplace_back ( font ) ;
2021-02-13 17:34:22 +01:00
return true ;
}
} ;
static int CALLBACK EnumFontCallback ( const ENUMLOGFONTEX * logfont , const NEWTEXTMETRICEX * metric , DWORD type , LPARAM lParam )
{
EFCParam * info = ( EFCParam * ) lParam ;
/* Skip duplicates */
2023-01-14 19:16:14 +01:00
if ( ! info - > Add ( logfont - > elfFullName ) ) return 1 ;
2021-02-13 17:34:22 +01:00
/* Only use TrueType fonts */
if ( ! ( type & TRUETYPE_FONTTYPE ) ) return 1 ;
/* Don't use SYMBOL fonts */
if ( logfont - > elfLogFont . lfCharSet = = SYMBOL_CHARSET ) return 1 ;
/* Use monospaced fonts when asked for it. */
if ( info - > callback - > Monospace ( ) & & ( logfont - > elfLogFont . lfPitchAndFamily & ( FF_MODERN | FIXED_PITCH ) ) ! = ( FF_MODERN | FIXED_PITCH ) ) return 1 ;
/* The font has to have at least one of the supported locales to be usable. */
if ( ( metric - > ntmFontSig . fsCsb [ 0 ] & info - > locale . lsCsbSupported [ 0 ] ) = = 0 & & ( metric - > ntmFontSig . fsCsb [ 1 ] & info - > locale . lsCsbSupported [ 1 ] ) = = 0 ) {
/* On win9x metric->ntmFontSig seems to contain garbage. */
FONTSIGNATURE fs ;
memset ( & fs , 0 , sizeof ( fs ) ) ;
HFONT font = CreateFontIndirect ( & logfont - > elfLogFont ) ;
if ( font ! = nullptr ) {
HDC dc = GetDC ( nullptr ) ;
HGDIOBJ oldfont = SelectObject ( dc , font ) ;
GetTextCharsetInfo ( dc , & fs , 0 ) ;
SelectObject ( dc , oldfont ) ;
ReleaseDC ( nullptr , dc ) ;
DeleteObject ( font ) ;
}
if ( ( fs . fsCsb [ 0 ] & info - > locale . lsCsbSupported [ 0 ] ) = = 0 & & ( fs . fsCsb [ 1 ] & info - > locale . lsCsbSupported [ 1 ] ) = = 0 ) return 1 ;
}
char font_name [ MAX_PATH ] ;
2021-02-21 20:48:21 +01:00
convert_from_fs ( ( const wchar_t * ) logfont - > elfFullName , font_name , lengthof ( font_name ) ) ;
2021-02-13 17:34:22 +01:00
info - > callback - > SetFontNames ( info - > settings , font_name , & logfont - > elfLogFont ) ;
if ( info - > callback - > FindMissingGlyphs ( ) ) return 1 ;
2023-06-06 20:43:27 +02:00
Debug ( fontcache , 1 , " Fallback font: {} " , font_name ) ;
2021-02-13 17:34:22 +01:00
return 0 ; // stop enumerating
}
2023-06-06 22:55:56 +02:00
bool SetFallbackFont ( FontCacheSettings * settings , const std : : string & language_isocode , int winlangid , MissingGlyphSearcher * callback )
2021-02-13 17:34:22 +01:00
{
2022-09-15 19:21:27 +02:00
Debug ( fontcache , 1 , " Trying fallback fonts " ) ;
2021-02-13 17:34:22 +01:00
EFCParam langInfo ;
2021-02-21 20:48:21 +01:00
if ( GetLocaleInfo ( MAKELCID ( winlangid , SORT_DEFAULT ) , LOCALE_FONTSIGNATURE , ( LPTSTR ) & langInfo . locale , sizeof ( langInfo . locale ) / sizeof ( wchar_t ) ) = = 0 ) {
2021-02-13 17:34:22 +01:00
/* Invalid langid or some other mysterious error, can't determine fallback font. */
2022-09-15 19:21:27 +02:00
Debug ( fontcache , 1 , " Can't get locale info for fallback font (langid=0x{:x}) " , winlangid ) ;
2021-02-13 17:34:22 +01:00
return false ;
}
langInfo . settings = settings ;
langInfo . callback = callback ;
LOGFONT font ;
/* Enumerate all fonts. */
font . lfCharSet = DEFAULT_CHARSET ;
font . lfFaceName [ 0 ] = ' \0 ' ;
font . lfPitchAndFamily = 0 ;
HDC dc = GetDC ( nullptr ) ;
int ret = EnumFontFamiliesEx ( dc , & font , ( FONTENUMPROC ) & EnumFontCallback , ( LPARAM ) & langInfo , 0 ) ;
ReleaseDC ( nullptr , dc ) ;
return ret = = 0 ;
}
# ifndef ANTIALIASED_QUALITY
# define ANTIALIASED_QUALITY 4
# endif
/**
* Create a new Win32FontCache .
* @ param fs The font size that is going to be cached .
* @ param logfont The font that has to be loaded .
* @ param pixels The number of pixels this font should be high .
*/
Win32FontCache : : Win32FontCache ( FontSize fs , const LOGFONT & logfont , int pixels ) : TrueTypeFontCache ( fs , pixels ) , logfont ( logfont )
{
this - > dc = CreateCompatibleDC ( nullptr ) ;
this - > SetFontSize ( fs , pixels ) ;
}
Win32FontCache : : ~ Win32FontCache ( )
{
this - > ClearFontCache ( ) ;
DeleteDC ( this - > dc ) ;
DeleteObject ( this - > font ) ;
}
void Win32FontCache : : SetFontSize ( FontSize fs , int pixels )
{
if ( pixels = = 0 ) {
/* Try to determine a good height based on the minimal height recommended by the font. */
2022-12-18 00:58:29 +01:00
int scaled_height = ScaleGUITrad ( FontCache : : GetDefaultFontHeight ( this - > fs ) ) ;
2021-02-13 17:34:22 +01:00
pixels = scaled_height ;
HFONT temp = CreateFontIndirect ( & this - > logfont ) ;
if ( temp ! = nullptr ) {
HGDIOBJ old = SelectObject ( this - > dc , temp ) ;
UINT size = GetOutlineTextMetrics ( this - > dc , 0 , nullptr ) ;
2021-05-01 22:06:17 +02:00
LPOUTLINETEXTMETRIC otm = ( LPOUTLINETEXTMETRIC ) new BYTE [ size ] ;
2021-02-13 17:34:22 +01:00
GetOutlineTextMetrics ( this - > dc , size , otm ) ;
/* Font height is minimum height plus the difference between the default
* height for this font size and the small size . */
2022-12-18 00:58:29 +01:00
int diff = scaled_height - ScaleGUITrad ( FontCache : : GetDefaultFontHeight ( FS_SMALL ) ) ;
2021-04-20 12:49:20 +02:00
/* Clamp() is not used as scaled_height could be greater than MAX_FONT_SIZE, which is not permitted in Clamp(). */
pixels = std : : min ( std : : max ( std : : min < int > ( otm - > otmusMinimumPPEM , MAX_FONT_MIN_REC_SIZE ) + diff , scaled_height ) , MAX_FONT_SIZE ) ;
2021-02-13 17:34:22 +01:00
2021-05-01 22:06:17 +02:00
delete [ ] ( BYTE * ) otm ;
2021-02-13 17:34:22 +01:00
SelectObject ( dc , old ) ;
DeleteObject ( temp ) ;
}
} else {
2021-04-20 12:49:20 +02:00
pixels = ScaleGUITrad ( pixels ) ;
2021-02-13 17:34:22 +01:00
}
this - > used_size = pixels ;
/* Create GDI font handle. */
this - > logfont . lfHeight = - pixels ;
this - > logfont . lfWidth = 0 ;
this - > logfont . lfOutPrecision = ANTIALIASED_QUALITY ;
if ( this - > font ! = nullptr ) {
SelectObject ( dc , this - > old_font ) ;
DeleteObject ( this - > font ) ;
}
this - > font = CreateFontIndirect ( & this - > logfont ) ;
this - > old_font = SelectObject ( this - > dc , this - > font ) ;
/* Query the font metrics we needed. */
UINT otmSize = GetOutlineTextMetrics ( this - > dc , 0 , nullptr ) ;
2021-05-01 22:06:17 +02:00
POUTLINETEXTMETRIC otm = ( POUTLINETEXTMETRIC ) new BYTE [ otmSize ] ;
2021-02-13 17:34:22 +01:00
GetOutlineTextMetrics ( this - > dc , otmSize , otm ) ;
this - > units_per_em = otm - > otmEMSquare ;
this - > ascender = otm - > otmTextMetrics . tmAscent ;
this - > descender = otm - > otmTextMetrics . tmDescent ;
this - > height = this - > ascender + this - > descender ;
this - > glyph_size . cx = otm - > otmTextMetrics . tmMaxCharWidth ;
this - > glyph_size . cy = otm - > otmTextMetrics . tmHeight ;
2022-12-22 19:50:05 +01:00
this - > fontname = FS2OTTD ( ( LPWSTR ) ( ( BYTE * ) otm + ( ptrdiff_t ) otm - > otmpFaceName ) ) ;
Debug ( fontcache , 2 , " Loaded font '{}' with size {} " , this - > fontname , pixels ) ;
2021-05-01 22:06:17 +02:00
delete [ ] ( BYTE * ) otm ;
2021-02-13 17:34:22 +01:00
}
/**
* Reset cached glyphs .
*/
void Win32FontCache : : ClearFontCache ( )
{
/* GUI scaling might have changed, determine font size anew if it was automatically selected. */
if ( this - > font ! = nullptr ) this - > SetFontSize ( this - > fs , this - > req_size ) ;
this - > TrueTypeFontCache : : ClearFontCache ( ) ;
}
/* virtual */ const Sprite * Win32FontCache : : InternalGetGlyph ( GlyphID key , bool aa )
{
GLYPHMETRICS gm ;
MAT2 mat = { { 0 , 1 } , { 0 , 0 } , { 0 , 0 } , { 0 , 1 } } ;
2023-01-14 19:07:05 +01:00
/* Call GetGlyphOutline with zero size initially to get required memory size. */
DWORD size = GetGlyphOutline ( this - > dc , key , GGO_GLYPH_INDEX | ( aa ? GGO_GRAY8_BITMAP : GGO_BITMAP ) , & gm , 0 , nullptr , & mat ) ;
2023-04-19 22:47:36 +02:00
if ( size = = GDI_ERROR ) UserError ( " Unable to render font glyph " ) ;
2023-01-14 19:07:05 +01:00
2022-09-30 22:36:00 +02:00
/* Add 1 scaled pixel for the shadow on the medium font. Our sprite must be at least 1x1 pixel. */
uint shadow = ( this - > fs = = FS_NORMAL ) ? ScaleGUITrad ( 1 ) : 0 ;
uint width = std : : max ( 1U , ( uint ) gm . gmBlackBoxX + shadow ) ;
uint height = std : : max ( 1U , ( uint ) gm . gmBlackBoxY + shadow ) ;
2021-02-13 17:34:22 +01:00
/* Limit glyph size to prevent overflows later on. */
2023-04-19 22:47:36 +02:00
if ( width > MAX_GLYPH_DIM | | height > MAX_GLYPH_DIM ) UserError ( " Font glyph is too large " ) ;
2021-02-13 17:34:22 +01:00
2023-01-14 19:09:00 +01:00
/* Call GetGlyphOutline again with size to actually render the glyph. */
2021-05-01 22:06:17 +02:00
byte * bmp = new byte [ size ] ;
2023-01-14 19:09:00 +01:00
GetGlyphOutline ( this - > dc , key , GGO_GLYPH_INDEX | ( aa ? GGO_GRAY8_BITMAP : GGO_BITMAP ) , & gm , size , bmp , & mat ) ;
2021-02-13 17:34:22 +01:00
/* GDI has rendered the glyph, now we allocate a sprite and copy the image into it. */
SpriteLoader : : Sprite sprite ;
sprite . AllocateData ( ZOOM_LVL_NORMAL , width * height ) ;
2023-04-16 21:00:55 +02:00
sprite . type = SpriteType : : Font ;
2021-01-16 16:43:30 +01:00
sprite . colours = ( aa ? SCC_PAL | SCC_ALPHA : SCC_PAL ) ;
2021-02-13 17:34:22 +01:00
sprite . width = width ;
sprite . height = height ;
sprite . x_offs = gm . gmptGlyphOrigin . x ;
sprite . y_offs = this - > ascender - gm . gmptGlyphOrigin . y ;
if ( size > 0 ) {
/* All pixel data returned by GDI is in the form of DWORD-aligned rows.
* For a non anti - aliased glyph , the returned bitmap has one bit per pixel .
* For anti - aliased rendering , GDI uses the strange value range of 0 to 64 ,
* inclusively . To map this to 0 to 255 , we shift left by two and then
* subtract one . */
2021-04-10 11:17:53 +02:00
uint pitch = Align ( aa ? gm . gmBlackBoxX : std : : max ( ( gm . gmBlackBoxX + 7u ) / 8u , 1u ) , 4 ) ;
2021-02-13 17:34:22 +01:00
/* Draw shadow for medium size. */
if ( this - > fs = = FS_NORMAL & & ! aa ) {
for ( uint y = 0 ; y < gm . gmBlackBoxY ; y + + ) {
for ( uint x = 0 ; x < gm . gmBlackBoxX ; x + + ) {
if ( aa ? ( bmp [ x + y * pitch ] > 0 ) : HasBit ( bmp [ ( x / 8 ) + y * pitch ] , 7 - ( x % 8 ) ) ) {
2022-09-30 22:36:00 +02:00
sprite . data [ shadow + x + ( shadow + y ) * sprite . width ] . m = SHADOW_COLOUR ;
sprite . data [ shadow + x + ( shadow + y ) * sprite . width ] . a = aa ? ( bmp [ x + y * pitch ] < < 2 ) - 1 : 0xFF ;
2021-02-13 17:34:22 +01:00
}
}
}
}
for ( uint y = 0 ; y < gm . gmBlackBoxY ; y + + ) {
for ( uint x = 0 ; x < gm . gmBlackBoxX ; x + + ) {
if ( aa ? ( bmp [ x + y * pitch ] > 0 ) : HasBit ( bmp [ ( x / 8 ) + y * pitch ] , 7 - ( x % 8 ) ) ) {
sprite . data [ x + y * sprite . width ] . m = FACE_COLOUR ;
sprite . data [ x + y * sprite . width ] . a = aa ? ( bmp [ x + y * pitch ] < < 2 ) - 1 : 0xFF ;
}
}
}
}
GlyphEntry new_glyph ;
2021-01-16 16:43:31 +01:00
new_glyph . sprite = BlitterFactory : : GetCurrentBlitter ( ) - > Encode ( & sprite , SimpleSpriteAlloc ) ;
2021-02-13 17:34:22 +01:00
new_glyph . width = gm . gmCellIncX ;
this - > SetGlyphPtr ( key , & new_glyph ) ;
2021-05-01 22:06:17 +02:00
delete [ ] bmp ;
2021-02-13 17:34:22 +01:00
return new_glyph . sprite ;
}
/* virtual */ GlyphID Win32FontCache : : MapCharToGlyph ( WChar key )
{
assert ( IsPrintable ( key ) ) ;
if ( key > = SCC_SPRITE_START & & key < = SCC_SPRITE_END ) {
return this - > parent - > MapCharToGlyph ( key ) ;
}
/* Convert characters outside of the BMP into surrogate pairs. */
WCHAR chars [ 2 ] ;
if ( key > = 0x010000U ) {
2021-02-21 17:03:19 +01:00
chars [ 0 ] = ( wchar_t ) ( ( ( key - 0x010000U ) > > 10 ) + 0xD800 ) ;
chars [ 1 ] = ( wchar_t ) ( ( ( key - 0x010000U ) & 0x3FF ) + 0xDC00 ) ;
2021-02-13 17:34:22 +01:00
} else {
2021-02-21 17:03:19 +01:00
chars [ 0 ] = ( wchar_t ) ( key & 0xFFFF ) ;
2021-02-13 17:34:22 +01:00
}
WORD glyphs [ 2 ] = { 0 , 0 } ;
GetGlyphIndicesW ( this - > dc , chars , key > = 0x010000U ? 2 : 1 , glyphs , GGI_MARK_NONEXISTING_GLYPHS ) ;
return glyphs [ 0 ] ! = 0xFFFF ? glyphs [ 0 ] : 0 ;
}
/* virtual */ const void * Win32FontCache : : InternalGetFontTable ( uint32 tag , size_t & length )
{
DWORD len = GetFontData ( this - > dc , tag , 0 , nullptr , 0 ) ;
void * result = nullptr ;
if ( len ! = GDI_ERROR & & len > 0 ) {
result = MallocT < BYTE > ( len ) ;
GetFontData ( this - > dc , tag , 0 , result , len ) ;
}
length = len ;
return result ;
}
/**
* Loads the GDI font .
* If a GDI font description is present , e . g . from the automatic font
* fallback search , use it . Otherwise , try to resolve it by font name .
* @ param fs The font size to load .
*/
void LoadWin32Font ( FontSize fs )
{
2022-12-22 19:41:58 +01:00
FontCacheSubSetting * settings = GetFontCacheSubSetting ( fs ) ;
2021-02-13 17:34:22 +01:00
2021-04-28 17:10:15 +02:00
if ( settings - > font . empty ( ) ) return ;
2021-02-13 17:34:22 +01:00
2021-04-28 17:10:15 +02:00
const char * font_name = settings - > font . c_str ( ) ;
2021-02-13 17:34:22 +01:00
LOGFONT logfont ;
MemSetT ( & logfont , 0 ) ;
logfont . lfPitchAndFamily = fs = = FS_MONO ? FIXED_PITCH : VARIABLE_PITCH ;
logfont . lfCharSet = DEFAULT_CHARSET ;
logfont . lfOutPrecision = OUT_OUTLINE_PRECIS ;
logfont . lfClipPrecision = CLIP_DEFAULT_PRECIS ;
if ( settings - > os_handle ! = nullptr ) {
logfont = * ( const LOGFONT * ) settings - > os_handle ;
2021-04-28 17:10:15 +02:00
} else if ( strchr ( font_name , ' . ' ) ! = nullptr ) {
2021-02-13 17:34:22 +01:00
/* Might be a font file name, try load it. */
2021-02-21 20:48:21 +01:00
wchar_t fontPath [ MAX_PATH ] = { } ;
2021-02-13 17:34:22 +01:00
/* See if this is an absolute path. */
if ( FileExists ( settings - > font ) ) {
2021-04-28 17:10:15 +02:00
convert_to_fs ( font_name , fontPath , lengthof ( fontPath ) ) ;
2021-02-13 17:34:22 +01:00
} else {
/* Scan the search-paths to see if it can be found. */
2021-04-28 17:10:15 +02:00
std : : string full_font = FioFindFullPath ( BASE_DIR , font_name ) ;
2021-02-13 17:34:22 +01:00
if ( ! full_font . empty ( ) ) {
2021-04-28 17:10:15 +02:00
convert_to_fs ( font_name , fontPath , lengthof ( fontPath ) ) ;
2021-02-13 17:34:22 +01:00
}
}
if ( fontPath [ 0 ] ! = 0 ) {
if ( AddFontResourceEx ( fontPath , FR_PRIVATE , 0 ) ! = 0 ) {
/* Try a nice little undocumented function first for getting the internal font name.
* Some documentation is found at : http : //www.undocprint.org/winspool/getfontresourceinfo */
2021-05-14 15:26:03 +02:00
static DllLoader _gdi32 ( L " gdi32.dll " ) ;
2021-02-13 17:34:22 +01:00
typedef BOOL ( WINAPI * PFNGETFONTRESOURCEINFO ) ( LPCTSTR , LPDWORD , LPVOID , DWORD ) ;
2021-05-14 15:26:03 +02:00
static PFNGETFONTRESOURCEINFO GetFontResourceInfo = _gdi32 . GetProcAddress ( " GetFontResourceInfoW " ) ;
2021-02-13 17:34:22 +01:00
if ( GetFontResourceInfo ! = nullptr ) {
/* Try to query an array of LOGFONTs that describe the file. */
DWORD len = 0 ;
if ( GetFontResourceInfo ( fontPath , & len , nullptr , 2 ) & & len > = sizeof ( LOGFONT ) ) {
2021-05-01 22:06:17 +02:00
LOGFONT * buf = ( LOGFONT * ) new byte [ len ] ;
2021-02-13 17:34:22 +01:00
if ( GetFontResourceInfo ( fontPath , & len , buf , 2 ) ) {
logfont = * buf ; // Just use first entry.
}
2021-05-01 22:06:17 +02:00
delete [ ] ( byte * ) buf ;
2021-02-13 17:34:22 +01:00
}
}
/* No dice yet. Use the file name as the font face name, hoping it matches. */
if ( logfont . lfFaceName [ 0 ] = = 0 ) {
2021-02-21 20:48:21 +01:00
wchar_t fname [ _MAX_FNAME ] ;
_wsplitpath ( fontPath , nullptr , nullptr , fname , nullptr ) ;
2021-02-13 17:34:22 +01:00
2021-02-21 20:48:21 +01:00
wcsncpy_s ( logfont . lfFaceName , lengthof ( logfont . lfFaceName ) , fname , _TRUNCATE ) ;
2021-04-28 17:10:15 +02:00
logfont . lfWeight = strcasestr ( font_name , " bold " ) ! = nullptr | | strcasestr ( font_name , " -bold " ) ! = nullptr ? FW_BOLD : FW_NORMAL ; // Poor man's way to allow selecting bold fonts.
2021-02-13 17:34:22 +01:00
}
} else {
2023-04-18 19:41:29 +02:00
ShowInfo ( " Unable to load file '{}' for {} font, using default windows font selection instead " , font_name , FontSizeToName ( fs ) ) ;
2021-02-13 17:34:22 +01:00
}
}
}
if ( logfont . lfFaceName [ 0 ] = = 0 ) {
2021-04-28 17:10:15 +02:00
logfont . lfWeight = strcasestr ( font_name , " bold " ) ! = nullptr ? FW_BOLD : FW_NORMAL ; // Poor man's way to allow selecting bold fonts.
convert_to_fs ( font_name , logfont . lfFaceName , lengthof ( logfont . lfFaceName ) ) ;
2021-02-13 17:34:22 +01:00
}
HFONT font = CreateFontIndirect ( & logfont ) ;
if ( font = = nullptr ) {
2023-04-18 19:41:29 +02:00
ShowInfo ( " Unable to use '{}' for {} font, Win32 reported error 0x{:X}, using sprite font instead " , font_name , FontSizeToName ( fs ) , GetLastError ( ) ) ;
2021-02-13 17:34:22 +01:00
return ;
}
DeleteObject ( font ) ;
new Win32FontCache ( fs , logfont , settings - > size ) ;
}