зеркало из https://github.com/mozilla/pjs.git
Bug 375760. Implement new textrun cache: gfxTextRunCache (base cache which leaves textrun lifetime management to the client) and gfxGlobalTextRunCache (cache which manages textrun lifetimes using nsExpirationTracker). r=vlad
This commit is contained in:
Родитель
ea66fe68a8
Коммит
a38734a09b
|
@ -159,28 +159,40 @@ protected:
|
|||
public:
|
||||
AutoTextRun(nsThebesFontMetrics* aMetrics, nsIRenderingContext* aRC,
|
||||
const char* aString, PRInt32 aLength, PRBool aEnableSpacing) {
|
||||
mTextRun = gfxTextRunCache::GetCache()->GetOrMakeTextRun(
|
||||
mTextRun = gfxGlobalTextRunCache::GetTextRun(
|
||||
NS_REINTERPRET_CAST(const PRUint8*, aString), aLength,
|
||||
aMetrics->mFontGroup,
|
||||
NS_STATIC_CAST(gfxContext*, aRC->GetNativeGraphicData(nsIRenderingContext::NATIVE_THEBES_CONTEXT)),
|
||||
aMetrics->mFontGroup, aString, aLength, aMetrics->mP2A,
|
||||
aMetrics->GetRightToLeftTextRunMode(), aEnableSpacing, &mOwning);
|
||||
aMetrics->mP2A,
|
||||
ComputeFlags(aMetrics, aEnableSpacing));
|
||||
}
|
||||
AutoTextRun(nsThebesFontMetrics* aMetrics, nsIRenderingContext* aRC,
|
||||
const PRUnichar* aString, PRInt32 aLength, PRBool aEnableSpacing) {
|
||||
mTextRun = gfxTextRunCache::GetCache()->GetOrMakeTextRun(
|
||||
mTextRun = gfxGlobalTextRunCache::GetTextRun(
|
||||
aString, aLength, aMetrics->mFontGroup,
|
||||
NS_STATIC_CAST(gfxContext*, aRC->GetNativeGraphicData(nsIRenderingContext::NATIVE_THEBES_CONTEXT)),
|
||||
aMetrics->mFontGroup, aString, aLength, aMetrics->mP2A,
|
||||
aMetrics->GetRightToLeftTextRunMode(), aEnableSpacing, &mOwning);
|
||||
}
|
||||
~AutoTextRun() {
|
||||
if (mOwning) {
|
||||
delete mTextRun;
|
||||
}
|
||||
aMetrics->mP2A,
|
||||
ComputeFlags(aMetrics, aEnableSpacing));
|
||||
}
|
||||
gfxTextRun* operator->() { return mTextRun; }
|
||||
gfxTextRun* get() { return mTextRun; }
|
||||
|
||||
private:
|
||||
gfxTextRun* mTextRun;
|
||||
PRBool mOwning;
|
||||
|
||||
static PRUint32 ComputeFlags(nsThebesFontMetrics* aMetrics,
|
||||
PRBool aEnableSpacing) {
|
||||
PRUint32 flags = 0;
|
||||
if (aMetrics->GetRightToLeftTextRunMode()) {
|
||||
flags |= gfxTextRunFactory::TEXT_IS_RTL;
|
||||
}
|
||||
if (aEnableSpacing) {
|
||||
flags |= gfxTextRunFactory::TEXT_ENABLE_SPACING |
|
||||
gfxTextRunFactory::TEXT_ABSOLUTE_SPACING |
|
||||
gfxTextRunFactory::TEXT_ENABLE_NEGATIVE_SPACING;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
};
|
||||
friend class AutoTextRun;
|
||||
|
||||
|
|
|
@ -38,152 +38,158 @@
|
|||
#ifndef GFX_TEXT_RUN_CACHE_H
|
||||
#define GFX_TEXT_RUN_CACHE_H
|
||||
|
||||
#include "nsRefPtrHashtable.h"
|
||||
#include "nsClassHashtable.h"
|
||||
|
||||
#include "gfxFont.h"
|
||||
#include "nsCheapSets.h"
|
||||
|
||||
#include "prtime.h"
|
||||
|
||||
/**
|
||||
* A textrun cache object. A general textrun caching solution. If you use
|
||||
* this class to create a textrun cache, you are responsible for managing
|
||||
* textrun lifetimes. The full power of textrun creation is exposed; you can
|
||||
* set all textrun creation flags and parameters.
|
||||
*/
|
||||
class THEBES_API gfxTextRunCache {
|
||||
public:
|
||||
/*
|
||||
* Get the global gfxTextRunCache. You must call Init() before
|
||||
* calling this method.
|
||||
*/
|
||||
static gfxTextRunCache* GetCache() {
|
||||
return mGlobalCache;
|
||||
gfxTextRunCache() {
|
||||
mCache.Init(100);
|
||||
}
|
||||
~gfxTextRunCache() {}
|
||||
|
||||
static nsresult Init();
|
||||
// It's OK to call Shutdown if we never actually started or we already
|
||||
// shut down.
|
||||
static void Shutdown();
|
||||
|
||||
/* Will return a pointer to a gfxTextRun, which may or may not be from
|
||||
* the cache. If aCallerOwns is set to true, the caller owns the textrun
|
||||
* and must delete it. Otherwise the returned textrun is only valid until
|
||||
* the next GetOrMakeTextRun call and the caller must not delete it.
|
||||
/**
|
||||
* Get a textrun from the cache, create one if necessary.
|
||||
* @param aFlags the flags TEXT_IS_ASCII, TEXT_IS_8BIT and TEXT_HAS_SURROGATES
|
||||
* are ignored; the cache sets them based on the string.
|
||||
* @param aCallerOwns if this is null, the cache always creates a new
|
||||
* textrun owned by the caller. If non-null, the cache may return a textrun
|
||||
* that was previously created and is owned by some previous caller
|
||||
* to GetOrMakeTextRun on this cache. If so, *aCallerOwns will be set
|
||||
* to false.
|
||||
*/
|
||||
gfxTextRun *GetOrMakeTextRun (gfxContext* aContext, gfxFontGroup *aFontGroup,
|
||||
const char *aString, PRUint32 aLength,
|
||||
PRUint32 aAppUnitsPerDevUnit, PRBool aIsRTL,
|
||||
PRBool aEnableSpacing, PRBool *aCallerOwns);
|
||||
gfxTextRun *GetOrMakeTextRun (gfxContext* aContext, gfxFontGroup *aFontGroup,
|
||||
const PRUnichar *aString, PRUint32 aLength,
|
||||
PRUint32 aAppUnitsPerDevUnit, PRBool aIsRTL,
|
||||
PRBool aEnableSpacing, PRBool *aCallerOwns);
|
||||
gfxTextRun *GetOrMakeTextRun(const PRUnichar *aText, PRUint32 aLength,
|
||||
gfxFontGroup *aFontGroup,
|
||||
const gfxFontGroup::Parameters *aParams,
|
||||
PRUint32 aFlags, PRBool *aCallerOwns = nsnull);
|
||||
/**
|
||||
* Get a textrun from the cache, create one if necessary.
|
||||
* @param aFlags the flags TEXT_IS_ASCII, TEXT_IS_8BIT and TEXT_HAS_SURROGATES
|
||||
* are ignored; the cache sets them based on the string.
|
||||
* @param aCallerOwns if this is null, the cache always creates a new
|
||||
* textrun owned by the caller. If non-null, the cache may return a textrun
|
||||
* that was previously created and is owned by some previous caller
|
||||
* to GetOrMakeTextRun on this cache. If so, *aCallerOwns will be set
|
||||
* to false.
|
||||
*/
|
||||
gfxTextRun *GetOrMakeTextRun(const PRUint8 *aText, PRUint32 aLength,
|
||||
gfxFontGroup *aFontGroup,
|
||||
const gfxFontGroup::Parameters *aParams,
|
||||
PRUint32 aFlags, PRBool *aCallerOwns = nsnull);
|
||||
|
||||
/**
|
||||
* Notify that a text run was hit in the cache, a new one created, and
|
||||
* that the new one has replaced the old one in the cache.
|
||||
*/
|
||||
virtual void NotifyRemovedFromCache(gfxTextRun *aTextRun) {}
|
||||
|
||||
/**
|
||||
* Remove a textrun from the cache. This must be called before aTextRun
|
||||
* is deleted!
|
||||
*/
|
||||
void RemoveTextRun(gfxTextRun *aTextRun);
|
||||
|
||||
/** The following flags are part of the cache key: */
|
||||
enum { FLAG_MASK =
|
||||
gfxTextRunFactory::TEXT_IS_RTL |
|
||||
gfxTextRunFactory::TEXT_ENABLE_SPACING |
|
||||
gfxTextRunFactory::TEXT_ABSOLUTE_SPACING |
|
||||
gfxTextRunFactory::TEXT_ENABLE_NEGATIVE_SPACING |
|
||||
gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS |
|
||||
gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX
|
||||
};
|
||||
|
||||
protected:
|
||||
gfxTextRunCache();
|
||||
struct THEBES_API CacheHashKey {
|
||||
void *mFontOrGroup;
|
||||
const void *mString;
|
||||
PRUint32 mLength;
|
||||
PRUint32 mAppUnitsPerDevUnit;
|
||||
PRUint32 mFlags;
|
||||
PRUint32 mStringHash;
|
||||
|
||||
static gfxTextRunCache *mGlobalCache;
|
||||
|
||||
/* A small container class to hold a gfxFontGroup ref and a string.
|
||||
* This is used as the key for the cache hash table; to avoid
|
||||
* copying a whole pile of strings every time we do a hash lookup.
|
||||
* we only create our own copy of the string when Realize() is called.
|
||||
* gfxTextRunCache calls Realize whenever it puts a new entry into
|
||||
* the hashtable.
|
||||
*/
|
||||
template<class GenericString, class RealString>
|
||||
struct FontGroupAndStringT {
|
||||
FontGroupAndStringT(gfxFontGroup *fg, const GenericString* str)
|
||||
: mFontGroup(fg), mString(str) { }
|
||||
|
||||
typedef typename RealString::char_type char_type;
|
||||
|
||||
FontGroupAndStringT(const FontGroupAndStringT<GenericString,RealString>& other)
|
||||
: mFontGroup(other.mFontGroup), mString(&mRealString)
|
||||
{
|
||||
mRealString.Assign(*other.mString);
|
||||
}
|
||||
|
||||
void Realize() {
|
||||
mRealString.Assign(*mString);
|
||||
mString = &mRealString;
|
||||
}
|
||||
const char_type *GetRealString() {
|
||||
return mRealString.get();
|
||||
}
|
||||
|
||||
nsRefPtr<gfxFontGroup> mFontGroup;
|
||||
RealString mRealString;
|
||||
const GenericString* mString;
|
||||
CacheHashKey(void *aFontOrGroup, const void *aString, PRUint32 aLength,
|
||||
PRUint32 aAppUnitsPerDevUnit, PRUint32 aFlags, PRUint32 aStringHash)
|
||||
: mFontOrGroup(aFontOrGroup), mString(aString), mLength(aLength),
|
||||
mAppUnitsPerDevUnit(aAppUnitsPerDevUnit), mFlags(aFlags),
|
||||
mStringHash(aStringHash) {}
|
||||
};
|
||||
|
||||
static PRUint32 HashDouble(const double d) {
|
||||
if (d == 0.0)
|
||||
return 0;
|
||||
int exponent;
|
||||
double mantissa = frexp (d, &exponent);
|
||||
return (PRUint32) (2 * fabs(mantissa) - 1);
|
||||
}
|
||||
class THEBES_API CacheHashEntry : public PLDHashEntryHdr {
|
||||
public:
|
||||
typedef const CacheHashKey &KeyType;
|
||||
typedef const CacheHashKey *KeyTypePointer;
|
||||
|
||||
template<class T>
|
||||
struct FontGroupAndStringHashKeyT : public PLDHashEntryHdr {
|
||||
typedef const T& KeyType;
|
||||
typedef const T* KeyTypePointer;
|
||||
|
||||
FontGroupAndStringHashKeyT(KeyTypePointer aObj) : mObj(*aObj) { }
|
||||
FontGroupAndStringHashKeyT(const FontGroupAndStringHashKeyT<T>& other) : mObj(other.mObj) { }
|
||||
~FontGroupAndStringHashKeyT() { }
|
||||
|
||||
KeyType GetKey() const { return mObj; }
|
||||
|
||||
PRBool KeyEquals(KeyTypePointer aKey) const {
|
||||
return
|
||||
mObj.mString->Equals(*(aKey->mString)) &&
|
||||
mObj.mFontGroup->Equals(*(aKey->mFontGroup.get()));
|
||||
}
|
||||
// When constructing a new entry in the hashtable, mTextRuns will be
|
||||
// blank. The caller of Put() will fill it in.
|
||||
CacheHashEntry(KeyTypePointer aKey) { }
|
||||
CacheHashEntry(const CacheHashEntry& toCopy) { NS_ERROR("Should not be called"); }
|
||||
~CacheHashEntry() { }
|
||||
|
||||
PRBool KeyEquals(const KeyTypePointer aKey) const;
|
||||
static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
|
||||
static PLDHashNumber HashKey(KeyTypePointer aKey) {
|
||||
PRUint32 h1 = HashString(*(aKey->mString));
|
||||
PRUint32 h2 = HashString(aKey->mFontGroup->GetFamilies());
|
||||
PRUint32 h3 = HashDouble(aKey->mFontGroup->GetStyle()->size);
|
||||
static PLDHashNumber HashKey(const KeyTypePointer aKey);
|
||||
enum { ALLOW_MEMMOVE = PR_TRUE };
|
||||
|
||||
return h1 ^ h2 ^ h3;
|
||||
}
|
||||
enum { ALLOW_MEMMOVE = PR_FALSE };
|
||||
|
||||
private:
|
||||
const T mObj;
|
||||
gfxTextRun *mTextRun;
|
||||
};
|
||||
|
||||
struct TextRunEntry {
|
||||
TextRunEntry(gfxTextRun *tr) : textRun(tr), lastUse(PR_Now()) { }
|
||||
void Used() { lastUse = PR_Now(); }
|
||||
CacheHashKey GetKeyForTextRun(gfxTextRun *aTextRun);
|
||||
|
||||
gfxTextRun* textRun;
|
||||
PRTime lastUse;
|
||||
|
||||
~TextRunEntry() { delete textRun; }
|
||||
};
|
||||
|
||||
typedef FontGroupAndStringT<nsAString, nsString> FontGroupAndString;
|
||||
typedef FontGroupAndStringT<nsACString, nsCString> FontGroupAndCString;
|
||||
|
||||
typedef FontGroupAndStringHashKeyT<FontGroupAndString> FontGroupAndStringHashKey;
|
||||
typedef FontGroupAndStringHashKeyT<FontGroupAndCString> FontGroupAndCStringHashKey;
|
||||
|
||||
nsClassHashtable<FontGroupAndStringHashKey, TextRunEntry> mHashTableUTF16;
|
||||
nsClassHashtable<FontGroupAndCStringHashKey, TextRunEntry> mHashTableASCII;
|
||||
|
||||
void EvictUTF16();
|
||||
void EvictASCII();
|
||||
|
||||
PRTime mLastUTF16Eviction;
|
||||
PRTime mLastASCIIEviction;
|
||||
|
||||
static PLDHashOperator UTF16EvictEnumerator(const FontGroupAndString& key,
|
||||
nsAutoPtr<TextRunEntry> &value,
|
||||
void *closure);
|
||||
|
||||
static PLDHashOperator ASCIIEvictEnumerator(const FontGroupAndCString& key,
|
||||
nsAutoPtr<TextRunEntry> &value,
|
||||
void *closure);
|
||||
nsTHashtable<CacheHashEntry> mCache;
|
||||
};
|
||||
|
||||
/**
|
||||
* A simple global textrun cache for textruns that do not carry state
|
||||
* (e.g., actual or potential linebreaks) and do not need complex initialization.
|
||||
* The lifetimes of these textruns are managed by the cache (they are auto-expired
|
||||
* after a certain period of time).
|
||||
*/
|
||||
class THEBES_API gfxGlobalTextRunCache {
|
||||
public:
|
||||
/**
|
||||
* Get a textrun for the given text, using a global cache. The returned
|
||||
* textrun is valid until the next event loop. We own it, the caller
|
||||
* must not free it.
|
||||
* Do not set any state in the textrun (e.g. actual or potential linebreaks).
|
||||
* Flags IS_8BIT, IS_ASCII and HAS_SURROGATES are automatically set
|
||||
* appropriately.
|
||||
* Flag IS_PERSISTENT must NOT be set unless aText is guaranteed to live
|
||||
* forever.
|
||||
*/
|
||||
static gfxTextRun *GetTextRun(const PRUnichar *aText, PRUint32 aLength,
|
||||
gfxFontGroup *aFontGroup,
|
||||
gfxContext *aRefContext,
|
||||
PRUint32 aAppUnitsPerDevUnit,
|
||||
PRUint32 aFlags);
|
||||
|
||||
/**
|
||||
* Get a textrun for the given text, using a global cache. The returned
|
||||
* textrun is valid until the next event loop. We own it, the caller
|
||||
* must not free it.
|
||||
* Do not set any state in the textrun (e.g. actual or potential linebreaks).
|
||||
* Flags IS_8BIT, IS_ASCII and HAS_SURROGATES are automatically set
|
||||
* appropriately.
|
||||
* Flag IS_PERSISTENT must NOT be set unless aText is guaranteed to live
|
||||
* forever.
|
||||
*/
|
||||
static gfxTextRun *GetTextRun(const PRUint8 *aText, PRUint32 aLength,
|
||||
gfxFontGroup *aFontGroup,
|
||||
gfxContext *aRefContext,
|
||||
PRUint32 aAppUnitsPerDevUnit,
|
||||
PRUint32 aFlags);
|
||||
|
||||
protected:
|
||||
friend class gfxPlatform;
|
||||
|
||||
static nsresult Init();
|
||||
static void Shutdown();
|
||||
};
|
||||
|
||||
#endif /* GFX_TEXT_RUN_CACHE_H */
|
||||
|
|
|
@ -108,9 +108,9 @@ gfxPlatform::Init()
|
|||
return rv;
|
||||
}
|
||||
|
||||
rv = gfxTextRunCache::Init();
|
||||
rv = gfxGlobalTextRunCache::Init();
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_ERROR("Could not initialize gfxTextRunCache");
|
||||
NS_ERROR("Could not initialize gfxGlobalTextRunCache");
|
||||
Shutdown();
|
||||
return rv;
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ gfxPlatform::Shutdown()
|
|||
{
|
||||
// These may be called before the corresponding subsystems have actually
|
||||
// started up. That's OK, they can handle it.
|
||||
gfxTextRunCache::Shutdown();
|
||||
gfxGlobalTextRunCache::Shutdown();
|
||||
gfxFontCache::Shutdown();
|
||||
#if defined(XP_MACOSX)
|
||||
gfxQuartzFontCache::Shutdown();
|
||||
|
|
|
@ -37,302 +37,391 @@
|
|||
|
||||
#include "gfxTextRunCache.h"
|
||||
|
||||
static inline PRBool
|
||||
IsAscii(const char *aString, PRUint32 aLength)
|
||||
#include "nsExpirationTracker.h"
|
||||
|
||||
static inline PRUint32
|
||||
HashMix(PRUint32 aHash, PRUnichar aCh)
|
||||
{
|
||||
const char *end = aString + aLength;
|
||||
while (aString < end) {
|
||||
if (0x80 & *aString)
|
||||
return PR_FALSE;
|
||||
++aString;
|
||||
}
|
||||
return PR_TRUE;
|
||||
return (aHash >> 28) ^ (aHash << 4) ^ aCh;
|
||||
}
|
||||
|
||||
static inline PRBool
|
||||
IsAscii(const PRUnichar *aString, PRUint32 aLength)
|
||||
{
|
||||
const PRUnichar *end = aString + aLength;
|
||||
while (aString < end) {
|
||||
if (0x0080 <= *aString)
|
||||
return PR_FALSE;
|
||||
++aString;
|
||||
}
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
static inline PRBool
|
||||
Is8Bit(const PRUnichar *aString, PRUint32 aLength)
|
||||
{
|
||||
const PRUnichar *end = aString + aLength;
|
||||
while (aString < end) {
|
||||
if (0x0100 <= *aString)
|
||||
return PR_FALSE;
|
||||
++aString;
|
||||
}
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
gfxTextRunCache* gfxTextRunCache::mGlobalCache = nsnull;
|
||||
|
||||
static int gDisableCache = -1;
|
||||
|
||||
gfxTextRunCache::gfxTextRunCache()
|
||||
{
|
||||
if (getenv("MOZ_GFX_NO_TEXT_CACHE"))
|
||||
gDisableCache = 1;
|
||||
else
|
||||
gDisableCache = 0;
|
||||
|
||||
mHashTableUTF16.Init();
|
||||
mHashTableASCII.Init();
|
||||
|
||||
mLastUTF16Eviction = mLastASCIIEviction = PR_Now();
|
||||
}
|
||||
|
||||
// static
|
||||
nsresult
|
||||
gfxTextRunCache::Init()
|
||||
{
|
||||
NS_ASSERTION(!mGlobalCache, "Why do we have an mGlobalCache?");
|
||||
mGlobalCache = new gfxTextRunCache();
|
||||
|
||||
if (!mGlobalCache) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// static
|
||||
void
|
||||
gfxTextRunCache::Shutdown()
|
||||
{
|
||||
delete mGlobalCache;
|
||||
mGlobalCache = nsnull;
|
||||
}
|
||||
|
||||
static PRUint32
|
||||
ComputeFlags(PRBool aIsRTL, PRBool aEnableSpacing)
|
||||
HashString(const PRUnichar *aText, PRUint32 aLength, PRUint32 *aFlags)
|
||||
{
|
||||
PRUint32 flags = 0;
|
||||
if (aIsRTL) {
|
||||
flags |= gfxTextRunFactory::TEXT_IS_RTL;
|
||||
*aFlags &= ~(gfxFontGroup::TEXT_HAS_SURROGATES | gfxFontGroup::TEXT_IS_ASCII);
|
||||
PRUint32 i;
|
||||
PRUint32 hashCode = 0;
|
||||
PRUnichar allBits = 0;
|
||||
for (i = 0; i < aLength; ++i) {
|
||||
PRUnichar ch = aText[i];
|
||||
hashCode = HashMix(hashCode, ch);
|
||||
allBits |= ch;
|
||||
if (IS_SURROGATE(ch)) {
|
||||
*aFlags |= gfxFontGroup::TEXT_HAS_SURROGATES;
|
||||
}
|
||||
}
|
||||
if (aEnableSpacing) {
|
||||
flags |= gfxTextRunFactory::TEXT_ENABLE_SPACING |
|
||||
gfxTextRunFactory::TEXT_ABSOLUTE_SPACING |
|
||||
gfxTextRunFactory::TEXT_ENABLE_NEGATIVE_SPACING;
|
||||
if (!(allBits & ~0x7F)) {
|
||||
*aFlags |= gfxFontGroup::TEXT_IS_ASCII;
|
||||
}
|
||||
return flags;
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
gfxTextRun*
|
||||
gfxTextRunCache::GetOrMakeTextRun(gfxContext *aContext, gfxFontGroup *aFontGroup,
|
||||
const PRUnichar *aString, PRUint32 aLength,
|
||||
PRUint32 aAppUnitsPerDevUnit, PRBool aIsRTL,
|
||||
PRBool aEnableSpacing, PRBool *aCallerOwns)
|
||||
static PRUint32
|
||||
HashString(const PRUint8 *aText, PRUint32 aLength, PRUint32 *aFlags)
|
||||
{
|
||||
// Choose pessimistic flags since we don't want to bother analyzing the string
|
||||
gfxTextRunFactory::Parameters params = {
|
||||
aContext, nsnull, nsnull, nsnull, 0, aAppUnitsPerDevUnit
|
||||
};
|
||||
PRUint32 flags = ComputeFlags(aIsRTL, aEnableSpacing);
|
||||
if (IsAscii(aString, aLength)) {
|
||||
flags |= gfxTextRunFactory::TEXT_IS_ASCII;
|
||||
} else {
|
||||
for (PRUint32 i = 0; i < aLength; ++i) {
|
||||
if (NS_IS_HIGH_SURROGATE(aString[i])) {
|
||||
flags |= gfxTextRunFactory::TEXT_HAS_SURROGATES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
*aFlags &= ~(gfxFontGroup::TEXT_HAS_SURROGATES | gfxFontGroup::TEXT_IS_ASCII);
|
||||
*aFlags |= gfxFontGroup::TEXT_IS_8BIT;
|
||||
PRUint32 i;
|
||||
PRUint32 hashCode = 0;
|
||||
PRUint8 allBits = 0;
|
||||
for (i = 0; i < aLength; ++i) {
|
||||
PRUint8 ch = aText[i];
|
||||
hashCode = HashMix(hashCode, ch);
|
||||
allBits |= ch;
|
||||
}
|
||||
|
||||
gfxTextRun *tr = nsnull;
|
||||
// Don't cache textruns that use spacing
|
||||
if (!gDisableCache && !aEnableSpacing) {
|
||||
// Evict first, to make sure that the textrun we return is live.
|
||||
EvictUTF16();
|
||||
|
||||
TextRunEntry *entry;
|
||||
nsDependentSubstring keyStr(aString, aString + aLength);
|
||||
FontGroupAndString key(aFontGroup, &keyStr);
|
||||
|
||||
if (mHashTableUTF16.Get(key, &entry)) {
|
||||
gfxTextRun *cachedTR = entry->textRun;
|
||||
// Check that this matches what we wanted. If it doesn't, we leave
|
||||
// this cache entry alone and return a fresh, caller-owned textrun
|
||||
// below.
|
||||
if (cachedTR->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit &&
|
||||
cachedTR->IsRightToLeft() == aIsRTL) {
|
||||
entry->Used();
|
||||
tr = cachedTR;
|
||||
tr->SetContext(aContext);
|
||||
}
|
||||
} else {
|
||||
key.Realize();
|
||||
// Text is persistent since it's in the key, which will live as
|
||||
// long as this textrun.
|
||||
tr = aFontGroup->MakeTextRun(key.GetRealString(), aLength, ¶ms,
|
||||
flags | gfxTextRunFactory::TEXT_IS_PERSISTENT);
|
||||
entry = new TextRunEntry(tr);
|
||||
mHashTableUTF16.Put(key, entry);
|
||||
}
|
||||
if (!(allBits & ~0x7F)) {
|
||||
*aFlags |= gfxFontGroup::TEXT_IS_ASCII;
|
||||
}
|
||||
|
||||
if (tr) {
|
||||
*aCallerOwns = PR_FALSE;
|
||||
} else {
|
||||
// Textrun is not in the cache for some reason.
|
||||
*aCallerOwns = PR_TRUE;
|
||||
PRUnichar *newStr = new PRUnichar[aLength];
|
||||
if (newStr) {
|
||||
memcpy(newStr, aString, sizeof(PRUnichar)*aLength);
|
||||
tr = aFontGroup->MakeTextRun(newStr, aLength, ¶ms, flags);
|
||||
}
|
||||
}
|
||||
|
||||
return tr;
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
gfxTextRun*
|
||||
gfxTextRunCache::GetOrMakeTextRun(gfxContext *aContext, gfxFontGroup *aFontGroup,
|
||||
const char *aString, PRUint32 aLength,
|
||||
PRUint32 aAppUnitsPerDevUnit, PRBool aIsRTL,
|
||||
PRBool aEnableSpacing, PRBool *aCallerOwns)
|
||||
static void *GetCacheKeyFontOrGroup(gfxTextRun *aTextRun)
|
||||
{
|
||||
// Choose pessimistic flags since we don't want to bother analyzing the string
|
||||
gfxTextRunFactory::Parameters params = {
|
||||
aContext, nsnull, nsnull, nsnull, 0, aAppUnitsPerDevUnit
|
||||
};
|
||||
PRUint32 flags = ComputeFlags(aIsRTL, aEnableSpacing) | gfxTextRunFactory::TEXT_IS_8BIT;
|
||||
if (IsAscii(aString, aLength))
|
||||
flags |= gfxTextRunFactory::TEXT_IS_ASCII;
|
||||
PRUint32 glyphRunCount;
|
||||
const gfxTextRun::GlyphRun *glyphRuns = aTextRun->GetGlyphRuns(&glyphRunCount);
|
||||
gfxFontGroup *fontGroup = aTextRun->GetFontGroup();
|
||||
gfxFont *firstFont = fontGroup->GetFontAt(0);
|
||||
return glyphRunCount == 1 && glyphRuns[0].mFont == firstFont
|
||||
? NS_STATIC_CAST(void *, firstFont)
|
||||
: NS_STATIC_CAST(void *, fontGroup);
|
||||
}
|
||||
|
||||
const PRUint8 *str = reinterpret_cast<const PRUint8*>(aString);
|
||||
static const PRUnichar *
|
||||
CloneText(const PRUnichar *aText, PRUint32 aLength,
|
||||
nsAutoArrayPtr<PRUnichar> *aBuffer, PRUint32 aFlags)
|
||||
{
|
||||
if (*aBuffer == aText || (aFlags & gfxFontGroup::TEXT_IS_PERSISTENT))
|
||||
return aText;
|
||||
PRUnichar *newText = new PRUnichar[aLength];
|
||||
if (!newText)
|
||||
return nsnull;
|
||||
memcpy(newText, aText, aLength*sizeof(PRUnichar));
|
||||
*aBuffer = newText;
|
||||
return newText;
|
||||
}
|
||||
|
||||
gfxTextRun *tr = nsnull;
|
||||
// Don't cache textruns that use spacing
|
||||
if (!gDisableCache && !aEnableSpacing) {
|
||||
// Evict first, to make sure that the textrun we return is live.
|
||||
EvictASCII();
|
||||
|
||||
TextRunEntry *entry;
|
||||
nsDependentCSubstring keyStr(aString, aString + aLength);
|
||||
FontGroupAndCString key(aFontGroup, &keyStr);
|
||||
static const PRUint8 *
|
||||
CloneText(const PRUint8 *aText, PRUint32 aLength,
|
||||
nsAutoArrayPtr<PRUint8> *aBuffer, PRUint32 aFlags)
|
||||
{
|
||||
if (*aBuffer == aText || (aFlags & gfxFontGroup::TEXT_IS_PERSISTENT))
|
||||
return aText;
|
||||
PRUint8 *newText = new PRUint8[aLength];
|
||||
if (!newText)
|
||||
return nsnull;
|
||||
memcpy(newText, aText, aLength);
|
||||
*aBuffer = newText;
|
||||
return newText;
|
||||
}
|
||||
|
||||
if (mHashTableASCII.Get(key, &entry)) {
|
||||
gfxTextRun *cachedTR = entry->textRun;
|
||||
// Check that this matches what we wanted. If it doesn't, we leave
|
||||
// this cache entry alone and return a fresh, caller-owned textrun
|
||||
// below.
|
||||
if (cachedTR->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit &&
|
||||
cachedTR->IsRightToLeft() == aIsRTL) {
|
||||
entry->Used();
|
||||
tr = cachedTR;
|
||||
tr->SetContext(aContext);
|
||||
}
|
||||
} else {
|
||||
key.Realize();
|
||||
// Text is persistent since it's in the key, which will live as
|
||||
// long as this textrun.
|
||||
tr = aFontGroup->MakeTextRun(reinterpret_cast<const PRUint8 *>(key.GetRealString()),
|
||||
aLength, ¶ms,
|
||||
flags | gfxTextRunFactory::TEXT_IS_PERSISTENT);
|
||||
entry = new TextRunEntry(tr);
|
||||
mHashTableASCII.Put(key, entry);
|
||||
}
|
||||
}
|
||||
|
||||
if (tr) {
|
||||
*aCallerOwns = PR_FALSE;
|
||||
} else {
|
||||
// Textrun is not in the cache for some reason.
|
||||
gfxTextRun *
|
||||
gfxTextRunCache::GetOrMakeTextRun(const PRUnichar *aText, PRUint32 aLength,
|
||||
gfxFontGroup *aFontGroup,
|
||||
const gfxFontGroup::Parameters *aParams,
|
||||
PRUint32 aFlags, PRBool *aCallerOwns)
|
||||
{
|
||||
if (aCallerOwns) {
|
||||
*aCallerOwns = PR_TRUE;
|
||||
PRUint8 *newStr = new PRUint8[aLength];
|
||||
if (newStr) {
|
||||
memcpy(newStr, str, aLength);
|
||||
tr = aFontGroup->MakeTextRun(newStr, aLength, ¶ms, flags);
|
||||
}
|
||||
if (aLength == 0) {
|
||||
aFlags |= gfxFontGroup::TEXT_IS_PERSISTENT;
|
||||
} else if (aLength == 1 && aText[0] == ' ') {
|
||||
aFlags |= gfxFontGroup::TEXT_IS_PERSISTENT;
|
||||
static const PRUnichar space = ' ';
|
||||
aText = &space;
|
||||
}
|
||||
|
||||
PRUint32 hashCode = HashString(aText, aLength, &aFlags);
|
||||
gfxFont *font = aFontGroup->GetFontAt(0);
|
||||
CacheHashKey key(font, aText, aLength, aParams->mAppUnitsPerDevUnit, aFlags, hashCode);
|
||||
CacheHashEntry *entry = nsnull;
|
||||
if (font) {
|
||||
entry = mCache.GetEntry(key);
|
||||
}
|
||||
if (!entry) {
|
||||
key.mFontOrGroup = aFontGroup;
|
||||
entry = mCache.GetEntry(key);
|
||||
}
|
||||
nsAutoArrayPtr<PRUnichar> text;
|
||||
if (entry) {
|
||||
gfxTextRun *textRun = entry->mTextRun;
|
||||
if (aCallerOwns) {
|
||||
*aCallerOwns = PR_FALSE;
|
||||
return textRun;
|
||||
}
|
||||
aText = CloneText(aText, aLength, &text, aFlags);
|
||||
if (!aText)
|
||||
return nsnull;
|
||||
gfxTextRun *newRun =
|
||||
textRun->Clone(aParams, aText, aLength, aFontGroup, aFlags);
|
||||
if (newRun) {
|
||||
entry->mTextRun = newRun;
|
||||
NotifyRemovedFromCache(textRun);
|
||||
text.forget();
|
||||
return newRun;
|
||||
}
|
||||
}
|
||||
|
||||
return tr;
|
||||
aText = CloneText(aText, aLength, &text, aFlags);
|
||||
if (!aText)
|
||||
return nsnull;
|
||||
gfxTextRun *newRun =
|
||||
aFontGroup->MakeTextRun(aText, aLength, aParams, aFlags);
|
||||
if (newRun) {
|
||||
key.mFontOrGroup = GetCacheKeyFontOrGroup(newRun);
|
||||
entry = mCache.PutEntry(key);
|
||||
if (entry) {
|
||||
entry->mTextRun = newRun;
|
||||
}
|
||||
NS_ASSERTION(!entry || entry == mCache.GetEntry(GetKeyForTextRun(newRun)),
|
||||
"Inconsistent hashing");
|
||||
}
|
||||
text.forget();
|
||||
return newRun;
|
||||
}
|
||||
|
||||
gfxTextRun *
|
||||
gfxTextRunCache::GetOrMakeTextRun(const PRUint8 *aText, PRUint32 aLength,
|
||||
gfxFontGroup *aFontGroup,
|
||||
const gfxFontGroup::Parameters *aParams,
|
||||
PRUint32 aFlags, PRBool *aCallerOwns)
|
||||
{
|
||||
if (aCallerOwns) {
|
||||
*aCallerOwns = PR_TRUE;
|
||||
}
|
||||
if (aLength == 0) {
|
||||
aFlags |= gfxFontGroup::TEXT_IS_PERSISTENT;
|
||||
} else if (aLength == 1 && aText[0] == ' ') {
|
||||
aFlags |= gfxFontGroup::TEXT_IS_PERSISTENT;
|
||||
static const PRUint8 space = ' ';
|
||||
aText = &space;
|
||||
}
|
||||
|
||||
PRUint32 hashCode = HashString(aText, aLength, &aFlags);
|
||||
gfxFont *font = aFontGroup->GetFontAt(0);
|
||||
CacheHashKey key(font, aText, aLength, aParams->mAppUnitsPerDevUnit, aFlags, hashCode);
|
||||
CacheHashEntry *entry = nsnull;
|
||||
if (font) {
|
||||
entry = mCache.GetEntry(key);
|
||||
}
|
||||
if (!entry) {
|
||||
key.mFontOrGroup = aFontGroup;
|
||||
entry = mCache.GetEntry(key);
|
||||
}
|
||||
nsAutoArrayPtr<PRUint8> text;
|
||||
if (entry) {
|
||||
gfxTextRun *textRun = entry->mTextRun;
|
||||
if (aCallerOwns) {
|
||||
*aCallerOwns = PR_FALSE;
|
||||
return textRun;
|
||||
}
|
||||
aText = CloneText(aText, aLength, &text, aFlags);
|
||||
if (!aText)
|
||||
return nsnull;
|
||||
gfxTextRun *newRun =
|
||||
textRun->Clone(aParams, aText, aLength,
|
||||
aFontGroup, aFlags);
|
||||
if (newRun) {
|
||||
entry->mTextRun = newRun;
|
||||
NotifyRemovedFromCache(textRun);
|
||||
text.forget();
|
||||
return newRun;
|
||||
}
|
||||
}
|
||||
|
||||
aText = CloneText(aText, aLength, &text, aFlags);
|
||||
if (!aText)
|
||||
return nsnull;
|
||||
gfxTextRun *newRun =
|
||||
aFontGroup->MakeTextRun(aText, aLength, aParams, aFlags);
|
||||
if (newRun) {
|
||||
key.mFontOrGroup = GetCacheKeyFontOrGroup(newRun);
|
||||
entry = mCache.PutEntry(key);
|
||||
if (entry) {
|
||||
entry->mTextRun = newRun;
|
||||
}
|
||||
NS_ASSERTION(!entry || entry == mCache.GetEntry(GetKeyForTextRun(newRun)),
|
||||
"Inconsistent hashing");
|
||||
}
|
||||
text.forget();
|
||||
return newRun;
|
||||
}
|
||||
|
||||
gfxTextRunCache::CacheHashKey
|
||||
gfxTextRunCache::GetKeyForTextRun(gfxTextRun *aTextRun)
|
||||
{
|
||||
PRUint32 hashCode;
|
||||
const void *text;
|
||||
PRUint32 length = aTextRun->GetLength();
|
||||
if (aTextRun->GetFlags() & gfxFontGroup::TEXT_IS_8BIT) {
|
||||
PRUint32 flags;
|
||||
text = aTextRun->GetText8Bit();
|
||||
hashCode = HashString(aTextRun->GetText8Bit(), length, &flags);
|
||||
} else {
|
||||
PRUint32 flags;
|
||||
text = aTextRun->GetTextUnicode();
|
||||
hashCode = HashString(aTextRun->GetTextUnicode(), length, &flags);
|
||||
}
|
||||
void *fontOrGroup = GetCacheKeyFontOrGroup(aTextRun);
|
||||
return CacheHashKey(fontOrGroup, text, length, aTextRun->GetAppUnitsPerDevUnit(),
|
||||
aTextRun->GetFlags(), hashCode);
|
||||
}
|
||||
|
||||
void
|
||||
gfxTextRunCache::RemoveTextRun(gfxTextRun *aTextRun)
|
||||
{
|
||||
CacheHashKey key = GetKeyForTextRun(aTextRun);
|
||||
#ifdef DEBUG
|
||||
CacheHashEntry *entry = mCache.GetEntry(key);
|
||||
NS_ASSERTION(entry && entry->mTextRun == aTextRun,
|
||||
"Failed to find textrun in cache");
|
||||
#endif
|
||||
mCache.RemoveEntry(key);
|
||||
}
|
||||
|
||||
static PRBool
|
||||
CompareDifferentWidthStrings(const PRUint8 *aStr1, const PRUnichar *aStr2,
|
||||
PRUint32 aLength)
|
||||
{
|
||||
PRUint32 i;
|
||||
for (i = 0; i < aLength; ++i) {
|
||||
if (aStr1[i] != aStr2[i])
|
||||
return PR_FALSE;
|
||||
}
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
PRBool
|
||||
gfxTextRunCache::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const
|
||||
{
|
||||
gfxTextRun *textRun = mTextRun;
|
||||
if (!textRun)
|
||||
return PR_FALSE;
|
||||
PRUint32 length = textRun->GetLength();
|
||||
if (aKey->mFontOrGroup != GetCacheKeyFontOrGroup(textRun) ||
|
||||
aKey->mLength != length ||
|
||||
aKey->mAppUnitsPerDevUnit != textRun->GetAppUnitsPerDevUnit() ||
|
||||
((aKey->mFlags ^ textRun->GetFlags()) & FLAG_MASK))
|
||||
return PR_FALSE;
|
||||
|
||||
if (textRun->GetFlags() & gfxFontGroup::TEXT_IS_8BIT) {
|
||||
if (aKey->mFlags & gfxFontGroup::TEXT_IS_8BIT)
|
||||
return memcmp(textRun->GetText8Bit(), aKey->mString, length) == 0;
|
||||
return CompareDifferentWidthStrings(textRun->GetText8Bit(),
|
||||
NS_STATIC_CAST(const PRUnichar *, aKey->mString), length);
|
||||
} else {
|
||||
if (!(aKey->mFlags & gfxFontGroup::TEXT_IS_8BIT))
|
||||
return memcmp(textRun->GetTextUnicode(), aKey->mString, length*sizeof(PRUnichar)) == 0;
|
||||
return CompareDifferentWidthStrings(NS_STATIC_CAST(const PRUint8 *, aKey->mString),
|
||||
textRun->GetTextUnicode(), length);
|
||||
}
|
||||
}
|
||||
|
||||
PLDHashNumber
|
||||
gfxTextRunCache::CacheHashEntry::HashKey(const KeyTypePointer aKey)
|
||||
{
|
||||
return aKey->mStringHash + (long)aKey->mFontOrGroup + aKey->mAppUnitsPerDevUnit +
|
||||
(aKey->mFlags & FLAG_MASK);
|
||||
}
|
||||
|
||||
/*
|
||||
* Stupid eviction algorithm: every 3*EVICT_AGE (3*1s),
|
||||
* evict every run that hasn't been used for more than a second.
|
||||
*
|
||||
* Don't evict anything if we have less than EVICT_MIN_COUNT (1000)
|
||||
* entries in the cache.
|
||||
*
|
||||
* XXX todo Don't use PR_Now(); use RDTSC or something for the timing.
|
||||
* XXX todo Use a less-stupid eviction algorithm
|
||||
* XXX todo Tweak EVICT_MIN_COUNT based on actual browsing
|
||||
* Cache textruns and expire them after 3*10 seconds of no use
|
||||
*/
|
||||
class TextRunCache : public nsExpirationTracker<gfxTextRun,3> {
|
||||
public:
|
||||
enum { TIMEOUT_SECONDS = 10 };
|
||||
TextRunCache()
|
||||
: nsExpirationTracker<gfxTextRun,3>(TIMEOUT_SECONDS*1000) {}
|
||||
~TextRunCache() {
|
||||
AgeAllGenerations();
|
||||
}
|
||||
|
||||
#define EVICT_MIN_COUNT 1000
|
||||
// This gets called when the timeout has expired on a gfxTextRun
|
||||
virtual void NotifyExpired(gfxTextRun *aTextRun) {
|
||||
RemoveObject(aTextRun);
|
||||
mCache.RemoveTextRun(aTextRun);
|
||||
delete aTextRun;
|
||||
}
|
||||
|
||||
// 1s, in PRTime units (microseconds)
|
||||
#define EVICT_AGE 1000000
|
||||
gfxTextRunCache mCache;
|
||||
};
|
||||
|
||||
void
|
||||
gfxTextRunCache::EvictUTF16()
|
||||
static TextRunCache *gTextRunCache = nsnull;
|
||||
|
||||
static nsresult
|
||||
UpdateOwnership(gfxTextRun *aTextRun, PRBool aOwned)
|
||||
{
|
||||
PRTime evictBarrier = PR_Now();
|
||||
if (!aTextRun)
|
||||
return nsnull;
|
||||
if (aOwned)
|
||||
return gTextRunCache->AddObject(aTextRun);
|
||||
if (!aTextRun->GetExpirationState()->IsTracked())
|
||||
return NS_OK;
|
||||
return gTextRunCache->MarkUsed(aTextRun);
|
||||
}
|
||||
|
||||
if (mLastUTF16Eviction > (evictBarrier - (3*EVICT_AGE)))
|
||||
return;
|
||||
gfxTextRun *
|
||||
gfxGlobalTextRunCache::GetTextRun(const PRUnichar *aText, PRUint32 aLength,
|
||||
gfxFontGroup *aFontGroup,
|
||||
gfxContext *aRefContext,
|
||||
PRUint32 aAppUnitsPerDevUnit,
|
||||
PRUint32 aFlags)
|
||||
{
|
||||
if (!gTextRunCache)
|
||||
return nsnull;
|
||||
PRBool owned;
|
||||
gfxTextRunFactory::Parameters params = {
|
||||
aRefContext, nsnull, nsnull, nsnull, 0, aAppUnitsPerDevUnit
|
||||
};
|
||||
nsAutoPtr<gfxTextRun> textRun;
|
||||
textRun = gTextRunCache->mCache.GetOrMakeTextRun(aText, aLength, aFontGroup, ¶ms, aFlags, &owned);
|
||||
nsresult rv = UpdateOwnership(textRun, owned);
|
||||
if (NS_FAILED(rv))
|
||||
return nsnull;
|
||||
return textRun.forget();
|
||||
}
|
||||
|
||||
if (mHashTableUTF16.Count() < EVICT_MIN_COUNT)
|
||||
return;
|
||||
gfxTextRun *
|
||||
gfxGlobalTextRunCache::GetTextRun(const PRUint8 *aText, PRUint32 aLength,
|
||||
gfxFontGroup *aFontGroup,
|
||||
gfxContext *aRefContext,
|
||||
PRUint32 aAppUnitsPerDevUnit,
|
||||
PRUint32 aFlags)
|
||||
{
|
||||
if (!gTextRunCache)
|
||||
return nsnull;
|
||||
PRBool owned;
|
||||
gfxTextRunFactory::Parameters params = {
|
||||
aRefContext, nsnull, nsnull, nsnull, 0, aAppUnitsPerDevUnit
|
||||
};
|
||||
nsAutoPtr<gfxTextRun> textRun;
|
||||
textRun = gTextRunCache->mCache.GetOrMakeTextRun(aText, aLength, aFontGroup, ¶ms, aFlags, &owned);
|
||||
nsresult rv = UpdateOwnership(textRun, owned);
|
||||
if (NS_FAILED(rv))
|
||||
return nsnull;
|
||||
return textRun.forget();
|
||||
}
|
||||
|
||||
//fprintf (stderr, "Evicting UTF16\n");
|
||||
mLastUTF16Eviction = evictBarrier;
|
||||
evictBarrier -= EVICT_AGE;
|
||||
mHashTableUTF16.Enumerate(UTF16EvictEnumerator, &evictBarrier);
|
||||
nsresult
|
||||
gfxGlobalTextRunCache::Init()
|
||||
{
|
||||
gTextRunCache = new TextRunCache();
|
||||
return gTextRunCache ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
void
|
||||
gfxTextRunCache::EvictASCII()
|
||||
gfxGlobalTextRunCache::Shutdown()
|
||||
{
|
||||
PRTime evictBarrier = PR_Now();
|
||||
|
||||
if (mLastASCIIEviction > (evictBarrier - (3*EVICT_AGE)))
|
||||
return;
|
||||
|
||||
if (mHashTableASCII.Count() < EVICT_MIN_COUNT)
|
||||
return;
|
||||
|
||||
//fprintf (stderr, "Evicting ASCII\n");
|
||||
mLastASCIIEviction = evictBarrier;
|
||||
evictBarrier -= EVICT_AGE;
|
||||
mHashTableASCII.Enumerate(ASCIIEvictEnumerator, &evictBarrier);
|
||||
}
|
||||
|
||||
PLDHashOperator
|
||||
gfxTextRunCache::UTF16EvictEnumerator(const FontGroupAndString& key,
|
||||
nsAutoPtr<TextRunEntry> &value,
|
||||
void *closure)
|
||||
{
|
||||
PRTime t = *(PRTime *)closure;
|
||||
|
||||
if (value->lastUse < t)
|
||||
return PL_DHASH_REMOVE;
|
||||
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
PLDHashOperator
|
||||
gfxTextRunCache::ASCIIEvictEnumerator(const FontGroupAndCString& key,
|
||||
nsAutoPtr<TextRunEntry> &value,
|
||||
void *closure)
|
||||
{
|
||||
PRTime t = *(PRTime *)closure;
|
||||
|
||||
if (value->lastUse < t)
|
||||
return PL_DHASH_REMOVE;
|
||||
|
||||
return PL_DHASH_NEXT;
|
||||
delete gTextRunCache;
|
||||
gTextRunCache = nsnull;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче