tge/engine/platformMacCarb/macCarbFont.cc
2017-04-17 06:17:10 -06:00

253 lines
10 KiB
C++
Executable File

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platformMacCarb/platformMacCarb.h"
#include "platformMacCarb/macCarbFont.h"
#include "dgl/gFont.h"
#include "dgl/gBitmap.h"
#include "Math/mRect.h"
#include "console/console.h"
#include "core/unicode.h"
//------------------------------------------------------------------------------
// New Unicode capable font class.
PlatformFont *createPlatformFont(const char *name, U32 size, U32 charset /* = TGE_ANSI_CHARSET */)
{
PlatformFont *retFont = new MacCarbFont;
if(retFont->create(name, size, charset))
return retFont;
delete retFont;
return NULL;
}
//------------------------------------------------------------------------------
MacCarbFont::MacCarbFont()
{
mStyle = NULL;
mLayout = NULL;
mColorSpace = NULL;
}
MacCarbFont::~MacCarbFont()
{
// apple docs say we should dispose the layout first.
ATSUDisposeTextLayout(mLayout);
ATSUDisposeStyle(mStyle);
CGColorSpaceRelease(mColorSpace);
}
//------------------------------------------------------------------------------
bool MacCarbFont::create( const char *name, U32 size, U32 charset)
{
// create and cache the style and layout.
// based on apple sample code at http://developer.apple.com/qa/qa2001/qa1027.html
// note: charset is ignored on mac. -- we don't need it to get the right chars.
// But do we need it to translate encodings? hmm...
CFStringRef cfsName;
ATSUFontID atsuFontID;
ATSFontRef atsFontRef;
Fixed atsuSize;
ATSURGBAlphaColor black;
ATSFontMetrics fontMetrics;
U32 scaledSize;
// Look up the font. We need it in 2 differnt formats, for differnt Apple APIs.
cfsName = CFStringCreateWithCString( kCFAllocatorDefault, name, kCFStringEncodingUTF8);
if(!cfsName)
Con::errorf("Error: could not make a cfstring out of \"%s\" ",name);
atsFontRef = ATSFontFindFromName( cfsName, kATSOptionFlagsDefault);
atsuFontID = FMGetFontFromATSFontRef( atsFontRef);
// make sure we found it. ATSFontFindFromName() appears to return 0 if it cant find anything. Apple docs contain no info on error returns.
if( !atsFontRef || !atsuFontID )
{
Con::errorf("MacCarbFont::create - could not load font -%s-",name);
return false;
}
// adjust the size. win dpi = 96, mac dpi = 72. 72/96 = .75
// Interestingly enough, 0.75 is not what makes things the right size.
scaledSize = size - 2 - (int)((float)size * 0.1);
mSize = scaledSize;
// Set up the size and color. We send these to ATSUSetAttributes().
atsuSize = IntToFixed(scaledSize);
black.red = black.green = black.blue = black.alpha = 1.0;
// Three parrallel arrays for setting up font, size, and color attributes.
ATSUAttributeTag theTags[] = { kATSUFontTag, kATSUSizeTag, kATSURGBAlphaColorTag};
ByteCount theSizes[] = { sizeof(ATSUFontID), sizeof(Fixed), sizeof(ATSURGBAlphaColor) };
ATSUAttributeValuePtr theValues[] = { &atsuFontID, &atsuSize, &black };
// create and configure the style object.
ATSUCreateStyle(&mStyle);
ATSUSetAttributes( mStyle, 3, theTags, theSizes, theValues );
// create the layout object,
ATSUCreateTextLayout(&mLayout);
// we'll bind the layout to a bitmap context when we actually draw.
// ATSUSetTextPointerLocation() - will set the text buffer
// ATSUSetLayoutControls() - will set the cg context.
// get font metrics, save our baseline and height
ATSFontGetHorizontalMetrics(atsFontRef, kATSOptionFlagsDefault, &fontMetrics);
mBaseline = scaledSize * fontMetrics.ascent;
mHeight = scaledSize * ( fontMetrics.ascent - fontMetrics.descent + fontMetrics.leading ) + 1;
// cache our grey color space, so we dont have to re create it every time.
mColorSpace = CGColorSpaceCreateDeviceGray();
// and finally cache the font's name. We use this to cheat some antialiasing options below.
mName = StringTable->insert(name);
return true;
}
//------------------------------------------------------------------------------
bool MacCarbFont::isValidChar(const UTF8 *str) const
{
// since only low order characters are invalid, and since those characters
// are single codeunits in UTF8, we can safely cast here.
return isValidChar((UTF16)*str);
}
bool MacCarbFont::isValidChar( const UTF16 ch) const
{
// We cut out the ASCII control chars here. Only printable characters are valid.
// 0x20 == 32 == space
if( ch < 0x20 )
return false;
return true;
}
PlatformFont::CharInfo& MacCarbFont::getCharInfo(const UTF8 *str) const
{
return getCharInfo(oneUTF32toUTF16(oneUTF8toUTF32(str,NULL)));
}
PlatformFont::CharInfo& MacCarbFont::getCharInfo(const UTF16 ch) const
{
// We use some static data here to avoid re allocating the same variable in a loop.
// this func is primarily called by GFont::loadCharInfo(),
Rect imageRect;
CGContextRef imageCtx;
U32 bitmapDataSize;
ATSUTextMeasurement tbefore, tafter, tascent, tdescent;
OSStatus err;
// 16 bit character buffer for the ATUSI calls.
// -- hey... could we cache this at the class level, set style and loc *once*,
// then just write to this buffer and clear the layout cache, to speed up drawing?
static UniChar chUniChar[1];
chUniChar[0] = ch;
// Declare and clear out the CharInfo that will be returned.
static PlatformFont::CharInfo c;
dMemset(&c, 0, sizeof(c));
// prep values for GFont::addBitmap()
c.bitmapIndex = 0;
c.xOffset = 0;
c.yOffset = 0;
// put the text in the layout.
// we've hardcoded a string length of 1 here, but this could work for longer strings... (hint hint)
// note: ATSUSetTextPointerLocation() also clears the previous cached layout information.
ATSUSetTextPointerLocation( mLayout, chUniChar, 0, 1, 1);
ATSUSetRunStyle( mLayout, mStyle, 0,1);
// get the typographic bounds. this tells us how characters are placed relative to other characters.
ATSUGetUnjustifiedBounds( mLayout, 0, 1, &tbefore, &tafter, &tascent, &tdescent);
c.xIncrement = FixedToInt(tafter);
// find out how big of a bitmap we'll need.
// as a bonus, we also get the origin where we should draw, encoded in the Rect.
ATSUMeasureTextImage( mLayout, 0, 1, 0, 0, &imageRect);
U32 xFudge = 2;
U32 yFudge = 1;
c.width = imageRect.right - imageRect.left + xFudge; // add 2 because small fonts don't always have enough room
c.height = imageRect.bottom - imageRect.top + yFudge;
c.xOrigin = imageRect.left; // dist x0 -> center line
c.yOrigin = -imageRect.top; // dist y0 -> base line
// kick out early if the character is undrawable
if( c.width == xFudge || c.height == yFudge)
return c;
// allocate a greyscale bitmap and clear it.
bitmapDataSize = c.width * c.height;
c.bitmapData = new U8[bitmapDataSize];
dMemset(c.bitmapData,0x00,bitmapDataSize);
// get a graphics context on the bitmap
imageCtx = CGBitmapContextCreate( c.bitmapData, c.width, c.height, 8, c.width, mColorSpace, kCGImageAlphaNone);
if(!imageCtx) {
Con::errorf("Error: failed to create a graphics context on the CharInfo bitmap! Drawing a blank block.");
c.xIncrement = c.width;
dMemset(c.bitmapData,0x0F,bitmapDataSize);
return c;
}
// Turn off antialiasing for monospaced console fonts. yes, this is cheating.
if(mSize < 12 && ( dStrstr(mName,"Monaco")!=NULL || dStrstr(mName,"Courier")!=NULL ))
CGContextSetShouldAntialias(imageCtx, false);
// Set up drawing options for the context.
// Since we're not going straight to the screen, we need to adjust accordingly
CGContextSetShouldSmoothFonts(imageCtx, false);
CGContextSetRenderingIntent(imageCtx, kCGRenderingIntentAbsoluteColorimetric);
CGContextSetInterpolationQuality( imageCtx, kCGInterpolationNone);
CGContextSetGrayFillColor( imageCtx, 1.0, 1.0);
CGContextSetTextDrawingMode( imageCtx, kCGTextFill);
// tell ATSUI to substitute fonts as needed for missing glyphs
ATSUSetTransientFontMatching(mLayout, true);
// set up three parrallel arrays for setting up attributes.
// this is how most options in ATSUI are set, by passing arrays of options.
ATSUAttributeTag theTags[] = { kATSUCGContextTag };
ByteCount theSizes[] = { sizeof(CGContextRef) };
ATSUAttributeValuePtr theValues[] = { &imageCtx };
// bind the layout to the context.
ATSUSetLayoutControls( mLayout, 1, theTags, theSizes, theValues );
// Draw the character!
int yoff = c.height < 3 ? 1 : 0; // kludge for 1 pixel high characters, such as '-' and '_'
int xoff = 1;
err = ATSUDrawText( mLayout, 0, 1, IntToFixed(-imageRect.left + xoff), IntToFixed(imageRect.bottom + yoff ) );
CGContextRelease(imageCtx);
if(err != noErr) {
Con::errorf("Error: could not draw the character! Drawing a blank box.");
dMemset(c.bitmapData,0x0F,bitmapDataSize);
}
#if TORQUE_DEBUG
// Con::printf("Font Metrics: Rect = %2i %2i %2i %2i Char= %C, 0x%x Size= %i, Baseline= %i, Height= %i",imageRect.top, imageRect.bottom, imageRect.left, imageRect.right,ch,ch, mSize,mBaseline, mHeight);
// Con::printf("Font Bounds: left= %2i right= %2i Char= %C, 0x%x Size= %i",FixedToInt(tbefore), FixedToInt(tafter), ch,ch, mSize);
#endif
return c;
}
//-----------------------------------------------------------------------------
// The following code snippet demonstrates how to get the elusive GlyphIDs,
// which are needed when you want to do various complex and arcane things
// with ATSUI and CoreGraphics.
//
// ATSUGlyphInfoArray glyphinfoArr;
// ATSUGetGlyphInfo( mLayout, kATSUFromTextBeginning, kATSUToTextEnd,sizeof(ATSUGlyphInfoArray), &glyphinfoArr);
// ATSUGlyphInfo glyphinfo = glyphinfoArr.glyphs[0];
// Con::printf(" Glyphinfo: screenX= %i, idealX=%f, deltaY=%f", glyphinfo.screenX, glyphinfo.idealX, glyphinfo.deltaY);