Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

533 lines
16 KiB

//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <malloc.h>
#include <tier0/dbg.h>
#include <vgui/ISurface.h>
#include <tier0/mem.h>
#include <utlbuffer.h>
#include <vstdlib/vstrtools.h>
#include "filesystem.h"
#include "vgui_surfacelib/osxfont.h"
#include "FontEffects.h"
#define DEBUG_FONT_CREATE 0
struct MetricsTweaks_t
{
const char *m_windowsFontName;
int m_sizeAdjust;
float m_ascentMultiplier;
float m_descentMultiplier;
float m_leadingMultiplier;
};
static const MetricsTweaks_t g_defaultMetricTweaks = { NULL, 0, 1.0, 1.0, 1.0 };// -2, 1.0, 1.0, 1.0 };
static MetricsTweaks_t g_FontMetricTweaks[] =
{
{ "Helvetica", 0, 1.0, 1.0, 1.05 },
{ "Helvetica Bold", 0, 1.0, 1.0, 1.0 },
{ "HL2cross", 0, 0.8, 1.0, 1.1},
{ "Counter-Strike Logo", 0, 1.0, 1.0, 1.1},
{ "TF2", -2, 1.0, 1.0, 1.0 },
{ "TF2 Professor", -2, 1.0, 1.1, 1.1 },
{ "TF2 Build", -2, 1.0, 1.0, 1.0 },
{ "UniversLTStd-BoldCn", 0, 1.4, 1.0, 0.8 },
{ "UniversLTStd-Cn", 0, 1.2, 1.0, 1.0 },
//{ "TF2 Secondary", -2, 1.0, 1.0, 1.0 },
// { "Verdana", 0, 1.25, 1.0, 1.0 },
};
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
COSXFont::COSXFont() : m_ExtendedABCWidthsCache(256, 0, &ExtendedABCWidthsCacheLessFunc),
m_ExtendedKernedABCWidthsCache( 256, 0, &ExtendedKernedABCWidthsCacheLessFunc )
{
m_iTall = 0;
m_iAscent = 0;
m_iDescent = 0;
m_iWeight = 0;
m_iFlags = 0;
m_iMaxCharWidth = 0;
m_bAntiAliased = false;
m_bUnderlined = false;
m_iBlur = 0;
m_pGaussianDistribution = NULL;
m_iScanLines = 0;
m_bRotary = false;
m_bAdditive = false;
m_ContextRef = 0;
m_pContextMemory = NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
COSXFont::~COSXFont()
{
if ( m_ContextRef )
{
CGContextRelease( m_ContextRef );
}
if ( m_pContextMemory )
delete [] m_pContextMemory;
}
//-----------------------------------------------------------------------------
// Purpose: creates the font from windows. returns false if font does not exist in the OS.
//-----------------------------------------------------------------------------
bool COSXFont::Create(const char *windowsFontName, int tall, int weight, int blur, int scanlines, int flags)
{
// setup font properties
m_szName = windowsFontName;
m_iTall = tall;
m_iWeight = weight;
m_iFlags = flags;
m_bAntiAliased = flags & FONTFLAG_ANTIALIAS;
#if 0
// the font used in portal2 looks ok (better, in fact) anti-aliased when small,
if ( tall < 20 )
{
m_bAntiAliased = false;
}
#endif
m_bUnderlined = flags & FONTFLAG_UNDERLINE;
m_iDropShadowOffset = (flags & FONTFLAG_DROPSHADOW) ? 1 : 0;
m_iOutlineSize = (flags & FONTFLAG_OUTLINE) ? 1 : 0;
m_iBlur = blur;
m_iScanLines = scanlines;
m_bRotary = flags & FONTFLAG_ROTARY;
m_bAdditive = flags & FONTFLAG_ADDITIVE;
char sCustomPath[1024];
Q_snprintf( sCustomPath, sizeof( sCustomPath ), "./platform/vgui/fonts/%s.ttf", windowsFontName );
if ( g_pFullFileSystem->FileExists( sCustomPath ) )
{
CFStringRef path = CFStringCreateWithCString( NULL, windowsFontName, kCFStringEncodingUTF8 );
CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path, kCFURLPOSIXPathStyle, false);
CGDataProviderRef dataProvider = CGDataProviderCreateWithURL( url );
CGFontRef cgFont = CGFontCreateWithDataProvider( dataProvider );
m_hFont = CTFontCreateWithGraphicsFont( cgFont, tall, nullptr, nullptr );
CFRelease( cgFont );
CFRelease( dataProvider );
CFRelease( url );
CFRelease( path );
CTFontCopyCharacterSet(m_hFont);
}
else
{
const void *pKeys[2];
const void *pValues[2];
float fCTWeight = ( (float)( weight - 400 ) / 500.0f );
pKeys[0] = kCTFontWeightTrait;
pValues[0] = CFNumberCreate( NULL, kCFNumberFloatType, &fCTWeight );
float fCTSlant = ( flags & FONTFLAG_ITALIC ) != 0 ? 1.0f : 0.0f;
pKeys[1] = kCTFontSlantTrait;
pValues[1] = CFNumberCreate( NULL, kCFNumberFloatType, &fCTSlant );
CFDictionaryRef pTraitsDict = CFDictionaryCreate( NULL, pKeys, pValues, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
if ( !pTraitsDict )
{
goto Fail;
}
CFRelease( (CFNumberRef)pValues[0] );
CFRelease( (CFNumberRef)pValues[1] );
pKeys[0] = kCTFontNameAttribute;
pValues[0] = CFStringCreateWithCString( NULL, windowsFontName, kCFStringEncodingUTF8 );
pKeys[1] = kCTFontTraitsAttribute;
pValues[1] = pTraitsDict;
CFDictionaryRef pDescDict;
pDescDict = CFDictionaryCreate( NULL, pKeys, pValues, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
CFRelease( (CFStringRef)pValues[0] );
CFRelease( pTraitsDict );
if ( !pDescDict )
{
goto Fail;
}
CTFontDescriptorRef pFontDesc;
pFontDesc = CTFontDescriptorCreateWithAttributes( pDescDict );
CFRelease( pDescDict );
if ( !pFontDesc )
{
goto Fail;
}
// Fudge the size of the font to something reasonable.
m_hFont = CTFontCreateWithFontDescriptor( pFontDesc, int(tall*0.85), NULL );
CFRelease( pFontDesc );
}
if ( !m_hFont )
{
goto Fail;
}
CGRect bbox;
bbox = CTFontGetBoundingBox( m_hFont );
m_iAscent = ceil( CTFontGetAscent( m_hFont ) );
// The bounding box height seems to be overly large so use
// ascent plus descent.
m_iHeight = m_iAscent + ceil( CTFontGetDescent( m_hFont ) ) + m_iDropShadowOffset + 2 * m_iOutlineSize;
m_iMaxCharWidth = ceil( bbox.size.width ) + 2 * m_iOutlineSize;
uint bytesPerRow;
bytesPerRow = m_iMaxCharWidth * 4;
m_pContextMemory = new char[ (int)bytesPerRow * m_iHeight ];
memset( m_pContextMemory, 0x0, (int)( bytesPerRow * m_iHeight) );
CGColorSpaceRef colorSpace;
colorSpace = CGColorSpaceCreateDeviceRGB();
m_ContextRef = CGBitmapContextCreate( m_pContextMemory, m_iMaxCharWidth, m_iHeight,
8,
bytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedLast );
CGColorSpaceRelease( colorSpace );
if ( !m_ContextRef )
{
goto Fail;
}
CGContextSetAllowsAntialiasing( m_ContextRef, m_bAntiAliased );
CGContextSetShouldAntialias( m_ContextRef, m_bAntiAliased );
CGContextSetTextDrawingMode( m_ContextRef, kCGTextFill );
CGContextSetRGBStrokeColor( m_ContextRef, 1.0, 1.0, 1.0, 1.0 );
CGContextSetLineWidth( m_ContextRef, 1 );
return true;
Fail:
return false;
}
void COSXFont::GetKernedCharWidth( wchar_t ch, wchar_t chBefore, wchar_t chAfter, float &wide, float &abcA, float &abcC )
{
int a,b,c;
GetCharABCWidths(ch, a, b, c );
wide = ( a + b + c );
abcA = a;
abcC = c;
}
static bool GetGlyphsForCharacter( CTFontRef hFont, wchar_t ch, CGGlyph* pGlyphs )
{
UniChar pUniChars[2];
pUniChars[0] = ch;
pUniChars[1] = 0;
if ( !CTFontGetGlyphsForCharacters( hFont, pUniChars, pGlyphs, 1 ) )
{
char str[2];
str[0] = (char)ch;
str[1] = 0;
CFStringRef s = CFStringCreateWithCString(nullptr, str, kTextEncodingUnicodeDefault);
pGlyphs[0] = CTFontGetGlyphWithName(hFont, s);
CFRelease( s );
if ( !pGlyphs[0] )
{
return false;
}
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: writes the char into the specified 32bpp texture
//-----------------------------------------------------------------------------
void COSXFont::GetCharRGBA( wchar_t ch, int rgbaWide, int rgbaTall, unsigned char *rgba )
{
wchar_t pWchars[1];
pWchars[0] = (wchar_t)ch;
CGGlyph pGlyphs[1];
if ( !GetGlyphsForCharacter( m_hFont, ch, pGlyphs ) )
{
AssertMsg( false, "CTFontGetGlyphsForCharacters failed" );
return;
}
CGRect rect = { { 0, 0 }, { m_iMaxCharWidth, m_iHeight } };
CGContextClearRect( m_ContextRef, rect );
CGRect pBounds[1];
CTFontGetBoundingRectsForGlyphs( m_hFont, kCTFontDefaultOrientation, pGlyphs, pBounds, 1 );
CGPoint pPositions[1];
// The character will be drawn offset by the 'A' distance so adjust
// it back as this routine only wants the core bits.
pPositions[0].x = m_iOutlineSize;
// The DrawGlyphs coordinate system puts zero Y at the bottom of
// the bitmap and puts the text baseline at zero Y so push
// it up to place characters where we expect them.
pPositions[0].y = ( m_iHeight - m_iAscent ) - m_iOutlineSize;
CTFontDrawGlyphs( m_hFont, pGlyphs, pPositions, 1, m_ContextRef );
CGContextFlush( m_ContextRef );
char *pContextData = (char *)CGBitmapContextGetData( m_ContextRef );
uint8 *pchPixelData = rgba;
for ( int y = 0; y < rgbaTall; y++ )
{
char *row = pContextData + y * m_iMaxCharWidth * 4;
for ( int x = 0; x < rgbaWide; x++ )
{
if ( row[0] || row[1] || row[2] || row[3] )
{
pchPixelData[0] = 0xff;
pchPixelData[1] = 0xff;
pchPixelData[2] = 0xff;
pchPixelData[3] = row[3];
}
else
{
pchPixelData[0] = 0;
pchPixelData[1] = 0;
pchPixelData[2] = 0;
pchPixelData[3] = 0;
}
row += 4;
pchPixelData += 4;
}
}
// Draw top and bottom bars for character placement debugging.
#if FORCE_CHAR_BOX_BOUNDS
pchPixelData = rgba;
for ( int x = 0; x < rgbaWide; x++ )
{
pchPixelData[0] = 0;
pchPixelData[1] = 0;
pchPixelData[2] = 0;
pchPixelData[3] = 0xff;
pchPixelData += 4;
}
pchPixelData = rgba + ( rgbaTall - 1 ) * rgbaWide * 4;
for ( int x = 0; x < rgbaWide; x++ )
{
pchPixelData[0] = 0;
pchPixelData[1] = 0;
pchPixelData[2] = 0;
pchPixelData[3] = 0xff;
pchPixelData += 4;
}
#endif
// apply requested effects in specified order
ApplyDropShadowToTexture( rgbaWide, rgbaTall, rgba, m_iDropShadowOffset );
ApplyOutlineToTexture( rgbaWide, rgbaTall, rgba, m_iOutlineSize );
ApplyGaussianBlurToTexture( rgbaWide, rgbaTall, rgba, m_iBlur );
ApplyScanlineEffectToTexture( rgbaWide, rgbaTall, rgba, m_iScanLines );
ApplyRotaryEffectToTexture( rgbaWide, rgbaTall, rgba, m_bRotary );
}
//-----------------------------------------------------------------------------
// Purpose: gets the abc widths for a character
//-----------------------------------------------------------------------------
void COSXFont::GetCharABCWidths(int ch, int &a, int &b, int &c)
{
Assert(IsValid());
// look for it in the cache
abc_cache_t finder = { (wchar_t)ch };
uint16 i = m_ExtendedABCWidthsCache.Find(finder);
if (m_ExtendedABCWidthsCache.IsValidIndex(i))
{
a = m_ExtendedABCWidthsCache[i].abc.a;
b = m_ExtendedABCWidthsCache[i].abc.b;
c = m_ExtendedABCWidthsCache[i].abc.c;
return;
}
a = 0;
b = 0;
c = 0;
wchar_t pWchars[1];
pWchars[0] = (wchar_t)ch;
CGGlyph pGlyphs[1];
if ( !GetGlyphsForCharacter( m_hFont, ch, pGlyphs ) )
{
AssertMsg( false, "CTFontGetGlyphsForCharacters failed" );
return;
}
CGSize pAdvances[1];
CTFontGetAdvancesForGlyphs( m_hFont, kCTFontDefaultOrientation, pGlyphs, pAdvances, 1 );
CGRect pBounds[1];
CTFontGetBoundingRectsForGlyphs( m_hFont, kCTFontDefaultOrientation, pGlyphs, pBounds, 1 );
a = 0;
b = ceil(pAdvances->width);
c = 0;
finder.abc.a = a;
finder.abc.b = b;
finder.abc.c = c;
m_ExtendedABCWidthsCache.Insert( finder );
}
//-----------------------------------------------------------------------------
// Purpose: returns true if the font is equivalent to that specified
//-----------------------------------------------------------------------------
bool COSXFont::IsEqualTo(const char *windowsFontName, int tall, int weight, int blur, int scanlines, int flags)
{
if (!Q_stricmp(windowsFontName, m_szName.String() )
&& m_iTall == tall
&& m_iWeight == weight
&& m_iBlur == blur
&& m_iFlags == flags)
return true;
return false;
}
//-----------------------------------------------------------------------------
// Purpose: returns true only if this font is valid for use
//-----------------------------------------------------------------------------
bool COSXFont::IsValid()
{
if ( !m_szName.IsEmpty() && m_szName.String()[0] )
return true;
return false;
}
//-----------------------------------------------------------------------------
// Purpose: returns the height of the font, in pixels
//-----------------------------------------------------------------------------
int COSXFont::GetHeight()
{
assert(IsValid());
return m_iHeight;
}
//-----------------------------------------------------------------------------
// Purpose: returns the ascent of the font, in pixels (ascent=units above the base line)
//-----------------------------------------------------------------------------
int COSXFont::GetAscent()
{
assert(IsValid());
return m_iAscent;
}
//-----------------------------------------------------------------------------
// Purpose: returns the ascent of the font, in pixels (ascent=units above the base line)
//-----------------------------------------------------------------------------
int COSXFont::GetDescent()
{
assert(IsValid());
return m_iDescent;
}
//-----------------------------------------------------------------------------
// Purpose: returns the maximum width of a character, in pixels
//-----------------------------------------------------------------------------
int COSXFont::GetMaxCharWidth()
{
assert(IsValid());
return m_iMaxCharWidth;
}
//-----------------------------------------------------------------------------
// Purpose: returns the flags used to make this font, used by the dynamic resizing code
//-----------------------------------------------------------------------------
int COSXFont::GetFlags()
{
return m_iFlags;
}
//-----------------------------------------------------------------------------
// Purpose: Comparison function for abc widths storage
//-----------------------------------------------------------------------------
bool COSXFont::ExtendedABCWidthsCacheLessFunc(const abc_cache_t &lhs, const abc_cache_t &rhs)
{
return lhs.wch < rhs.wch;
}
//-----------------------------------------------------------------------------
// Purpose: Comparison function for abc widths storage
//-----------------------------------------------------------------------------
bool COSXFont::ExtendedKernedABCWidthsCacheLessFunc(const kerned_abc_cache_t &lhs, const kerned_abc_cache_t &rhs)
{
return lhs.wch < rhs.wch || ( lhs.wch == rhs.wch && lhs.wchBefore < rhs.wchBefore )
|| ( lhs.wch == rhs.wch && lhs.wchBefore == rhs.wchBefore && lhs.wchAfter < rhs.wchAfter );
}
void *COSXFont::SetAsActiveFont( CGContextRef cgContext )
{
CGContextSelectFont ( cgContext, m_szName.String(), m_iHeight, kCGEncodingMacRoman);
return NULL;
}
#ifdef DBGFLAG_VALIDATE
//-----------------------------------------------------------------------------
// Purpose: Ensure that all of our internal structures are consistent, and
// account for all memory that we've allocated.
// Input: validator - Our global validator object
// pchName - Our name (typically a member var in our container)
//-----------------------------------------------------------------------------
void COSXFont::Validate( CValidator &validator, char *pchName )
{
validator.Push( "COSXFont", this, pchName );
m_ExtendedABCWidthsCache.Validate( validator, "m_ExtendedABCWidthsCache" );
m_ExtendedKernedABCWidthsCache.Validate( validator, "m_ExtendedKernedABCWidthsCache" );
validator.ClaimMemory( m_pGaussianDistribution );
validator.Pop();
}
#endif // DBGFLAG_VALIDATE