123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449 |
- //-----------------------------------------------------------------------------
- // Copyright (c) 2012 GarageGames, LLC
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to
- // deal in the Software without restriction, including without limitation the
- // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- // sell copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- // IN THE SOFTWARE.
- //-----------------------------------------------------------------------------
- #include "platformMac/macCarbFont.h"
- #include "platformMac/platformMacCarb.h"
- #include "math/mRect.h"
- #include "console/console.h"
- #include "core/strings/unicode.h"
- #include "core/stringTable.h"
- #include "core/strings/stringFunctions.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)
- {
- String nameStr = name;
- nameStr = nameStr.trim();
-
- // 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;
-
- bool isBold = false;
- bool isItalic = false;
-
- bool haveModifier;
- do
- {
- haveModifier = false;
- if( nameStr.compare( "Bold", 4, String::NoCase | String::Right ) == 0 )
- {
- isBold = true;
- nameStr = nameStr.substr( 0, nameStr.length() - 4 ).trim();
- haveModifier = true;
- }
- if( nameStr.compare( "Italic", 6, String::NoCase | String::Right ) == 0 )
- {
- isItalic = true;
- nameStr = nameStr.substr( 0, nameStr.length() - 6 ).trim();
- haveModifier = true;
- }
- }
- while( haveModifier );
-
- // Look up the font. We need it in 2 differnt formats, for differnt Apple APIs.
- cfsName = CFStringCreateWithCString( kCFAllocatorDefault, nameStr.c_str(), kCFStringEncodingUTF8);
- if(!cfsName)
- Con::errorf("Error: could not make a cfstring out of \"%s\" ",nameStr.c_str());
-
- 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 );
-
- if( isBold )
- {
- ATSUAttributeTag tag = kATSUQDBoldfaceTag;
- ByteCount size = sizeof( Boolean );
- Boolean value = true;
- ATSUAttributeValuePtr valuePtr = &value;
- ATSUSetAttributes( mStyle, 1, &tag, &size, &valuePtr );
- }
-
- if( isItalic )
- {
- ATSUAttributeTag tag = kATSUQDItalicTag;
- ByteCount size = sizeof( Boolean );
- Boolean value = true;
- ATSUAttributeValuePtr valuePtr = &value;
- ATSUSetAttributes( mStyle, 1, &tag, &size, &valuePtr );
- }
-
- // 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;
- }
- void PlatformFont::enumeratePlatformFonts( Vector< StringTableEntry >& fonts, UTF16* fontFamily )
- {
- if( fontFamily )
- {
- // Determine the font ID from the family name.
-
- ATSUFontID fontID;
- if( ATSUFindFontFromName(
- fontFamily,
- dStrlen( fontFamily ) * 2,
- kFontFamilyName,
- kFontMicrosoftPlatform,
- kFontNoScriptCode,
- kFontNoLanguageCode, &fontID ) != kATSUInvalidFontErr )
- {
- // Get the number of fonts in the family.
-
- ItemCount numFonts;
- ATSUCountFontNames( fontID, &numFonts );
-
- // Read out font names.
-
- U32 bufferSize = 512;
- char* buffer = ( char* ) dMalloc( bufferSize );
-
- for( U32 i = 0; i < numFonts; ++ i )
- {
- for( U32 n = 0; n < 2; ++ n )
- {
- ByteCount actualNameLength;
- FontNameCode fontNameCode;
- FontPlatformCode fontPlatformCode;
- FontScriptCode fontScriptCode;
- FontLanguageCode fontLanguageCode;
-
- if( ATSUGetIndFontName(
- fontID,
- i,
- bufferSize - 2,
- buffer,
- &actualNameLength,
- &fontNameCode,
- &fontPlatformCode,
- &fontScriptCode,
- &fontLanguageCode ) == noErr )
- {
- *( ( UTF16* ) &buffer[ actualNameLength ] ) = '\0';
- char* utf8 = convertUTF16toUTF8( ( UTF16* ) buffer );
- fonts.push_back( StringTable->insert( utf8 ) );
- delete [] utf8;
- break;
- }
-
- // Allocate larger buffer.
-
- bufferSize = actualNameLength + 2;
- buffer = ( char* ) dRealloc( buffer, bufferSize );
- }
- }
-
- dFree( buffer );
- }
- }
- else
- {
- // Get the number of installed fonts.
-
- ItemCount numFonts;
- ATSUFontCount( &numFonts );
-
- // Get all the font IDs.
-
- ATSUFontID* fontIDs = new ATSUFontID[ numFonts ];
- if( ATSUGetFontIDs( fontIDs, numFonts, &numFonts ) == noErr )
- {
- U32 bufferSize = 512;
- char* buffer = ( char* ) dMalloc( bufferSize );
-
- // Read all family names.
-
- for( U32 i = 0; i < numFonts; ++ i )
- {
- for( U32 n = 0; n < 2; ++ n )
- {
- ByteCount actualNameLength;
- ItemCount fontIndex;
-
- OSStatus result = ATSUFindFontName(
- fontIDs[ i ],
- kFontFamilyName,
- kFontMicrosoftPlatform,
- kFontNoScriptCode,
- kFontNoLanguageCode,
- bufferSize - 2,
- buffer,
- &actualNameLength,
- &fontIndex );
-
- if( result == kATSUNoFontNameErr )
- break;
- else if( result == noErr )
- {
- *( ( UTF16* ) &buffer[ actualNameLength ] ) = '\0';
- char* utf8 = convertUTF16toUTF8( ( UTF16* ) buffer );
- StringTableEntry name = StringTable->insert( utf8 );
- delete [] utf8;
-
- // Avoid duplicates.
-
- bool duplicate = false;
- for( U32 i = 0, num = fonts.size(); i < num; ++ i )
- if( fonts[ i ] == name )
- {
- duplicate = true;
- break;
- }
-
- if( !duplicate )
- fonts.push_back( name );
-
- break;
- }
-
- // Allocate larger buffer.
-
- bufferSize = actualNameLength + 2;
- buffer = ( char* ) dRealloc( buffer, bufferSize );
- }
- }
-
- dFree( buffer );
- }
-
- delete [] fontIDs;
- }
- }
- //-----------------------------------------------------------------------------
- // 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);
|