/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsATSUIUtils.h" #include "nsIDeviceContext.h" #include "nsDrawingSurfaceMac.h" #include "nsDeviceContextMac.h" #include "nsTransform2D.h" #include "plhash.h" #include "nsFontUtils.h" #include #include //------------------------------------------------------------------------ // ATSUILayoutCache // //------------------------------------------------------------------------ //-------------------------------- // Class definition //-------------------------------- class ATSUILayoutCache { public: ATSUILayoutCache(); ~ATSUILayoutCache(); PRBool Get(short aFont, short aSize, PRBool aBold, PRBool aItalic, nscolor aColor, ATSUTextLayout *aTxlayout); void Set(short aFont, short aSize, PRBool aBold, PRBool aItalic, nscolor aColor, ATSUTextLayout aTxlayout); private: typedef struct { short font; short size; nscolor color; short boldItalic; } atsuiLayoutCacheKey; PRBool Get(atsuiLayoutCacheKey *key, ATSUTextLayout *txlayout); void Set(atsuiLayoutCacheKey *key, ATSUTextLayout txlayout); static PR_CALLBACK PLHashNumber HashKey(const void *aKey); static PR_CALLBACK PRIntn CompareKeys(const void *v1, const void *v2); static PR_CALLBACK PRIntn CompareValues(const void *v1, const void *v2); static PR_CALLBACK PRIntn FreeHashEntries(PLHashEntry *he, PRIntn italic, void *arg); struct PLHashTable* mTable; PRUint32 mCount; }; ATSUILayoutCache::ATSUILayoutCache() { mTable = PL_NewHashTable(8, (PLHashFunction)HashKey, (PLHashComparator)CompareKeys, (PLHashComparator)CompareValues, nsnull, nsnull); mCount = 0; } //-------------------------------- // Public methods //-------------------------------- ATSUILayoutCache::~ATSUILayoutCache() { if (mTable) { PL_HashTableEnumerateEntries(mTable, FreeHashEntries, 0); PL_HashTableDestroy(mTable); mTable = nsnull; } } PRBool ATSUILayoutCache::Get(short aFont, short aSize, PRBool aBold, PRBool aItalic, nscolor aColor, ATSUTextLayout *aTxlayout) { atsuiLayoutCacheKey k = {aFont, aSize, aColor, (aBold ? 1 : 0) + (aItalic ? 2 : 0) }; return Get(&k, aTxlayout); } void ATSUILayoutCache::Set(short aFont, short aSize, PRBool aBold, PRBool aItalic, nscolor aColor, ATSUTextLayout aTxlayout) { atsuiLayoutCacheKey k = {aFont, aSize, aColor, (aBold ? 1 : 0) + (aItalic ? 2 : 0) }; return Set(&k, aTxlayout); } //-------------------------------- // Private methods //-------------------------------- PRBool ATSUILayoutCache::Get(atsuiLayoutCacheKey *key, ATSUTextLayout *txlayout) { PLHashEntry **hep = PL_HashTableRawLookup(mTable, HashKey(key), key); PLHashEntry *he = *hep; if( he ) { *txlayout = (ATSUTextLayout)he->value; return PR_TRUE; } return PR_FALSE; } void ATSUILayoutCache::Set(atsuiLayoutCacheKey *key, ATSUTextLayout txlayout) { atsuiLayoutCacheKey *newKey = new atsuiLayoutCacheKey; if (newKey) { *newKey = *key; PL_HashTableAdd(mTable, newKey, txlayout); mCount ++; } } PR_CALLBACK PLHashNumber ATSUILayoutCache::HashKey(const void *aKey) { atsuiLayoutCacheKey* key = (atsuiLayoutCacheKey*)aKey; return key->font + (key->size << 7) + (key->boldItalic << 12) + key->color; } PR_CALLBACK PRIntn ATSUILayoutCache::CompareKeys(const void *v1, const void *v2) { atsuiLayoutCacheKey *k1 = (atsuiLayoutCacheKey *)v1; atsuiLayoutCacheKey *k2 = (atsuiLayoutCacheKey *)v2; return (k1->font == k2->font) && (k1->color == k2->color ) && (k1->size == k2->size) && (k1->boldItalic == k2->boldItalic); } PR_CALLBACK PRIntn ATSUILayoutCache::CompareValues(const void *v1, const void *v2) { ATSUTextLayout t1 = (ATSUTextLayout)v1; ATSUTextLayout t2 = (ATSUTextLayout)v2; return (t1 == t2); } PR_CALLBACK PRIntn ATSUILayoutCache::FreeHashEntries(PLHashEntry *he, PRIntn italic, void *arg) { delete (atsuiLayoutCacheKey*)he->key; ATSUDisposeTextLayout((ATSUTextLayout)he->value); return HT_ENUMERATE_REMOVE; } #pragma mark - //------------------------------------------------------------------------ // nsATSUIUtils // //------------------------------------------------------------------------ //-------------------------------- // globals //-------------------------------- ATSUILayoutCache* nsATSUIUtils::gTxLayoutCache = nsnull; PRBool nsATSUIUtils::gIsAvailable = PR_FALSE; PRBool nsATSUIUtils::gInitialized = PR_FALSE; fpMeasureText_type nsATSUIUtils::fpMeasureText = nsnull; //-------------------------------- // Initialize //-------------------------------- void nsATSUIUtils::Initialize() { if (!gInitialized) { long version; gIsAvailable = (::Gestalt(gestaltATSUVersion, &version) == noErr); gTxLayoutCache = new ATSUILayoutCache(); if (!gTxLayoutCache) gIsAvailable = PR_FALSE; // ATSUMeasureText is deprecated starting in Mac OS X 10.2, and replaced by // ATSUGetUnjustifiedBounds. Try to dynamically load the latter. CFBundleRef bundle = ::CFBundleGetBundleWithIdentifier(CFSTR("com.apple.ApplicationServices")); if (bundle) { // We dynamically load the function and only use if available (OS X 10.2+) fpMeasureText = (fpMeasureText_type) ::CFBundleGetFunctionPointerForName(bundle, CFSTR("ATSUGetUnjustifiedBounds")); } if (fpMeasureText == NULL) { // If the function is not found (which would happen on OS X 10.1), then // default to using ATSUMeasureText. bundle = ::CFBundleGetBundleWithIdentifier(CFSTR("com.apple.Carbon")); if (bundle) { fpMeasureText = (fpMeasureText_type) ::CFBundleGetFunctionPointerForName(bundle, CFSTR("ATSUMeasureText")); } } gInitialized = PR_TRUE; } } //-------------------------------- // IsAvailable //-------------------------------- PRBool nsATSUIUtils::IsAvailable() { return gIsAvailable; } #pragma mark - //------------------------------------------------------------------------ // nsATSUIToolkit // //------------------------------------------------------------------------ nsATSUIToolkit::nsATSUIToolkit() { nsATSUIUtils::Initialize(); } //------------------------------------------------------------------------ // GetTextLayout // //------------------------------------------------------------------------ #define FloatToFixed(a) ((Fixed)((float)(a) * fixed1)) //------------------------------------------------------------------------ ATSUTextLayout nsATSUIToolkit::GetTextLayout(short aFontNum, short aSize, PRBool aBold, PRBool aItalic, nscolor aColor) { ATSUTextLayout txLayout = nsnull; OSStatus err; if (nsATSUIUtils::gTxLayoutCache->Get(aFontNum, aSize, aBold, aItalic, aColor, &txLayout)) return txLayout; UniChar dmy[1]; err = ::ATSUCreateTextLayoutWithTextPtr (dmy, 0,0,0,0,NULL, NULL, &txLayout); if(noErr != err) { NS_WARNING("ATSUCreateTextLayoutWithTextPtr failed"); // goto errorDone; return nsnull; } ATSUStyle theStyle; err = ::ATSUCreateStyle(&theStyle); if(noErr != err) { NS_WARNING("ATSUCreateStyle failed"); // goto errorDoneDestroyTextLayout; err = ::ATSUDisposeTextLayout(txLayout); return nsnull; } ATSUAttributeTag theTag[3]; ByteCount theValueSize[3]; ATSUAttributeValuePtr theValue[3]; //--- Font ID & Face ----- ATSUFontID atsuFontID; err = ::ATSUFONDtoFontID(aFontNum, (StyleField)((aBold ? bold : normal) | (aItalic ? italic : normal)), &atsuFontID); if(noErr != err) { NS_WARNING("ATSUFONDtoFontID failed"); // goto errorDoneDestroyStyle; err = ::ATSUDisposeStyle(theStyle); err = ::ATSUDisposeTextLayout(txLayout); return nsnull; } theTag[0] = kATSUFontTag; theValueSize[0] = (ByteCount) sizeof(ATSUFontID); theValue[0] = (ATSUAttributeValuePtr) &atsuFontID; //--- Font ID & Face ----- //--- Size ----- float dev2app; short fontsize = aSize; dev2app = mContext->DevUnitsToAppUnits(); // Fixed size = FloatToFixed( roundf(float(fontsize) / dev2app)); Fixed size = FloatToFixed( (float) rint(float(fontsize) / dev2app)); if( FixRound ( size ) < 9 && !nsFontUtils::DisplayVerySmallFonts()) size = X2Fix(9); theTag[1] = kATSUSizeTag; theValueSize[1] = (ByteCount) sizeof(Fixed); theValue[1] = (ATSUAttributeValuePtr) &size; //--- Size ----- //--- Color ----- RGBColor color; #define COLOR8TOCOLOR16(color8) ((color8 << 8) | color8) color.red = COLOR8TOCOLOR16(NS_GET_R(aColor)); color.green = COLOR8TOCOLOR16(NS_GET_G(aColor)); color.blue = COLOR8TOCOLOR16(NS_GET_B(aColor)); theTag[2] = kATSUColorTag; theValueSize[2] = (ByteCount) sizeof(RGBColor); theValue[2] = (ATSUAttributeValuePtr) &color; //--- Color ----- err = ::ATSUSetAttributes(theStyle, 3, theTag, theValueSize, theValue); if(noErr != err) { NS_WARNING("ATSUSetAttributes failed"); // goto errorDoneDestroyStyle; err = ::ATSUDisposeStyle(theStyle); err = ::ATSUDisposeTextLayout(txLayout); return nsnull; } err = ::ATSUSetRunStyle(txLayout, theStyle, kATSUFromTextBeginning, kATSUToTextEnd); if(noErr != err) { NS_WARNING("ATSUSetRunStyle failed"); // goto errorDoneDestroyStyle; err = ::ATSUDisposeStyle(theStyle); err = ::ATSUDisposeTextLayout(txLayout); return nsnull; } err = ::ATSUSetTransientFontMatching(txLayout, true); if(noErr != err) { NS_WARNING( "ATSUSetTransientFontMatching failed"); // goto errorDoneDestroyStyle; err = ::ATSUDisposeStyle(theStyle); err = ::ATSUDisposeTextLayout(txLayout); return nsnull; } nsATSUIUtils::gTxLayoutCache->Set(aFontNum, aSize, aBold, aItalic, aColor, txLayout); return txLayout; } //------------------------------------------------------------------------ // PrepareToDraw // //------------------------------------------------------------------------ void nsATSUIToolkit::PrepareToDraw(CGrafPtr aPort, nsIDeviceContext* aContext) { mPort = aPort; mContext = aContext; } //------------------------------------------------------------------------ // StartDraw // //------------------------------------------------------------------------ void nsATSUIToolkit::StartDraw( const PRUnichar *aCharPt, PRUint32 aLen, short aSize, short aFontNum, PRBool aBold, PRBool aItalic, nscolor aColor, ATSUTextLayout& oLayout) { OSStatus err = noErr; oLayout = GetTextLayout(aFontNum, aSize, aBold, aItalic, aColor); if (nsnull == oLayout) { NS_WARNING("GetTextLayout return nsnull"); return; } // ATSUSetTextPointerLocation won't invalidate atsui's internal cache if aCharPt is // the same address it already has. therefore, since we are definitely changing the // text here, we should explicitly invalidate any existing caches ::ATSUClearLayoutCache(oLayout, kATSUFromTextBeginning); err = ::ATSUSetTextPointerLocation( oLayout, (ConstUniCharArrayPtr)aCharPt, 0, aLen, aLen); if (noErr != err) { NS_WARNING("ATSUSetTextPointerLocation failed"); oLayout = nsnull; } return; } //------------------------------------------------------------------------ // GetWidth // //------------------------------------------------------------------------ nsresult nsATSUIToolkit::GetTextDimensions( const PRUnichar *aCharPt, PRUint32 aLen, nsTextDimensions& oDim, short aSize, short aFontNum, PRBool aBold, PRBool aItalic, nscolor aColor) { if (!nsATSUIUtils::IsAvailable()) return NS_ERROR_NOT_INITIALIZED; StPortSetter setter(mPort); ATSUTextLayout aTxtLayout; StartDraw(aCharPt, aLen, aSize, aFontNum, aBold, aItalic, aColor, aTxtLayout); if (nsnull == aTxtLayout) return NS_ERROR_FAILURE; OSStatus err = noErr; ATSUTextMeasurement after; ATSUTextMeasurement ascent; ATSUTextMeasurement descent; err = nsATSUIUtils::fpMeasureText(aTxtLayout, 0, aLen, NULL, &after, &ascent, &descent); if (noErr != err) { NS_WARNING("MeasureText failed"); return NS_ERROR_FAILURE; } oDim.width = FixRound(after); oDim.ascent = FixRound(ascent); oDim.descent = FixRound(descent); // aTxtLayout is cached and does not need to be disposed return NS_OK; } #ifdef MOZ_MATHML //------------------------------------------------------------------------ // GetBoundingMetrics // //------------------------------------------------------------------------ nsresult nsATSUIToolkit::GetBoundingMetrics( const PRUnichar *aCharPt, PRUint32 aLen, nsBoundingMetrics &oBoundingMetrics, short aSize, short aFontNum, PRBool aBold, PRBool aItalic, nscolor aColor) { if(!nsATSUIUtils::IsAvailable()) return NS_ERROR_NOT_INITIALIZED; StPortSetter setter(mPort); ATSUTextLayout aTxtLayout; StartDraw(aCharPt, aLen, aSize, aFontNum, aBold, aItalic, aColor, aTxtLayout); if(nsnull == aTxtLayout) return NS_ERROR_FAILURE; OSStatus err = noErr; Rect rect; ATSUTextMeasurement width; if((err = ATSUMeasureTextImage(aTxtLayout, kATSUFromTextBeginning, kATSUToTextEnd, 0, 0, &rect)) != noErr) { NS_WARNING("ATSUMeasureTextImage failed"); return NS_ERROR_FAILURE; } // return the values in points, the caller will convert them into twips oBoundingMetrics.leftBearing = rect.left; oBoundingMetrics.rightBearing = rect.right; oBoundingMetrics.ascent = -rect.top; oBoundingMetrics.descent = rect.bottom; err = nsATSUIUtils::fpMeasureText(aTxtLayout, kATSUFromTextBeginning, kATSUToTextEnd, NULL, &width, NULL, NULL); if (err != noErr) { oBoundingMetrics.width = oBoundingMetrics.rightBearing; } else oBoundingMetrics.width = FixRound(width); return NS_OK; } #endif // MOZ_MATHML //------------------------------------------------------------------------ // DrawString // //------------------------------------------------------------------------ nsresult nsATSUIToolkit::DrawString( const PRUnichar *aCharPt, PRUint32 aLen, PRInt32 x, PRInt32 y, short &oWidth, short aSize, short aFontNum, PRBool aBold, PRBool aItalic, nscolor aColor) { oWidth = 0; if (!nsATSUIUtils::IsAvailable()) return NS_ERROR_NOT_INITIALIZED; StPortSetter setter(mPort); ATSUTextLayout aTxtLayout; StartDraw(aCharPt, aLen, aSize, aFontNum, aBold, aItalic, aColor, aTxtLayout); if (nsnull == aTxtLayout) return NS_ERROR_FAILURE; OSStatus err = noErr; ATSUTextMeasurement iAfter; err = nsATSUIUtils::fpMeasureText(aTxtLayout, 0, aLen, NULL, &iAfter, NULL, NULL); if (noErr != err) { NS_WARNING("MeasureText failed"); return NS_ERROR_FAILURE; } err = ::ATSUDrawText(aTxtLayout, 0, aLen, Long2Fix(x), Long2Fix(y)); if (noErr != err) { NS_WARNING("ATSUDrawText failed"); return NS_ERROR_FAILURE; } oWidth = FixRound(iAfter); // aTxtLayout is cached and does not need to be disposed return NS_OK; }