macCarbFont.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "platformMac/macCarbFont.h"
  23. #include "platformMac/platformMacCarb.h"
  24. #include "math/mRect.h"
  25. #include "console/console.h"
  26. #include "core/strings/unicode.h"
  27. #include "core/stringTable.h"
  28. #include "core/strings/stringFunctions.h"
  29. //------------------------------------------------------------------------------
  30. // New Unicode capable font class.
  31. PlatformFont *createPlatformFont(const char *name, U32 size, U32 charset /* = TGE_ANSI_CHARSET */)
  32. {
  33. PlatformFont *retFont = new MacCarbFont;
  34. if(retFont->create(name, size, charset))
  35. return retFont;
  36. delete retFont;
  37. return NULL;
  38. }
  39. //------------------------------------------------------------------------------
  40. MacCarbFont::MacCarbFont()
  41. {
  42. mStyle = NULL;
  43. mLayout = NULL;
  44. mColorSpace = NULL;
  45. }
  46. MacCarbFont::~MacCarbFont()
  47. {
  48. // apple docs say we should dispose the layout first.
  49. ATSUDisposeTextLayout(mLayout);
  50. ATSUDisposeStyle(mStyle);
  51. CGColorSpaceRelease(mColorSpace);
  52. }
  53. //------------------------------------------------------------------------------
  54. bool MacCarbFont::create( const char* name, U32 size, U32 charset)
  55. {
  56. String nameStr = name;
  57. nameStr = nameStr.trim();
  58. // create and cache the style and layout.
  59. // based on apple sample code at http://developer.apple.com/qa/qa2001/qa1027.html
  60. // note: charset is ignored on mac. -- we don't need it to get the right chars.
  61. // But do we need it to translate encodings? hmm...
  62. CFStringRef cfsName;
  63. ATSUFontID atsuFontID;
  64. ATSFontRef atsFontRef;
  65. Fixed atsuSize;
  66. ATSURGBAlphaColor black;
  67. ATSFontMetrics fontMetrics;
  68. U32 scaledSize;
  69. bool isBold = false;
  70. bool isItalic = false;
  71. bool haveModifier;
  72. do
  73. {
  74. haveModifier = false;
  75. if( nameStr.compare( "Bold", 4, String::NoCase | String::Right ) == 0 )
  76. {
  77. isBold = true;
  78. nameStr = nameStr.substr( 0, nameStr.length() - 4 ).trim();
  79. haveModifier = true;
  80. }
  81. if( nameStr.compare( "Italic", 6, String::NoCase | String::Right ) == 0 )
  82. {
  83. isItalic = true;
  84. nameStr = nameStr.substr( 0, nameStr.length() - 6 ).trim();
  85. haveModifier = true;
  86. }
  87. }
  88. while( haveModifier );
  89. // Look up the font. We need it in 2 differnt formats, for differnt Apple APIs.
  90. cfsName = CFStringCreateWithCString( kCFAllocatorDefault, nameStr.c_str(), kCFStringEncodingUTF8);
  91. if(!cfsName)
  92. Con::errorf("Error: could not make a cfstring out of \"%s\" ",nameStr.c_str());
  93. atsFontRef = ATSFontFindFromName( cfsName, kATSOptionFlagsDefault);
  94. atsuFontID = FMGetFontFromATSFontRef( atsFontRef);
  95. // make sure we found it. ATSFontFindFromName() appears to return 0 if it cant find anything. Apple docs contain no info on error returns.
  96. if( !atsFontRef || !atsuFontID )
  97. {
  98. Con::errorf("MacCarbFont::create - could not load font -%s-",name);
  99. return false;
  100. }
  101. // adjust the size. win dpi = 96, mac dpi = 72. 72/96 = .75
  102. // Interestingly enough, 0.75 is not what makes things the right size.
  103. scaledSize = size - 2 - (int)((float)size * 0.1);
  104. mSize = scaledSize;
  105. // Set up the size and color. We send these to ATSUSetAttributes().
  106. atsuSize = IntToFixed(scaledSize);
  107. black.red = black.green = black.blue = black.alpha = 1.0;
  108. // Three parrallel arrays for setting up font, size, and color attributes.
  109. ATSUAttributeTag theTags[] = { kATSUFontTag, kATSUSizeTag, kATSURGBAlphaColorTag};
  110. ByteCount theSizes[] = { sizeof(ATSUFontID), sizeof(Fixed), sizeof(ATSURGBAlphaColor) };
  111. ATSUAttributeValuePtr theValues[] = { &atsuFontID, &atsuSize, &black };
  112. // create and configure the style object.
  113. ATSUCreateStyle(&mStyle);
  114. ATSUSetAttributes( mStyle, 3, theTags, theSizes, theValues );
  115. if( isBold )
  116. {
  117. ATSUAttributeTag tag = kATSUQDBoldfaceTag;
  118. ByteCount size = sizeof( Boolean );
  119. Boolean value = true;
  120. ATSUAttributeValuePtr valuePtr = &value;
  121. ATSUSetAttributes( mStyle, 1, &tag, &size, &valuePtr );
  122. }
  123. if( isItalic )
  124. {
  125. ATSUAttributeTag tag = kATSUQDItalicTag;
  126. ByteCount size = sizeof( Boolean );
  127. Boolean value = true;
  128. ATSUAttributeValuePtr valuePtr = &value;
  129. ATSUSetAttributes( mStyle, 1, &tag, &size, &valuePtr );
  130. }
  131. // create the layout object,
  132. ATSUCreateTextLayout(&mLayout);
  133. // we'll bind the layout to a bitmap context when we actually draw.
  134. // ATSUSetTextPointerLocation() - will set the text buffer
  135. // ATSUSetLayoutControls() - will set the cg context.
  136. // get font metrics, save our baseline and height
  137. ATSFontGetHorizontalMetrics(atsFontRef, kATSOptionFlagsDefault, &fontMetrics);
  138. mBaseline = scaledSize * fontMetrics.ascent;
  139. mHeight = scaledSize * ( fontMetrics.ascent - fontMetrics.descent + fontMetrics.leading ) + 1;
  140. // cache our grey color space, so we dont have to re create it every time.
  141. mColorSpace = CGColorSpaceCreateDeviceGray();
  142. // and finally cache the font's name. We use this to cheat some antialiasing options below.
  143. mName = StringTable->insert(name);
  144. return true;
  145. }
  146. //------------------------------------------------------------------------------
  147. bool MacCarbFont::isValidChar(const UTF8 *str) const
  148. {
  149. // since only low order characters are invalid, and since those characters
  150. // are single codeunits in UTF8, we can safely cast here.
  151. return isValidChar((UTF16)*str);
  152. }
  153. bool MacCarbFont::isValidChar( const UTF16 ch) const
  154. {
  155. // We cut out the ASCII control chars here. Only printable characters are valid.
  156. // 0x20 == 32 == space
  157. if( ch < 0x20 )
  158. return false;
  159. return true;
  160. }
  161. PlatformFont::CharInfo& MacCarbFont::getCharInfo(const UTF8 *str) const
  162. {
  163. return getCharInfo(oneUTF32toUTF16(oneUTF8toUTF32(str,NULL)));
  164. }
  165. PlatformFont::CharInfo& MacCarbFont::getCharInfo(const UTF16 ch) const
  166. {
  167. // We use some static data here to avoid re allocating the same variable in a loop.
  168. // this func is primarily called by GFont::loadCharInfo(),
  169. Rect imageRect;
  170. CGContextRef imageCtx;
  171. U32 bitmapDataSize;
  172. ATSUTextMeasurement tbefore, tafter, tascent, tdescent;
  173. OSStatus err;
  174. // 16 bit character buffer for the ATUSI calls.
  175. // -- hey... could we cache this at the class level, set style and loc *once*,
  176. // then just write to this buffer and clear the layout cache, to speed up drawing?
  177. static UniChar chUniChar[1];
  178. chUniChar[0] = ch;
  179. // Declare and clear out the CharInfo that will be returned.
  180. static PlatformFont::CharInfo c;
  181. dMemset(&c, 0, sizeof(c));
  182. // prep values for GFont::addBitmap()
  183. c.bitmapIndex = 0;
  184. c.xOffset = 0;
  185. c.yOffset = 0;
  186. // put the text in the layout.
  187. // we've hardcoded a string length of 1 here, but this could work for longer strings... (hint hint)
  188. // note: ATSUSetTextPointerLocation() also clears the previous cached layout information.
  189. ATSUSetTextPointerLocation( mLayout, chUniChar, 0, 1, 1);
  190. ATSUSetRunStyle( mLayout, mStyle, 0,1);
  191. // get the typographic bounds. this tells us how characters are placed relative to other characters.
  192. ATSUGetUnjustifiedBounds( mLayout, 0, 1, &tbefore, &tafter, &tascent, &tdescent);
  193. c.xIncrement = FixedToInt(tafter);
  194. // find out how big of a bitmap we'll need.
  195. // as a bonus, we also get the origin where we should draw, encoded in the Rect.
  196. ATSUMeasureTextImage( mLayout, 0, 1, 0, 0, &imageRect);
  197. U32 xFudge = 2;
  198. U32 yFudge = 1;
  199. c.width = imageRect.right - imageRect.left + xFudge; // add 2 because small fonts don't always have enough room
  200. c.height = imageRect.bottom - imageRect.top + yFudge;
  201. c.xOrigin = imageRect.left; // dist x0 -> center line
  202. c.yOrigin = -imageRect.top; // dist y0 -> base line
  203. // kick out early if the character is undrawable
  204. if( c.width == xFudge || c.height == yFudge)
  205. return c;
  206. // allocate a greyscale bitmap and clear it.
  207. bitmapDataSize = c.width * c.height;
  208. c.bitmapData = new U8[bitmapDataSize];
  209. dMemset(c.bitmapData,0x00,bitmapDataSize);
  210. // get a graphics context on the bitmap
  211. imageCtx = CGBitmapContextCreate( c.bitmapData, c.width, c.height, 8, c.width, mColorSpace, kCGImageAlphaNone);
  212. if(!imageCtx) {
  213. Con::errorf("Error: failed to create a graphics context on the CharInfo bitmap! Drawing a blank block.");
  214. c.xIncrement = c.width;
  215. dMemset(c.bitmapData,0x0F,bitmapDataSize);
  216. return c;
  217. }
  218. // Turn off antialiasing for monospaced console fonts. yes, this is cheating.
  219. if(mSize < 12 && ( dStrstr(mName,"Monaco")!=NULL || dStrstr(mName,"Courier")!=NULL ))
  220. CGContextSetShouldAntialias(imageCtx, false);
  221. // Set up drawing options for the context.
  222. // Since we're not going straight to the screen, we need to adjust accordingly
  223. CGContextSetShouldSmoothFonts(imageCtx, false);
  224. CGContextSetRenderingIntent(imageCtx, kCGRenderingIntentAbsoluteColorimetric);
  225. CGContextSetInterpolationQuality( imageCtx, kCGInterpolationNone);
  226. CGContextSetGrayFillColor( imageCtx, 1.0, 1.0);
  227. CGContextSetTextDrawingMode( imageCtx, kCGTextFill);
  228. // tell ATSUI to substitute fonts as needed for missing glyphs
  229. ATSUSetTransientFontMatching(mLayout, true);
  230. // set up three parrallel arrays for setting up attributes.
  231. // this is how most options in ATSUI are set, by passing arrays of options.
  232. ATSUAttributeTag theTags[] = { kATSUCGContextTag };
  233. ByteCount theSizes[] = { sizeof(CGContextRef) };
  234. ATSUAttributeValuePtr theValues[] = { &imageCtx };
  235. // bind the layout to the context.
  236. ATSUSetLayoutControls( mLayout, 1, theTags, theSizes, theValues );
  237. // Draw the character!
  238. int yoff = c.height < 3 ? 1 : 0; // kludge for 1 pixel high characters, such as '-' and '_'
  239. int xoff = 1;
  240. err = ATSUDrawText( mLayout, 0, 1, IntToFixed(-imageRect.left + xoff), IntToFixed(imageRect.bottom + yoff ) );
  241. CGContextRelease(imageCtx);
  242. if(err != noErr) {
  243. Con::errorf("Error: could not draw the character! Drawing a blank box.");
  244. dMemset(c.bitmapData,0x0F,bitmapDataSize);
  245. }
  246. #if TORQUE_DEBUG
  247. // 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);
  248. // Con::printf("Font Bounds: left= %2i right= %2i Char= %C, 0x%x Size= %i",FixedToInt(tbefore), FixedToInt(tafter), ch,ch, mSize);
  249. #endif
  250. return c;
  251. }
  252. void PlatformFont::enumeratePlatformFonts( Vector< StringTableEntry >& fonts, UTF16* fontFamily )
  253. {
  254. if( fontFamily )
  255. {
  256. // Determine the font ID from the family name.
  257. ATSUFontID fontID;
  258. if( ATSUFindFontFromName(
  259. fontFamily,
  260. dStrlen( fontFamily ) * 2,
  261. kFontFamilyName,
  262. kFontMicrosoftPlatform,
  263. kFontNoScriptCode,
  264. kFontNoLanguageCode, &fontID ) != kATSUInvalidFontErr )
  265. {
  266. // Get the number of fonts in the family.
  267. ItemCount numFonts;
  268. ATSUCountFontNames( fontID, &numFonts );
  269. // Read out font names.
  270. U32 bufferSize = 512;
  271. char* buffer = ( char* ) dMalloc( bufferSize );
  272. for( U32 i = 0; i < numFonts; ++ i )
  273. {
  274. for( U32 n = 0; n < 2; ++ n )
  275. {
  276. ByteCount actualNameLength;
  277. FontNameCode fontNameCode;
  278. FontPlatformCode fontPlatformCode;
  279. FontScriptCode fontScriptCode;
  280. FontLanguageCode fontLanguageCode;
  281. if( ATSUGetIndFontName(
  282. fontID,
  283. i,
  284. bufferSize - 2,
  285. buffer,
  286. &actualNameLength,
  287. &fontNameCode,
  288. &fontPlatformCode,
  289. &fontScriptCode,
  290. &fontLanguageCode ) == noErr )
  291. {
  292. *( ( UTF16* ) &buffer[ actualNameLength ] ) = '\0';
  293. char* utf8 = convertUTF16toUTF8( ( UTF16* ) buffer );
  294. fonts.push_back( StringTable->insert( utf8 ) );
  295. delete [] utf8;
  296. break;
  297. }
  298. // Allocate larger buffer.
  299. bufferSize = actualNameLength + 2;
  300. buffer = ( char* ) dRealloc( buffer, bufferSize );
  301. }
  302. }
  303. dFree( buffer );
  304. }
  305. }
  306. else
  307. {
  308. // Get the number of installed fonts.
  309. ItemCount numFonts;
  310. ATSUFontCount( &numFonts );
  311. // Get all the font IDs.
  312. ATSUFontID* fontIDs = new ATSUFontID[ numFonts ];
  313. if( ATSUGetFontIDs( fontIDs, numFonts, &numFonts ) == noErr )
  314. {
  315. U32 bufferSize = 512;
  316. char* buffer = ( char* ) dMalloc( bufferSize );
  317. // Read all family names.
  318. for( U32 i = 0; i < numFonts; ++ i )
  319. {
  320. for( U32 n = 0; n < 2; ++ n )
  321. {
  322. ByteCount actualNameLength;
  323. ItemCount fontIndex;
  324. OSStatus result = ATSUFindFontName(
  325. fontIDs[ i ],
  326. kFontFamilyName,
  327. kFontMicrosoftPlatform,
  328. kFontNoScriptCode,
  329. kFontNoLanguageCode,
  330. bufferSize - 2,
  331. buffer,
  332. &actualNameLength,
  333. &fontIndex );
  334. if( result == kATSUNoFontNameErr )
  335. break;
  336. else if( result == noErr )
  337. {
  338. *( ( UTF16* ) &buffer[ actualNameLength ] ) = '\0';
  339. char* utf8 = convertUTF16toUTF8( ( UTF16* ) buffer );
  340. StringTableEntry name = StringTable->insert( utf8 );
  341. delete [] utf8;
  342. // Avoid duplicates.
  343. bool duplicate = false;
  344. for( U32 i = 0, num = fonts.size(); i < num; ++ i )
  345. if( fonts[ i ] == name )
  346. {
  347. duplicate = true;
  348. break;
  349. }
  350. if( !duplicate )
  351. fonts.push_back( name );
  352. break;
  353. }
  354. // Allocate larger buffer.
  355. bufferSize = actualNameLength + 2;
  356. buffer = ( char* ) dRealloc( buffer, bufferSize );
  357. }
  358. }
  359. dFree( buffer );
  360. }
  361. delete [] fontIDs;
  362. }
  363. }
  364. //-----------------------------------------------------------------------------
  365. // The following code snippet demonstrates how to get the elusive GlyphIDs,
  366. // which are needed when you want to do various complex and arcane things
  367. // with ATSUI and CoreGraphics.
  368. //
  369. // ATSUGlyphInfoArray glyphinfoArr;
  370. // ATSUGetGlyphInfo( mLayout, kATSUFromTextBeginning, kATSUToTextEnd,sizeof(ATSUGlyphInfoArray), &glyphinfoArr);
  371. // ATSUGlyphInfo glyphinfo = glyphinfoArr.glyphs[0];
  372. // Con::printf(" Glyphinfo: screenX= %i, idealX=%f, deltaY=%f", glyphinfo.screenX, glyphinfo.idealX, glyphinfo.deltaY);