Bug 385423. Refactor textrun cache so that all textrun clients use a single global word-based cache. Responsibility for stripping out problematic characters (e.g. newlines) is given to the word cache. r=vlad,smontagu

This commit is contained in:
roc+@cs.cmu.edu 2007-07-03 20:39:01 -07:00
parent fb292fb269
commit 33428c5bf6
17 changed files with 527 additions and 755 deletions

View File

@ -159,7 +159,7 @@ protected:
public:
AutoTextRun(nsThebesFontMetrics* aMetrics, nsIRenderingContext* aRC,
const char* aString, PRInt32 aLength, PRBool aEnableSpacing) {
mTextRun = gfxGlobalTextRunCache::GetTextRun(
mTextRun = gfxTextRunCache::MakeTextRun(
NS_REINTERPRET_CAST(const PRUint8*, aString), aLength,
aMetrics->mFontGroup,
NS_STATIC_CAST(gfxContext*, aRC->GetNativeGraphicData(nsIRenderingContext::NATIVE_THEBES_CONTEXT)),
@ -168,17 +168,17 @@ protected:
}
AutoTextRun(nsThebesFontMetrics* aMetrics, nsIRenderingContext* aRC,
const PRUnichar* aString, PRInt32 aLength, PRBool aEnableSpacing) {
mTextRun = gfxGlobalTextRunCache::GetTextRun(
mTextRun = gfxTextRunCache::MakeTextRun(
aString, aLength, aMetrics->mFontGroup,
NS_STATIC_CAST(gfxContext*, aRC->GetNativeGraphicData(nsIRenderingContext::NATIVE_THEBES_CONTEXT)),
aMetrics->mP2A,
ComputeFlags(aMetrics, aEnableSpacing));
}
gfxTextRun* operator->() { return mTextRun; }
gfxTextRun* get() { return mTextRun; }
gfxTextRun* operator->() { return mTextRun.get(); }
gfxTextRun* get() { return mTextRun.get(); }
private:
gfxTextRun* mTextRun;
gfxTextRunCache::AutoTextRun mTextRun;
static PRUint32 ComputeFlags(nsThebesFontMetrics* aMetrics,
PRBool aEnableSpacing) {

View File

@ -429,9 +429,11 @@ public:
// Flags in the mask 0x0000F000 are reserved for per-platform fonts
// Flags in the mask 0x00000FFF are set by the textrun creator.
enum {
USER_TEXT_FLAGS = 0xFFFF0000,
CACHE_TEXT_FLAGS = 0xF0000000,
USER_TEXT_FLAGS = 0x0FFF0000,
PLATFORM_TEXT_FLAGS = 0x0000F000,
TEXTRUN_TEXT_FLAGS = 0x00000FFF,
SETTABLE_FLAGS = CACHE_TEXT_FLAGS | USER_TEXT_FLAGS,
/**
* When set, the text string pointer used to create the text run
@ -517,7 +519,7 @@ public:
* gfxFont. The glyphs are associated with a string of source text, and the
* gfxTextRun APIs take parameters that are offsets into that source text.
*
* \r, \t and \n characters (for which gfxFontGroup::IsInvisibleChar returns
* \r, \t and \n characters (for which gfxFontGroup::IsInvalidChar returns
* PR_TRUE) should be set to a CompressedGlyph with SetMissing() to make them
* invisible and zero-width.
*
@ -795,12 +797,12 @@ public:
void SetUserData(void *aUserData) { mUserData = aUserData; }
PRUint32 GetFlags() const { return mFlags; }
void SetFlagBits(PRUint32 aFlags) {
NS_ASSERTION(!(aFlags & ~gfxTextRunFactory::USER_TEXT_FLAGS),
NS_ASSERTION(!(aFlags & ~gfxTextRunFactory::SETTABLE_FLAGS),
"Only user flags should be mutable");
mFlags |= aFlags;
}
void ClearFlagBits(PRUint32 aFlags) {
NS_ASSERTION(!(aFlags & ~gfxTextRunFactory::USER_TEXT_FLAGS),
NS_ASSERTION(!(aFlags & ~gfxTextRunFactory::SETTABLE_FLAGS),
"Only user flags should be mutable");
mFlags &= ~aFlags;
}
@ -1168,10 +1170,9 @@ public:
virtual gfxFontGroup *Copy(const gfxFontStyle *aStyle) = 0;
/**
* Tabs, CRs and LFs should be zero-width and invisible. They should
* break up shaping.
* Tabs, CRs and LFs should not be passed in to MakeTextRun.
*/
static PRBool IsInvisibleChar(PRUnichar ch) {
static PRBool IsInvalidChar(PRUnichar ch) {
return ch == '\t' || ch == '\r' || ch == '\n';
}

View File

@ -39,153 +39,80 @@
#define GFX_TEXT_RUN_CACHE_H
#include "gfxFont.h"
#include "nsCheapSets.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:
gfxTextRunCache() {
mCache.Init(100);
}
~gfxTextRunCache() {
NS_ASSERTION(mCache.Count() == 0, "Textrun cache not empty!");
}
/**
* 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 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:
struct THEBES_API CacheHashKey {
void *mFontOrGroup;
const void *mString;
PRUint32 mLength;
PRUint32 mAppUnitsPerDevUnit;
PRUint32 mFlags;
PRUint32 mStringHash;
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) {}
};
class THEBES_API CacheHashEntry : public PLDHashEntryHdr {
public:
typedef const CacheHashKey &KeyType;
typedef const CacheHashKey *KeyTypePointer;
// 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(const KeyTypePointer aKey);
enum { ALLOW_MEMMOVE = PR_TRUE };
gfxTextRun *mTextRun;
};
CacheHashKey GetKeyForTextRun(gfxTextRun *aTextRun);
nsTHashtable<CacheHashEntry> mCache;
};
/**
* A simple global textrun cache for textruns that do not carry state
* A simple 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 {
class THEBES_API gfxTextRunCache {
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.
* Get a textrun for the given text, using a global cache. The textrun
* must be released via ReleaseTextRun, not deleted.
* 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.
* The string can contain any characters, invalid ones will be stripped
* properly.
*/
static gfxTextRun *GetTextRun(const PRUnichar *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
gfxContext *aRefContext,
PRUint32 aAppUnitsPerDevUnit,
PRUint32 aFlags);
static gfxTextRun *MakeTextRun(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.
* As above, but allows a full Parameters object to be passed in.
*/
static gfxTextRun *MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
const gfxTextRunFactory::Parameters* aParams,
PRUint32 aFlags);
/**
* Get a textrun for the given text, using a global cache. The textrun
* must be released via ReleaseTextRun, not deleted.
* 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.
* The string can contain any characters, invalid ones will be stripped
* properly.
*/
static gfxTextRun *GetTextRun(const PRUint8 *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
gfxContext *aRefContext,
PRUint32 aAppUnitsPerDevUnit,
PRUint32 aFlags);
static gfxTextRun *MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
gfxContext *aRefContext,
PRUint32 aAppUnitsPerDevUnit,
PRUint32 aFlags);
/**
* Release a previously acquired textrun. Consider using AutoTextRun
* instead of calling this.
*/
static void ReleaseTextRun(gfxTextRun *aTextRun);
class AutoTextRun {
public:
AutoTextRun(gfxTextRun *aTextRun) : mTextRun(aTextRun) {}
AutoTextRun() : mTextRun(nsnull) {}
AutoTextRun& operator=(gfxTextRun *aTextRun) {
gfxTextRunCache::ReleaseTextRun(mTextRun);
mTextRun = aTextRun;
return *this;
}
~AutoTextRun() {
gfxTextRunCache::ReleaseTextRun(mTextRun);
}
gfxTextRun *get() { return mTextRun; }
gfxTextRun *operator->() { return mTextRun; }
private:
gfxTextRun *mTextRun;
};
protected:
friend class gfxPlatform;

View File

@ -46,101 +46,46 @@
*/
class THEBES_API gfxTextRunWordCache {
public:
gfxTextRunWordCache() {
mCache.Init(100);
}
~gfxTextRunWordCache() {
NS_ASSERTION(mCache.Count() == 0, "Textrun cache not empty!");
}
enum { TEXT_IN_CACHE = 0x10000000 };
/**
* Create a textrun using cached words.
* Invalid characters (see gfxFontGroup::IsInvalidChar) will be automatically
* treated as invisible missing.
* @param aFlags the flags TEXT_IS_ASCII and TEXT_HAS_SURROGATES must be set
* by the caller, if applicable
* @param aIsInCache if true is returned, then RemoveTextRun must be called
* before the textrun changes or dies.
* by the caller, if applicable; TEXT_IN_CACHE is added if we
* have a reference to the textrun in the cache and RemoveTextRun must
* be called when the textrun dies.
*/
gfxTextRun *MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
const gfxFontGroup::Parameters *aParams,
PRUint32 aFlags, PRBool *aIsInCache);
static gfxTextRun *MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
const gfxFontGroup::Parameters *aParams,
PRUint32 aFlags);
/**
* Create a textrun using cached words
* @param aFlags the flag TEXT_IS_ASCII must be set by the caller,
* if applicable
* @param aIsInCache if true is returned, then RemoveTextRun must be called
* before the textrun changes or dies.
* Create a textrun using cached words.
* Invalid characters (see gfxFontGroup::IsInvalidChar) will be automatically
* treated as invisible missing.
* @param aFlags the flags TEXT_IS_ASCII and TEXT_HAS_SURROGATES must be set
* by the caller, if applicable; TEXT_IN_CACHE is added if we
* have a reference to the textrun in the cache and RemoveTextRun must
* be called when the textrun dies.
*/
gfxTextRun *MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
const gfxFontGroup::Parameters *aParams,
PRUint32 aFlags, PRBool *aIsInCache);
static gfxTextRun *MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
const gfxFontGroup::Parameters *aParams,
PRUint32 aFlags);
/**
* Remove a textrun from the cache. This must be called before aTextRun
* is deleted! The text in the textrun must still be valid.
*/
void RemoveTextRun(gfxTextRun *aTextRun);
static void RemoveTextRun(gfxTextRun *aTextRun);
protected:
struct THEBES_API CacheHashKey {
void *mFontOrGroup;
const void *mString;
PRUint32 mLength;
PRUint32 mAppUnitsPerDevUnit;
PRUint32 mStringHash;
PRPackedBool mIsDoubleByteText;
};
friend class gfxPlatform;
class THEBES_API CacheHashEntry : public PLDHashEntryHdr {
public:
typedef const CacheHashKey &KeyType;
typedef const CacheHashKey *KeyTypePointer;
// When constructing a new entry in the hashtable, the caller of Put()
// will fill us in.
CacheHashEntry(KeyTypePointer aKey) : mTextRun(nsnull), mWordOffset(0),
mHashedByFont(PR_FALSE) { }
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(const KeyTypePointer aKey);
enum { ALLOW_MEMMOVE = PR_TRUE };
gfxTextRun *mTextRun;
// The offset of the start of the word in the textrun. The length of
// the word is not stored here because we can figure it out by
// looking at the textrun's text.
PRUint32 mWordOffset:31;
// This is set to true when the cache entry was hashed by the first
// font in mTextRun's fontgroup; it's false when the cache entry
// was hashed by the fontgroup itself.
PRUint32 mHashedByFont:1;
};
// Used to track words that should be copied from one textrun to
// another during the textrun construction process
struct DeferredWord {
gfxTextRun *mSourceTextRun;
PRUint32 mSourceOffset;
PRUint32 mDestOffset;
PRUint32 mLength;
PRUint32 mHash;
};
PRBool LookupWord(gfxTextRun *aTextRun, gfxFont *aFirstFont,
PRUint32 aStart, PRUint32 aEnd, PRUint32 aHash,
nsTArray<DeferredWord>* aDeferredWords);
void FinishTextRun(gfxTextRun *aTextRun, gfxTextRun *aNewRun,
gfxContext *aContext,
const nsTArray<DeferredWord>& aDeferredWords,
PRBool aSuccessful);
void RemoveWord(gfxTextRun *aTextRun, PRUint32 aStart,
PRUint32 aEnd, PRUint32 aHash);
nsTHashtable<CacheHashEntry> mCache;
static nsresult Init();
static void Shutdown();
};
#endif /* GFX_TEXT_RUN_WORD_CACHE_H */

View File

@ -687,13 +687,8 @@ SetGlyphsForCharacterGroup(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount,
}
}
gfxTextRun::CompressedGlyph g;
if (gfxFontGroup::IsInvisibleChar(aString[firstOffset/2])) {
NS_ASSERTION(firstOffset == lastOffset,
"Invisible character grouped with other characters?");
aRun->SetCharacterGlyph(aSegmentStart + firstOffset/2, g.SetMissing());
return;
}
NS_ASSERTION(!gfxFontGroup::IsInvalidChar(aString[firstOffset/2]),
"Invalid char passed in");
if (!allMatched) {
for (i = firstOffset; i <= lastOffset; ++i) {
@ -703,6 +698,7 @@ SetGlyphsForCharacterGroup(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount,
return;
}
gfxTextRun::CompressedGlyph g;
PRUint32 offset;
for (offset = firstOffset + 2; offset <= lastOffset; offset += 2) {
PRUint32 index = offset/2;

View File

@ -1062,11 +1062,9 @@ SetGlyphsForCharacterGroup(const PangoGlyphInfo *aGlyphs, PRUint32 aGlyphCount,
if (ch >= 0x10000) {
// Skip surrogate
++utf16Offset;
} else {
if (gfxFontGroup::IsInvisibleChar(PRUnichar(ch))) {
aTextRun->SetCharacterGlyph(utf16Offset, g.SetMissing());
}
}
NS_ASSERTION(!gfxFontGroup::IsInvalidChar(PRUnichar(ch)),
"Invalid character detected");
++utf16Offset;
// We produced this UTF8 so we don't need to worry about malformed stuff
@ -1256,9 +1254,8 @@ gfxPangoFontGroup::CreateGlyphRunsXft(gfxTextRun *aTextRun,
// treat this null byte as a missing glyph. Pango
// doesn't create glyphs for these, not even missing-glyphs.
aTextRun->SetMissingGlyph(utf16Offset, 0);
} else if (ch < 0x10000 && IsInvisibleChar(PRUnichar(ch))) {
aTextRun->SetCharacterGlyph(utf16Offset, g.SetMissing());
} else {
NS_ASSERTION(!IsInvalidChar(ch), "Invalid char detected");
FT_UInt glyph = XftCharIndex(dpy, xfont, ch);
XGlyphInfo info;
XftGlyphExtents(dpy, xfont, &glyph, 1, &info);

View File

@ -53,6 +53,7 @@
#include "gfxContext.h"
#include "gfxImageSurface.h"
#include "gfxTextRunCache.h"
#include "gfxTextRunWordCache.h"
#include "nsIPref.h"
#include "nsServiceManagerUtils.h"
@ -108,9 +109,16 @@ gfxPlatform::Init()
return rv;
}
rv = gfxGlobalTextRunCache::Init();
rv = gfxTextRunWordCache::Init();
if (NS_FAILED(rv)) {
NS_ERROR("Could not initialize gfxGlobalTextRunCache");
NS_ERROR("Could not initialize gfxTextRunWordCache");
Shutdown();
return rv;
}
rv = gfxTextRunCache::Init();
if (NS_FAILED(rv)) {
NS_ERROR("Could not initialize gfxTextRunCache");
Shutdown();
return rv;
}
@ -123,7 +131,8 @@ gfxPlatform::Shutdown()
{
// These may be called before the corresponding subsystems have actually
// started up. That's OK, they can handle it.
gfxGlobalTextRunCache::Shutdown();
gfxTextRunCache::Shutdown();
gfxTextRunWordCache::Shutdown();
gfxFontCache::Shutdown();
#if defined(XP_MACOSX)
gfxQuartzFontCache::Shutdown();

View File

@ -36,345 +36,100 @@
* ***** END LICENSE BLOCK ***** */
#include "gfxTextRunCache.h"
#include "gfxTextRunWordCache.h"
#include "nsExpirationTracker.h"
static inline PRUint32
HashMix(PRUint32 aHash, PRUnichar aCh)
{
return (aHash >> 28) ^ (aHash << 4) ^ aCh;
}
static PRUint32
HashString(const PRUnichar *aText, PRUint32 aLength, PRUint32 *aFlags)
{
*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 (!(allBits & ~0x7F)) {
*aFlags |= gfxFontGroup::TEXT_IS_ASCII;
}
return hashCode;
}
static PRUint32
HashString(const PRUint8 *aText, PRUint32 aLength, PRUint32 *aFlags)
{
*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;
}
if (!(allBits & ~0x7F)) {
*aFlags |= gfxFontGroup::TEXT_IS_ASCII;
}
return hashCode;
}
static void *GetCacheKeyFontOrGroup(gfxTextRun *aTextRun)
{
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);
}
gfxTextRun *
gfxTextRunCache::GetOrMakeTextRun(const PRUnichar *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 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);
}
if (entry) {
gfxTextRun *textRun = entry->mTextRun;
if (aCallerOwns) {
*aCallerOwns = PR_FALSE;
return textRun;
}
gfxTextRun *newRun =
textRun->Clone(aParams, aText, aLength, aFontGroup, aFlags);
if (newRun) {
newRun->SetHashCode(hashCode);
entry->mTextRun = newRun;
NotifyRemovedFromCache(textRun);
return newRun;
}
}
gfxTextRun *newRun =
aFontGroup->MakeTextRun(aText, aLength, aParams, aFlags);
if (newRun) {
newRun->SetHashCode(hashCode);
key.mFontOrGroup = GetCacheKeyFontOrGroup(newRun);
entry = mCache.PutEntry(key);
if (entry) {
entry->mTextRun = newRun;
}
NS_ASSERTION(!entry || entry == mCache.GetEntry(GetKeyForTextRun(newRun)),
"Inconsistent hashing");
}
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);
}
if (entry) {
gfxTextRun *textRun = entry->mTextRun;
if (aCallerOwns) {
*aCallerOwns = PR_FALSE;
return textRun;
}
gfxTextRun *newRun =
textRun->Clone(aParams, aText, aLength,
aFontGroup, aFlags);
if (newRun) {
newRun->SetHashCode(hashCode);
entry->mTextRun = newRun;
NotifyRemovedFromCache(textRun);
return newRun;
}
}
gfxTextRun *newRun =
aFontGroup->MakeTextRun(aText, aLength, aParams, aFlags);
if (newRun) {
newRun->SetHashCode(hashCode);
key.mFontOrGroup = GetCacheKeyFontOrGroup(newRun);
entry = mCache.PutEntry(key);
if (entry) {
entry->mTextRun = newRun;
}
NS_ASSERTION(!entry || entry == mCache.GetEntry(GetKeyForTextRun(newRun)),
"Inconsistent hashing");
}
return newRun;
}
gfxTextRunCache::CacheHashKey
gfxTextRunCache::GetKeyForTextRun(gfxTextRun *aTextRun)
{
const void *text;
PRUint32 length = aTextRun->GetLength();
if (aTextRun->GetFlags() & gfxFontGroup::TEXT_IS_8BIT) {
text = aTextRun->GetText8Bit();
} else {
text = aTextRun->GetTextUnicode();
}
void *fontOrGroup = GetCacheKeyFontOrGroup(aTextRun);
return CacheHashKey(fontOrGroup, text, length, aTextRun->GetAppUnitsPerDevUnit(),
aTextRun->GetFlags(), aTextRun->GetHashCode());
}
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);
}
/*
* Cache textruns and expire them after 3*10 seconds of no use
*/
class TextRunCache : public nsExpirationTracker<gfxTextRun,3> {
class TextRunExpiringCache : public nsExpirationTracker<gfxTextRun,3> {
public:
enum { TIMEOUT_SECONDS = 10 };
TextRunCache()
: nsExpirationTracker<gfxTextRun,3>(TIMEOUT_SECONDS*1000) {}
~TextRunCache() {
TextRunExpiringCache()
: nsExpirationTracker<gfxTextRun,3>(10*1000) {}
~TextRunExpiringCache() {
AgeAllGenerations();
}
// This gets called when the timeout has expired on a gfxTextRun
virtual void NotifyExpired(gfxTextRun *aTextRun) {
RemoveObject(aTextRun);
mCache.RemoveTextRun(aTextRun);
gfxTextRunWordCache::RemoveTextRun(aTextRun);
delete aTextRun;
}
gfxTextRunCache mCache;
};
static TextRunCache *gTextRunCache = nsnull;
static nsresult
UpdateOwnership(gfxTextRun *aTextRun, PRBool aOwned)
{
if (!aTextRun)
return nsnull;
if (aOwned)
return gTextRunCache->AddObject(aTextRun);
if (!aTextRun->GetExpirationState()->IsTracked())
return NS_OK;
return gTextRunCache->MarkUsed(aTextRun);
}
static TextRunExpiringCache *gTextRunCache = nsnull;
gfxTextRun *
gfxGlobalTextRunCache::GetTextRun(const PRUnichar *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
gfxContext *aRefContext,
PRUint32 aAppUnitsPerDevUnit,
PRUint32 aFlags)
gfxTextRunCache::MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
const gfxTextRunFactory::Parameters* aParams,
PRUint32 aFlags)
{
if (!gTextRunCache)
return nsnull;
return gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup,
aParams, aFlags);
}
gfxTextRun *
gfxTextRunCache::MakeTextRun(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, &params, aFlags, &owned);
nsresult rv = UpdateOwnership(textRun, owned);
if (NS_FAILED(rv))
return nsnull;
return textRun.forget();
return gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup,
&params, aFlags);
}
gfxTextRun *
gfxGlobalTextRunCache::GetTextRun(const PRUint8 *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
gfxContext *aRefContext,
PRUint32 aAppUnitsPerDevUnit,
PRUint32 aFlags)
gfxTextRunCache::MakeTextRun(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, &params, aFlags, &owned);
nsresult rv = UpdateOwnership(textRun, owned);
if (NS_FAILED(rv))
return nsnull;
return textRun.forget();
};
return gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup,
&params, aFlags);
}
void
gfxTextRunCache::ReleaseTextRun(gfxTextRun *aTextRun)
{
if (!aTextRun)
return;
if (!(aTextRun->GetFlags() & gfxTextRunWordCache::TEXT_IN_CACHE)) {
delete aTextRun;
return;
}
nsresult rv = gTextRunCache->AddObject(aTextRun);
if (NS_FAILED(rv)) {
delete aTextRun;
return;
}
}
nsresult
gfxGlobalTextRunCache::Init()
gfxTextRunCache::Init()
{
gTextRunCache = new TextRunCache();
gTextRunCache = new TextRunExpiringCache();
return gTextRunCache ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
}
void
gfxGlobalTextRunCache::Shutdown()
gfxTextRunCache::Shutdown()
{
delete gTextRunCache;
gTextRunCache = nsnull;

View File

@ -37,6 +37,114 @@
#include "gfxTextRunWordCache.h"
/**
* Cache individual "words" (strings delimited by white-space or white-space-like
* characters that don't involve kerning or ligatures) in textruns.
*/
class TextRunWordCache {
public:
TextRunWordCache() {
mCache.Init(100);
}
~TextRunWordCache() {
NS_ASSERTION(mCache.Count() == 0, "Textrun cache not empty!");
}
/**
* Create a textrun using cached words.
* Invalid characters (see gfxFontGroup::IsInvalidChar) will be automatically
* treated as invisible missing.
* @param aFlags the flags TEXT_IS_ASCII and TEXT_HAS_SURROGATES must be set
* by the caller, if applicable
* @param aIsInCache if true is returned, then RemoveTextRun must be called
* before the textrun changes or dies.
*/
gfxTextRun *MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
const gfxFontGroup::Parameters *aParams,
PRUint32 aFlags);
/**
* Create a textrun using cached words.
* Invalid characters (see gfxFontGroup::IsInvalidChar) will be automatically
* treated as invisible missing.
* @param aFlags the flag TEXT_IS_ASCII must be set by the caller,
* if applicable
* @param aIsInCache if true is returned, then RemoveTextRun must be called
* before the textrun changes or dies.
*/
gfxTextRun *MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
const gfxFontGroup::Parameters *aParams,
PRUint32 aFlags);
/**
* Remove a textrun from the cache. This must be called before aTextRun
* is deleted! The text in the textrun must still be valid.
*/
void RemoveTextRun(gfxTextRun *aTextRun);
protected:
struct CacheHashKey {
void *mFontOrGroup;
const void *mString;
PRUint32 mLength;
PRUint32 mAppUnitsPerDevUnit;
PRUint32 mStringHash;
PRPackedBool mIsDoubleByteText;
};
class CacheHashEntry : public PLDHashEntryHdr {
public:
typedef const CacheHashKey &KeyType;
typedef const CacheHashKey *KeyTypePointer;
// When constructing a new entry in the hashtable, the caller of Put()
// will fill us in.
CacheHashEntry(KeyTypePointer aKey) : mTextRun(nsnull), mWordOffset(0),
mHashedByFont(PR_FALSE) { }
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(const KeyTypePointer aKey);
enum { ALLOW_MEMMOVE = PR_TRUE };
gfxTextRun *mTextRun;
// The offset of the start of the word in the textrun. The length of
// the word is not stored here because we can figure it out by
// looking at the textrun's text.
PRUint32 mWordOffset:31;
// This is set to true when the cache entry was hashed by the first
// font in mTextRun's fontgroup; it's false when the cache entry
// was hashed by the fontgroup itself.
PRUint32 mHashedByFont:1;
};
// Used to track words that should be copied from one textrun to
// another during the textrun construction process
struct DeferredWord {
gfxTextRun *mSourceTextRun;
PRUint32 mSourceOffset;
PRUint32 mDestOffset;
PRUint32 mLength;
PRUint32 mHash;
};
PRBool LookupWord(gfxTextRun *aTextRun, gfxFont *aFirstFont,
PRUint32 aStart, PRUint32 aEnd, PRUint32 aHash,
nsTArray<DeferredWord>* aDeferredWords);
void FinishTextRun(gfxTextRun *aTextRun, gfxTextRun *aNewRun,
gfxContext *aContext,
const nsTArray<DeferredWord>& aDeferredWords,
PRBool aSuccessful);
void RemoveWord(gfxTextRun *aTextRun, PRUint32 aStart,
PRUint32 aEnd, PRUint32 aHash);
nsTHashtable<CacheHashEntry> mCache;
};
static PRLogModuleInfo *gWordCacheLog = PR_NewLogModule("wordCache");
static inline PRUint32
@ -79,7 +187,7 @@ IsBoundarySpace(PRUnichar aChar)
static PRBool
IsWordBoundary(PRUnichar aChar)
{
return IsBoundarySpace(aChar) || gfxFontGroup::IsInvisibleChar(aChar);
return IsBoundarySpace(aChar) || gfxFontGroup::IsInvalidChar(aChar);
}
/**
@ -104,9 +212,9 @@ IsWordBoundary(PRUnichar aChar)
* @return true if the word was found in the cache, false otherwise.
*/
PRBool
gfxTextRunWordCache::LookupWord(gfxTextRun *aTextRun, gfxFont *aFirstFont,
PRUint32 aStart, PRUint32 aEnd, PRUint32 aHash,
nsTArray<DeferredWord>* aDeferredWords)
TextRunWordCache::LookupWord(gfxTextRun *aTextRun, gfxFont *aFirstFont,
PRUint32 aStart, PRUint32 aEnd, PRUint32 aHash,
nsTArray<DeferredWord>* aDeferredWords)
{
if (aEnd <= aStart)
return PR_TRUE;
@ -172,11 +280,13 @@ gfxTextRunWordCache::LookupWord(gfxTextRun *aTextRun, gfxFont *aFirstFont,
* entries.
*/
void
gfxTextRunWordCache::FinishTextRun(gfxTextRun *aTextRun, gfxTextRun *aNewRun,
gfxContext *aContext,
const nsTArray<DeferredWord>& aDeferredWords,
PRBool aSuccessful)
TextRunWordCache::FinishTextRun(gfxTextRun *aTextRun, gfxTextRun *aNewRun,
gfxContext *aContext,
const nsTArray<DeferredWord>& aDeferredWords,
PRBool aSuccessful)
{
aTextRun->SetFlagBits(gfxTextRunWordCache::TEXT_IN_CACHE);
PRUint32 i;
gfxFontGroup *fontGroup = aTextRun->GetFontGroup();
gfxFont *font = fontGroup->GetFontAt(0);
@ -241,10 +351,10 @@ gfxTextRunWordCache::FinishTextRun(gfxTextRun *aTextRun, gfxTextRun *aNewRun,
}
gfxTextRun *
gfxTextRunWordCache::MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
const gfxFontGroup::Parameters *aParams,
PRUint32 aFlags, PRBool *aIsInCache)
TextRunWordCache::MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
const gfxFontGroup::Parameters *aParams,
PRUint32 aFlags)
{
nsAutoPtr<gfxTextRun> textRun;
textRun = new gfxTextRun(aParams, aText, aLength, aFontGroup, aFlags);
@ -298,10 +408,8 @@ gfxTextRunWordCache::MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
// We got everything from the cache, so we're done. No point in calling
// FinishTextRun.
// This textrun is not referenced by the cache.
*aIsInCache = PR_FALSE;
return textRun.forget();
}
*aIsInCache = PR_TRUE;
// create textrun for unknown words
gfxTextRunFactory::Parameters params =
@ -315,10 +423,10 @@ gfxTextRunWordCache::MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
}
gfxTextRun *
gfxTextRunWordCache::MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
const gfxFontGroup::Parameters *aParams,
PRUint32 aFlags, PRBool *aIsInCache)
TextRunWordCache::MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
const gfxFontGroup::Parameters *aParams,
PRUint32 aFlags)
{
aFlags |= gfxTextRunFactory::TEXT_IS_8BIT;
nsAutoPtr<gfxTextRun> textRun;
@ -373,10 +481,8 @@ gfxTextRunWordCache::MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
// We got everything from the cache, so we're done. No point in calling
// FinishTextRun.
// This textrun is not referenced by the cache.
*aIsInCache = PR_FALSE;
return textRun.forget();
}
*aIsInCache = PR_TRUE;
// create textrun for unknown words
gfxTextRunFactory::Parameters params =
@ -390,8 +496,8 @@ gfxTextRunWordCache::MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
}
void
gfxTextRunWordCache::RemoveWord(gfxTextRun *aTextRun, PRUint32 aStart,
PRUint32 aEnd, PRUint32 aHash)
TextRunWordCache::RemoveWord(gfxTextRun *aTextRun, PRUint32 aStart,
PRUint32 aEnd, PRUint32 aHash)
{
if (aEnd <= aStart)
return;
@ -414,7 +520,7 @@ gfxTextRunWordCache::RemoveWord(gfxTextRun *aTextRun, PRUint32 aStart,
// Remove a textrun from the cache by looking up each word and removing it
void
gfxTextRunWordCache::RemoveTextRun(gfxTextRun *aTextRun)
TextRunWordCache::RemoveTextRun(gfxTextRun *aTextRun)
{
PRUint32 i;
PRUint32 wordStart = 0;
@ -464,7 +570,7 @@ GetFontOrGroup(gfxFontGroup *aFontGroup, PRBool aUseFont)
}
PRBool
gfxTextRunWordCache::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const
TextRunWordCache::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const
{
if (!mTextRun)
return PR_FALSE;
@ -492,8 +598,54 @@ gfxTextRunWordCache::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const
}
PLDHashNumber
gfxTextRunWordCache::CacheHashEntry::HashKey(const KeyTypePointer aKey)
TextRunWordCache::CacheHashEntry::HashKey(const KeyTypePointer aKey)
{
return aKey->mStringHash + (long)aKey->mFontOrGroup + aKey->mAppUnitsPerDevUnit +
aKey->mIsDoubleByteText;
}
static TextRunWordCache *gTextRunWordCache = nsnull;
nsresult
gfxTextRunWordCache::Init()
{
gTextRunWordCache = new TextRunWordCache();
return gTextRunWordCache ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
}
void
gfxTextRunWordCache::Shutdown()
{
delete gTextRunWordCache;
gTextRunWordCache = nsnull;
}
gfxTextRun *
gfxTextRunWordCache::MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
const gfxFontGroup::Parameters *aParams,
PRUint32 aFlags)
{
if (!gTextRunWordCache)
return nsnull;
return gTextRunWordCache->MakeTextRun(aText, aLength, aFontGroup, aParams, aFlags);
}
gfxTextRun *
gfxTextRunWordCache::MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
const gfxFontGroup::Parameters *aParams,
PRUint32 aFlags)
{
if (!gTextRunWordCache)
return nsnull;
return gTextRunWordCache->MakeTextRun(aText, aLength, aFontGroup, aParams, aFlags);
}
void
gfxTextRunWordCache::RemoveTextRun(gfxTextRun *aTextRun)
{
if (!gTextRunWordCache)
return;
gTextRunWordCache->RemoveTextRun(aTextRun);
}

View File

@ -645,9 +645,9 @@ SetupTextRunFromGlyphs(gfxTextRun *aRun, WCHAR *aGlyphs, HDC aDC,
lastWidth = partialWidthArray[i];
PRInt32 advanceAppUnits = advancePixels*appUnitsPerDevPixel;
WCHAR glyph = aGlyphs[i];
if (gfxFontGroup::IsInvisibleChar(aRun->GetChar(i))) {
aRun->SetCharacterGlyph(i, g.SetMissing());
} else if (advanceAppUnits >= 0 &&
NS_ASSERTION(!gfxFontGroup::IsInvalidChar(aRun->GetChar(i)),
"Invalid character detected!");
if (advanceAppUnits >= 0 &&
gfxTextRun::CompressedGlyph::IsSimpleAdvance(advanceAppUnits) &&
gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyph)) {
aRun->SetCharacterGlyph(i, g.SetSimpleGlyph(advanceAppUnits, glyph));

View File

@ -299,16 +299,15 @@ RunTest (TestEntry *test, gfxContext *ctx) {
flags |= gfxTextRunFactory::TEXT_IS_RTL;
}
PRUint32 length;
PRBool isInCache;
if (test->stringType == S_ASCII) {
flags |= gfxTextRunFactory::TEXT_IS_ASCII | gfxTextRunFactory::TEXT_IS_8BIT;
length = strlen(test->string);
textRun = gTextRunCache->MakeTextRun(NS_REINTERPRET_CAST(PRUint8*, test->string), length, fontGroup, &params, flags, &isInCache);
textRun = gfxTextRunWordCache::MakeTextRun(NS_REINTERPRET_CAST(PRUint8*, test->string), length, fontGroup, &params, flags);
} else {
flags |= gfxTextRunFactory::TEXT_HAS_SURROGATES; // just in case
NS_ConvertUTF8toUTF16 str(nsDependentCString(test->string));
length = str.Length();
textRun = gTextRunCache->MakeTextRun(str.get(), length, fontGroup, &params, flags, &isInCache);
textRun = gfxTextRunWordCache::MakeTextRun(str.get(), length, fontGroup, &params, flags);
}
gfxFontTestStore::NewStore();

View File

@ -82,7 +82,7 @@ public:
if (aTextRun->GetExpirationState()->IsTracked()) {
RemoveObject(aTextRun);
}
mCache.RemoveTextRun(aTextRun);
gfxTextRunWordCache::RemoveTextRun(aTextRun);
}
// This gets called when the timeout has expired on a gfxTextRun
@ -90,8 +90,6 @@ public:
RemoveFromCache(aTextRun);
delete aTextRun;
}
gfxTextRunWordCache mCache;
};
static gfxTextRun *
@ -105,11 +103,8 @@ MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
} else if (aLength == 1 && aText[0] == ' ') {
textRun = aFontGroup->MakeSpaceTextRun(aParams, aFlags);
} else {
PRBool isInCache;
textRun = gTextRuns->mCache.MakeTextRun(aText, aLength, aFontGroup,
aParams, aFlags, &isInCache);
if (!isInCache && textRun) {
}
textRun = gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup,
aParams, aFlags);
}
if (!textRun)
return nsnull;
@ -181,6 +176,7 @@ main (int argc, char **argv) {
nsDependentCString cStr(cString);
NS_ConvertUTF8toUTF16 str(cStr);
gfxTextRun *tr = MakeTextRun(str.get(), str.Length(), fontGroup, &params, flags);
tr->GetAdvanceWidth(0, str.Length(), nsnull);
// Now try to trigger an assertion with a word cache bug. The first
// word is in the cache so it gets added to the new textrun directly.
@ -189,7 +185,6 @@ main (int argc, char **argv) {
nsDependentCString cStr2(cString2);
NS_ConvertUTF8toUTF16 str2(cStr2);
gfxTextRun *tr2 = MakeTextRun(str2.get(), str2.Length(), fontGroup, &params, flags);
tr2->GetAdvanceWidth(0, str2.Length(), nsnull);
}

View File

@ -138,16 +138,10 @@
// This bit is set on frames that trimmed trailing whitespace characters when
// calculating their width during reflow.
#define TEXT_TRIMMED_TRAILING_WHITESPACE 0x01000000
// Set when the frame's text may have a different style from its in-flow
// brethren (e.g. the frame is in first-letter or first-line), so text runs
// may need to be reconstructed when the frame's content offset/length changes.
// We set this on the frame that has in first-letter or first-line, but not
// in-flow siblings outside first-letter or first-line.
#define TEXT_RUN_LAYOUT_DEPENDENT 0x02000000
#define TEXT_REFLOW_FLAGS \
(TEXT_FIRST_LETTER|TEXT_START_OF_LINE|TEXT_END_OF_LINE|TEXT_HYPHEN_BREAK| \
TEXT_TRIMMED_TRAILING_WHITESPACE|TEXT_RUN_LAYOUT_DEPENDENT)
TEXT_TRIMMED_TRAILING_WHITESPACE)
// Cache bits for IsEmpty().
// Set this bit if the textframe is known to be only collapsible whitespace.
@ -504,6 +498,15 @@ public:
PRInt32 GetContentLength() const { return mContentLength; }
PRInt32 GetContentEnd() const { return mContentOffset + mContentLength; }
/**
* Fix up the content offsets for all next-in-flows so that they do not
* overlap this frame's content.
* @param aClearTextRuns if true, then any next-in-flows whose content
* offsets changed have their textruns cleared (as would be necessary
* if this frame could have a different style to those frames)
*/
void AdjustNextInFlowContentOffsetsForGrowth(PRBool aClearTextRuns);
// Compute the length of the content mapped by this frame
// and all its in-flow siblings. Basically this means starting at mContentOffset
// and going to the end of the text node or the next bidi continuation
@ -559,8 +562,6 @@ protected:
PRBool isRTLChars,
PRBool isOddLevel,
PRBool isBidiSystem);
void SetOffsets(PRInt32 start, PRInt32 end);
};
static void
@ -632,8 +633,8 @@ public:
if (aTextRun->GetExpirationState()->IsTracked()) {
RemoveObject(aTextRun);
}
if (!(aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_UNCACHED)) {
mCache.RemoveTextRun(aTextRun);
if (aTextRun->GetFlags() & gfxTextRunWordCache::TEXT_IN_CACHE) {
gfxTextRunWordCache::RemoveTextRun(aTextRun);
}
}
@ -643,8 +644,6 @@ public:
RemoveFromCache(aTextRun);
delete aTextRun;
}
gfxTextRunWordCache mCache;
};
static gfxTextRun *
@ -654,16 +653,12 @@ MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
{
nsAutoPtr<gfxTextRun> textRun;
if (aLength == 0) {
textRun = aFontGroup->MakeEmptyTextRun(aParams, aFlags | nsTextFrameUtils::TEXT_IS_UNCACHED);
textRun = aFontGroup->MakeEmptyTextRun(aParams, aFlags);
} else if (aLength == 1 && aText[0] == ' ') {
textRun = aFontGroup->MakeSpaceTextRun(aParams, aFlags | nsTextFrameUtils::TEXT_IS_UNCACHED);
textRun = aFontGroup->MakeSpaceTextRun(aParams, aFlags);
} else {
PRBool isInCache;
textRun = gTextRuns->mCache.MakeTextRun(aText, aLength, aFontGroup,
aParams, aFlags, &isInCache);
if (!isInCache && textRun) {
textRun->SetFlagBits(nsTextFrameUtils::TEXT_IS_UNCACHED);
}
textRun = gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup,
aParams, aFlags);
}
if (!textRun)
return nsnull;
@ -682,16 +677,12 @@ MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
{
nsAutoPtr<gfxTextRun> textRun;
if (aLength == 0) {
textRun = aFontGroup->MakeEmptyTextRun(aParams, aFlags | nsTextFrameUtils::TEXT_IS_UNCACHED);
textRun = aFontGroup->MakeEmptyTextRun(aParams, aFlags);
} else if (aLength == 1 && aText[0] == ' ') {
textRun = aFontGroup->MakeSpaceTextRun(aParams, aFlags | nsTextFrameUtils::TEXT_IS_UNCACHED);
textRun = aFontGroup->MakeSpaceTextRun(aParams, aFlags);
} else {
PRBool isInCache;
textRun = gTextRuns->mCache.MakeTextRun(aText, aLength, aFontGroup,
aParams, aFlags, &isInCache);
if (!isInCache && textRun) {
textRun->SetFlagBits(nsTextFrameUtils::TEXT_IS_UNCACHED);
}
textRun = gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup,
aParams, aFlags);
}
if (!textRun)
return nsnull;
@ -1350,13 +1341,9 @@ void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame)
!HasTerminalNewline(mLastFrame)) {
nsTextFrame* frame = NS_STATIC_CAST(nsTextFrame*, aFrame);
mappedFlow->mEndFrame = NS_STATIC_CAST(nsTextFrame*, frame->GetNextInFlow());
// Frames in the same flow can overlap at least temporarily
// (e.g. when first-line builds its textrun, we need to have it suck
// up all the in-flow content because we don't know how long the line
// is going to be).
mappedFlow->mContentEndOffset =
PR_MAX(mappedFlow->mContentEndOffset,
frame->GetContentOffset() + frame->GetContentLength());
NS_ASSERTION(mappedFlow->mContentEndOffset <= frame->GetContentOffset(),
"frame offsets overlap!");
mappedFlow->mContentEndOffset = frame->GetContentEnd();
AccumulateRunInfo(frame);
return;
}
@ -1379,8 +1366,8 @@ void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame)
mappedFlow->mEndFrame = NS_STATIC_CAST(nsTextFrame*, frame->GetNextInFlow());
mappedFlow->mAncestorControllingInitialBreak = mCommonAncestorWithLastFrame;
mappedFlow->mContentOffset = frame->GetContentOffset();
mappedFlow->mContentEndOffset =
frame->GetContentOffset() + frame->GetContentLength();
mappedFlow->mContentEndOffset = frame->GetContentEnd();
// This is temporary: it's overwritten in BuildTextRunForFrames
mappedFlow->mTransformedTextOffset = 0;
mLastFrame = frame;
@ -1476,15 +1463,15 @@ GetHyphenTextRun(gfxTextRun* aTextRun, nsIRenderingContext* aRefContext)
static const PRUnichar unicodeHyphen = 0x2010;
gfxTextRun* textRun =
gfxGlobalTextRunCache::GetTextRun(&unicodeHyphen, 1, fontGroup, ctx,
aTextRun->GetAppUnitsPerDevUnit(), flags);
gfxTextRunCache::MakeTextRun(&unicodeHyphen, 1, fontGroup, ctx,
aTextRun->GetAppUnitsPerDevUnit(), flags);
if (textRun && textRun->CountMissingGlyphs() == 0)
return textRun;
static const PRUint8 dash = '-';
return gfxGlobalTextRunCache::GetTextRun(&dash, 1, fontGroup, ctx,
aTextRun->GetAppUnitsPerDevUnit(),
flags);
return gfxTextRunCache::MakeTextRun(&dash, 1, fontGroup, ctx,
aTextRun->GetAppUnitsPerDevUnit(),
flags);
}
static gfxFont::Metrics
@ -1574,12 +1561,8 @@ BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
PRInt32 contentLength = contentEnd - contentStart;
if (content == lastContent) {
NS_ASSERTION(endOfLastContent >= contentStart,
"Gap in textframes mapping content?!");
// Text frames can overlap (see comment in ScanFrame below)
contentStart = PR_MAX(contentStart, endOfLastContent);
if (contentStart >= contentEnd)
continue;
NS_ASSERTION(endOfLastContent == contentStart,
"Gap in textframes mapping content, or overlap?!");
userData->mMappedFlows[finalMappedFlowCount - 1].mContentLength += contentLength;
} else {
TextRunMappedFlow* newFlow = &userData->mMappedFlows[finalMappedFlowCount];
@ -1711,7 +1694,6 @@ BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
styles.AppendElement(sc);
}
}
textFlags |= nsTextFrameUtils::TEXT_IS_UNCACHED;
}
if (textFlags & nsTextFrameUtils::TEXT_HAS_TAB) {
@ -3283,18 +3265,18 @@ nsContinuingTextFrame::Init(nsIContent* aContent,
// NOTE: bypassing nsTextFrame::Init!!!
nsresult rv = nsFrame::Init(aContent, aParent, aPrevInFlow);
nsIFrame* nextContinuation = aPrevInFlow->GetNextContinuation();
// Hook the frame into the flow
SetPrevInFlow(aPrevInFlow);
aPrevInFlow->SetNextInFlow(this);
nsTextFrame* prev = NS_STATIC_CAST(nsTextFrame*, aPrevInFlow);
mTextRun = prev->GetTextRun();
mContentOffset = prev->GetContentOffset() + prev->GetContentLength();
#ifdef IBMBIDI
if (aPrevInFlow->GetStateBits() & NS_FRAME_IS_BIDI) {
PRInt32 start, end;
aPrevInFlow->GetOffsets(start, mContentOffset);
nsTextFrame* nextContinuation = NS_STATIC_CAST(nsTextFrame*,
prev->GetNextContinuation());
SetPrevInFlow(prev);
prev->SetNextInFlow(this);
mTextRun = prev->GetTextRun();
mContentOffset = prev->GetContentEnd();
mContentLength = mContent->TextLength() - mContentOffset;
#ifdef IBMBIDI
if (prev->GetStateBits() & NS_FRAME_IS_BIDI) {
nsPropertyTable *propTable = PresContext()->PropertyTable();
propTable->SetProperty(this, nsGkAtoms::embeddingLevel,
propTable->GetProperty(aPrevInFlow, nsGkAtoms::embeddingLevel),
@ -3305,11 +3287,13 @@ nsContinuingTextFrame::Init(nsIContent* aContent,
propTable->SetProperty(this, nsGkAtoms::charType,
propTable->GetProperty(aPrevInFlow, nsGkAtoms::charType),
nsnull, nsnull);
if (nextContinuation) {
SetNextContinuation(nextContinuation);
nextContinuation->SetPrevContinuation(this);
nextContinuation->GetOffsets(start, end);
mContentLength = PR_MAX(1, start - mContentOffset);
NS_ASSERTION(mContentOffset <= nextContinuation->GetContentOffset(),
"Overlapping text ranges!");
mContentLength = nextContinuation->GetContentOffset() - mContentOffset;
}
mState |= NS_FRAME_IS_BIDI;
} // prev frame is bidi
@ -3516,7 +3500,7 @@ nsTextFrame::ClearTextRun()
// return;
// }
if (textRun->GetFlags() & nsTextFrameUtils::TEXT_IS_UNCACHED) {
if (!(textRun->GetFlags() & gfxTextRunWordCache::TEXT_IN_CACHE)) {
// Remove it now because it's not doing anything useful
gTextRuns->RemoveFromCache(textRun);
delete textRun;
@ -3674,8 +3658,8 @@ nsTextFrame::GetSelectionDetails()
SelectionDetails* sd;
for (sd = details; sd; sd = sd->mNext) {
// The entire text is selected!
sd->mStart = mContentOffset;
sd->mEnd = mContentOffset + mContentLength;
sd->mStart = GetContentOffset();
sd->mEnd = GetContentEnd();
}
return details;
}
@ -4281,8 +4265,8 @@ nsTextFrame::IsVisibleInSelection(nsISelection* aSelection)
// where are the selection points "really"
SelectionDetails *sdptr = details;
while (sdptr) {
if (sdptr->mEnd > mContentOffset &&
sdptr->mStart < mContentOffset + mContentLength &&
if (sdptr->mEnd > GetContentOffset() &&
sdptr->mStart < GetContentEnd() &&
sdptr->mType == nsISelectionController::SELECTION_NORMAL) {
found = PR_TRUE;
break;
@ -4409,7 +4393,7 @@ nsTextFrame::SetSelected(nsPresContext* aPresContext,
if (thisNode == startNode)
{
if ((mContentOffset + mContentLength) >= startOffset)
if (GetContentEnd() >= startOffset)
{
found = PR_TRUE;
if (thisNode == endNode)
@ -4501,12 +4485,12 @@ nsTextFrame::GetPointFromOffset(nsPresContext* aPresContext,
// place if it's positioned there
properties.InitializeForDisplay(PR_FALSE);
if (inOffset < mContentOffset){
if (inOffset < GetContentOffset()){
NS_WARNING("offset before this frame's content");
inOffset = mContentOffset;
} else if (inOffset > mContentOffset + mContentLength) {
inOffset = GetContentOffset();
} else if (inOffset > GetContentEnd()) {
NS_WARNING("offset after this frame's content");
inOffset = mContentOffset + mContentLength;
inOffset = GetContentEnd();
}
PRInt32 trimmedOffset = properties.GetStart().GetOriginalOffset();
PRInt32 trimmedEnd = trimmedOffset + properties.GetOriginalLength();
@ -4859,17 +4843,24 @@ nsTextFrame::CheckVisibility(nsPresContext* aContext, PRInt32 aStartIndex,
NS_IMETHODIMP
nsTextFrame::GetOffsets(PRInt32 &start, PRInt32 &end) const
{
start = mContentOffset;
end = mContentOffset+mContentLength;
start = GetContentOffset();
end = GetContentEnd();
return NS_OK;
}
// returns PR_TRUE if this text frame completes the first-letter, PR_FALSE
// if it does not contain a true "letter"
// if returns PR_TRUE, then it also updates aLength to cover just the first-letter
// text
// XXX :first-letter should be handled during frame construction
// (and it has a good bit in common with nextBidi)
/**
* Returns PR_TRUE if this text frame completes the first-letter, PR_FALSE
* if it does not contain a true "letter".
* If returns PR_TRUE, then it also updates aLength to cover just the first-letter
* text.
*
* XXX :first-letter should be handled during frame construction
* (and it has a good bit in common with nextBidi)
*
* @param aLength an in/out parameter: on entry contains the maximum length to
* return, on exit returns length of the first-letter fragment (which may
* include leading punctuation, for example)
*/
static PRBool
FindFirstLetterRange(const nsTextFragment* aFrag,
gfxTextRun* aTextRun,
@ -5170,6 +5161,26 @@ HasSoftHyphenBefore(const nsTextFragment* aFrag, gfxTextRun* aTextRun,
return PR_FALSE;
}
void
nsTextFrame::AdjustNextInFlowContentOffsetsForGrowth(PRBool aClearTextRuns)
{
PRInt32 end = GetContentEnd();
nsTextFrame* f = this;
NS_ASSERTION(!GetNextInFlow() ||
NS_STATIC_CAST(nsTextFrame*, GetNextInFlow())->GetContentOffset() <= GetContentEnd(),
"We shrunk, this should not be called");
while (PR_TRUE) {
f = NS_STATIC_CAST(nsTextFrame*, f->GetNextInFlow());
if (!f || f->GetContentOffset() >= end)
break;
f->mContentLength = PR_MAX(end, f->GetContentEnd()) - end;
f->mContentOffset = end;
if (aClearTextRuns) {
f->ClearTextRun();
}
}
}
NS_IMETHODIMP
nsTextFrame::Reflow(nsPresContext* aPresContext,
nsHTMLReflowMetrics& aMetrics,
@ -5193,25 +5204,16 @@ nsTextFrame::Reflow(nsPresContext* aPresContext,
// can change whether the frame maps whitespace-only text or not.
RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS);
nsTextFrame* prevInFlow = NS_STATIC_CAST(nsTextFrame*, GetPrevInFlow());
if (prevInFlow) {
// Our mContentOffset may be out of date due to our prev-in-flow growing or
// shrinking. Update it.
mContentOffset = prevInFlow->GetContentOffset() + prevInFlow->GetContentLength();
}
// Temporarily map all possible content while we construct our new textrun.
// so that when doing reflow our styles prevail over any part of the
// textrun we look at. Note that next-in-flows may be mapping the same
// content; gfxTextRun construction logic will ensure that we take priority.
NS_ASSERTION(!GetPrevInFlow() ||
NS_STATIC_CAST(nsTextFrame*, GetPrevInFlow())->GetContentEnd() == mContentOffset,
"Discontinuous content offsets!");
PRInt32 maxContentLength = GetInFlowContentLength();
mContentLength = maxContentLength;
// XXX If there's no line layout, we shouldn't even have created this
// frame. This may happen if, for example, this is text inside a table
// but not inside a cell. For now, just don't reflow. We also don't need to
// reflow if there is no content.
if (!aReflowState.mLineLayout || !mContentLength) {
if (!aReflowState.mLineLayout || !maxContentLength) {
ClearMetrics(aMetrics);
aStatus = NS_FRAME_COMPLETE;
return NS_OK;
@ -5250,11 +5252,15 @@ nsTextFrame::Reflow(nsPresContext* aPresContext,
PRBool layoutDependentTextRun =
lineLayout.GetFirstLetterStyleOK() || lineLayout.GetInFirstLine();
if (layoutDependentTextRun) {
AddStateBits(TEXT_RUN_LAYOUT_DEPENDENT);
}
if (layoutDependentTextRun ||
(prevInFlow && (prevInFlow->GetStateBits() & TEXT_RUN_LAYOUT_DEPENDENT))) {
ClearTextRun();
// Temporarily map all possible content while we construct our new textrun.
// so that when doing reflow our styles prevail over any part of the
// textrun we look at.
mContentLength = maxContentLength;
// The following loop is going to traverse all in-flow frames, which could
// be kinda slow, but we're going to have to rebuild all their text runs
// anyway so this shouldn't be any worse
AdjustNextInFlowContentOffsetsForGrowth(PR_TRUE);
}
PRUint32 flowEndInTextRun;
@ -5273,7 +5279,7 @@ nsTextFrame::Reflow(nsPresContext* aPresContext,
// DOM offsets of the text range we need to measure, after trimming
// whitespace, restricting to first-letter, and restricting preformatted text
// to nearest newline
PRInt32 length = mContentLength;
PRInt32 length = maxContentLength;
PRInt32 offset = mContentOffset;
// Restrict preformatted text to the nearest newline
@ -5428,6 +5434,27 @@ nsTextFrame::Reflow(nsPresContext* aPresContext,
}
mContentLength = offset + charsFit - mContentOffset;
// Now fix up content offsets/lengths for in-flows
nsTextFrame* f = NS_STATIC_CAST(nsTextFrame*, GetNextInFlow());
if (f) {
if (f->GetContentOffset() > GetContentEnd()) {
// We must have shrunk. Add the leftover text to the start of f.
f->mContentLength = f->GetContentEnd() - GetContentEnd();
f->mContentOffset = GetContentEnd();
if (layoutDependentTextRun) {
// f's textrun may need to change since the text style may be
// different.
f->ClearTextRun();
}
} else if (f->GetContentOffset() < GetContentEnd()) {
// We must have grown. Remove the text from f and possibly its
// continuations.
NS_ASSERTION(!layoutDependentTextRun,
"We should have grown up above and be shrinking here!");
AdjustNextInFlowContentOffsetsForGrowth(layoutDependentTextRun);
}
}
/////////////////////////////////////////////////////////////////////
// Compute output metrics
/////////////////////////////////////////////////////////////////////
@ -5595,9 +5622,9 @@ nsTextFrame::TrimTrailingWhiteSpace(nsPresContext* aPresContext,
if (GetStateBits() & TEXT_TRIMMED_TRAILING_WHITESPACE) {
aLastCharIsJustifiable = PR_TRUE;
} else if (trimmed.mStart + trimmed.mLength < mContentOffset + mContentLength) {
} else if (trimmed.mStart + trimmed.mLength < GetContentEnd()) {
gfxSkipCharsIterator end = iter;
PRUint32 endOffset = end.ConvertOriginalToSkipped(mContentOffset + mContentLength);
PRUint32 endOffset = end.ConvertOriginalToSkipped(GetContentEnd());
if (trimmedEnd < endOffset) {
// We can't be dealing with tabs here ... they wouldn't be trimmed. So it's
// OK to pass null for the line container.
@ -5763,7 +5790,7 @@ nsTextFrame::List(FILE* out, PRInt32 aIndent) const
ToCString(tmp, &totalContentLength);
// Output the first/last content offset and prev/next in flow info
PRBool isComplete = (mContentOffset + mContentLength) == totalContentLength;
PRBool isComplete = GetContentEnd() == totalContentLength;
fprintf(out, "[%d,%d,%c] ",
mContentOffset, mContentLength,
isComplete ? 'T':'F');
@ -5872,20 +5899,24 @@ void
nsTextFrame::AdjustOffsetsForBidi(PRInt32 aStart, PRInt32 aEnd)
{
AddStateBits(NS_FRAME_IS_BIDI);
SetOffsets(aStart, aEnd);
/*
* After Bidi resolution we may need to reassign text runs.
* This is called during bidi resolution from the block container, so we
* shouldn't be holding a local reference to a textrun anywhere.
*/
ClearTextRunsInFlowChain(this);
}
void
nsTextFrame::SetOffsets(PRInt32 aStart, PRInt32 aEnd)
{
mContentOffset = aStart;
mContentLength = aEnd - aStart;
nsTextFrame* f = this;
// We were just assigned all the content for this in-flow run, so unmap all
// next-in-flows
while (PR_TRUE) {
/*
* After Bidi resolution we may need to reassign text runs.
* This is called during bidi resolution from the block container, so we
* shouldn't be holding a local reference to a textrun anywhere.
*/
f->ClearTextRun();
f = NS_STATIC_CAST(nsTextFrame*, f->GetNextInFlow());
if (!f)
break;
f->mContentOffset = aEnd;
f->mContentLength = 0;
}
}
/**

View File

@ -72,8 +72,7 @@ public:
TEXT_INCOMING_WHITESPACE = 0x200000,
TEXT_TRAILING_WHITESPACE = 0x400000,
TEXT_COMPRESSED_LEADING_WHITESPACE = 0x800000,
TEXT_IS_UNCACHED = 0x1000000,
TEXT_NO_BREAKS = 0x2000000
TEXT_NO_BREAKS = 0x1000000
};
static PRBool

View File

@ -188,41 +188,6 @@ CountGlyphs(const gfxTextRun::DetailedGlyph* aDetails) {
return glyphCount + 1;
}
/**
* Concatenate textruns together just by copying their glyphrun data
*/
static void
AppendTextRun(gfxTextRun* aDest, gfxTextRun* aSrc, PRUint32 aOffset)
{
PRUint32 numGlyphRuns;
const gfxTextRun::GlyphRun* glyphRuns = aSrc->GetGlyphRuns(&numGlyphRuns);
PRUint32 j;
PRUint32 offset = aOffset;
for (j = 0; j < numGlyphRuns; ++j) {
PRUint32 runOffset = glyphRuns[j].mCharacterOffset;
PRUint32 len =
(j + 1 < numGlyphRuns ? glyphRuns[j + 1].mCharacterOffset : aSrc->GetLength()) -
runOffset;
nsresult rv = aDest->AddGlyphRun(glyphRuns[j].mFont, offset);
if (NS_FAILED(rv))
return;
PRUint32 k;
for (k = 0; k < len; ++k) {
gfxTextRun::CompressedGlyph g = aSrc->GetCharacterGlyphs()[runOffset + k];
if (g.IsComplexCluster()) {
const gfxTextRun::DetailedGlyph* details = aSrc->GetDetailedGlyphs(runOffset + k);
aDest->SetDetailedGlyphs(offset, details, CountGlyphs(details));
} else {
aDest->SetCharacterGlyph(offset, g);
}
++offset;
}
}
NS_ASSERTION(offset - aOffset == aSrc->GetLength(),
"Something went wrong in our length calculations...");
}
/**
* Copy a given textrun, but merge certain characters into a single logical
* character. Glyphs for a character are added to the glyph list for the previous
@ -317,7 +282,7 @@ GetParametersForInner(nsTransformedTextRun* aTextRun, PRUint32* aFlags,
{
gfxTextRunFactory::Parameters params =
{ aRefContext, nsnull, nsnull,
nsnull, nsnull, PRUint32(aTextRun->GetAppUnitsPerDevUnit())
nsnull, nsnull, aTextRun->GetAppUnitsPerDevUnit()
};
*aFlags = aTextRun->GetFlags() & ~gfxFontGroup::TEXT_IS_PERSISTENT;
return params;
@ -341,17 +306,14 @@ nsFontVariantTextRunFactory::RebuildTextRun(nsTransformedTextRun* aTextRun,
PRUint32 flags;
gfxTextRunFactory::Parameters innerParams =
GetParametersForInner(aTextRun, &flags, aRefContext);
// The text outlives the child and inner textruns
flags |= gfxFontGroup::TEXT_IS_PERSISTENT;
PRUint32 length = aTextRun->GetLength();
const PRUnichar* str = aTextRun->GetTextUnicode();
nsRefPtr<nsStyleContext>* styles = aTextRun->mStyles.Elements();
// Create a textrun so we can check cluster-start properties
nsAutoPtr<gfxTextRun> inner;
// This text is going to outlive the inner text run
inner = fontGroup->MakeTextRun(str, length, &innerParams, flags);
if (!inner)
gfxTextRunCache::AutoTextRun inner(
gfxTextRunCache::MakeTextRun(str, length, fontGroup, &innerParams, flags));
if (!inner.get())
return;
nsCaseTransformTextRunFactory uppercaseFactory(nsnull, PR_TRUE);
@ -392,16 +354,21 @@ nsFontVariantTextRunFactory::RebuildTextRun(nsTransformedTextRun* aTextRun,
}
if ((i == length || runIsLowercase != isLowercase) && runStart < i) {
nsAutoPtr<gfxTextRun> child;
nsAutoPtr<gfxTextRun> transformedChild;
gfxTextRunCache::AutoTextRun cachedChild;
gfxTextRun* child;
// Setup actual line break data for child (which may affect shaping)
innerParams.mInitialBreaks = lineBreakBeforeArray.Elements();
innerParams.mInitialBreakCount = lineBreakBeforeArray.Length();
if (runIsLowercase) {
child = uppercaseFactory.MakeTextRun(str + runStart, i - runStart,
transformedChild = uppercaseFactory.MakeTextRun(str + runStart, i - runStart,
&innerParams, smallFont, flags, styleArray.Elements(), PR_FALSE);
child = transformedChild;
} else {
child = fontGroup->
MakeTextRun(str + runStart, i - runStart, &innerParams, flags);
cachedChild =
gfxTextRunCache::MakeTextRun(str + runStart, i - runStart, fontGroup,
&innerParams, flags);
child = cachedChild.get();
}
if (!child)
return;
@ -411,7 +378,7 @@ nsFontVariantTextRunFactory::RebuildTextRun(nsTransformedTextRun* aTextRun,
"lost some break-before values?");
child->SetPotentialLineBreaks(0, canBreakBeforeArray.Length(),
canBreakBeforeArray.Elements(), aRefContext);
AppendTextRun(aTextRun, child, runStart);
aTextRun->CopyGlyphDataFrom(child, 0, child->GetLength(), runStart, PR_FALSE);
runStart = i;
styleArray.Clear();
@ -518,20 +485,22 @@ nsCaseTransformTextRunFactory::RebuildTextRun(nsTransformedTextRun* aTextRun,
GetParametersForInner(aTextRun, &flags, aRefContext);
gfxFontGroup* fontGroup = aTextRun->GetFontGroup();
nsAutoPtr<gfxTextRun> child;
nsAutoPtr<gfxTextRun> transformedChild;
gfxTextRunCache::AutoTextRun cachedChild;
gfxTextRun* child;
// Setup actual line break data for child (which may affect shaping)
innerParams.mInitialBreaks = lineBreakBeforeArray.Elements();
innerParams.mInitialBreakCount = lineBreakBeforeArray.Length();
// The text outlives 'child'
flags |= gfxFontGroup::TEXT_IS_PERSISTENT;
if (mInnerTransformingTextRunFactory) {
child = mInnerTransformingTextRunFactory->MakeTextRun(
transformedChild = mInnerTransformingTextRunFactory->MakeTextRun(
convertedString.BeginReading(), convertedString.Length(),
&innerParams, fontGroup, flags, styleArray.Elements(), PR_FALSE);
child = transformedChild.get();
} else {
child = fontGroup->MakeTextRun(
convertedString.BeginReading(), convertedString.Length(), &innerParams,
flags);
cachedChild = gfxTextRunCache::MakeTextRun(
convertedString.BeginReading(), convertedString.Length(), fontGroup,
&innerParams, flags);
child = cachedChild.get();
}
if (!child)
return;

View File

@ -54,6 +54,7 @@
#include "gfxContext.h"
#include "gfxMatrix.h"
#include "gfxPlatform.h"
#include "gfxTextRunCache.h"
// XXX: This initial straightforward conversion from accessing cairo
// directly to Thebes doesn't handle clusters. Pretty much all code
@ -215,9 +216,9 @@ nsSVGGlyphFrame::LoopCharacters(gfxContext *aCtx, const nsString &aText,
const nsSVGCharacterPosition *aCP,
FillOrStroke aFillOrStroke)
{
nsAutoPtr<gfxTextRun> textRun(GetTextRun(aCtx, aText));
gfxTextRunCache::AutoTextRun textRun = GetTextRun(aCtx, aText);
if (!textRun)
if (!textRun.get())
return;
if (!aCP) {
@ -606,8 +607,8 @@ nsSVGGlyphFrame::GetCharacterPosition(gfxContext *aContext,
gfxFloat length = data->GetLength();
PRUint32 strLength = aText.Length();
nsAutoPtr<gfxTextRun> textRun(GetTextRun(aContext, aText));
if (!textRun)
gfxTextRunCache::AutoTextRun textRun = GetTextRun(aContext, aText);
if (!textRun.get())
return NS_ERROR_OUT_OF_MEMORY;
nsSVGCharacterPosition *cp = new nsSVGCharacterPosition[strLength];
@ -1347,17 +1348,11 @@ nsSVGGlyphFrame::GetTextRun(gfxContext *aCtx, const nsString &aText)
// References:
// https://bugzilla.mozilla.org/show_bug.cgi?id=375141
// http://weblogs.mozillazine.org/roc/archives/2007/03/text_text_text.html
gfxTextRunFactory::Parameters params =
{ aCtx, nsnull, nsnull,
nsnull, nsnull,
1 // see note above
};
if (!mFontGroup)
return nsnull;
return mFontGroup->MakeTextRun(aText.get(), aText.Length(), &params, 0);
return gfxTextRunCache::MakeTextRun(aText.get(), aText.Length(),
mFontGroup, aCtx, 1, 0);
}
//----------------------------------------------------------------------

View File

@ -44,6 +44,7 @@
#include "nsISVGChildFrame.h"
#include "gfxContext.h"
#include "gfxFont.h"
#include "gfxTextRunCache.h"
struct nsSVGCharacterPosition;
class nsSVGTextFrame;
@ -164,7 +165,7 @@ protected:
friend class nsSVGAutoGlyphHelperContext;
// A helper class to deal with gfxTextRuns and temporary thebes
// contexts. It destroys them when it goes out of scope.
// contexts.
class nsSVGAutoGlyphHelperContext
{
public:
@ -179,15 +180,16 @@ protected:
nsSVGCharacterPosition **cp);
gfxContext *GetContext() { return mCT; }
gfxTextRun *GetTextRun() { return mTextRun; }
gfxTextRun *GetTextRun() { return mTextRun.get(); }
private:
void Init(nsSVGGlyphFrame *aSource, const nsString &aText);
nsRefPtr<gfxContext> mCT;
nsAutoPtr<gfxTextRun> mTextRun;
nsRefPtr<gfxContext> mCT;
gfxTextRunCache::AutoTextRun mTextRun;
};
// The textrun must be released via gfxTextRunCache::AutoTextRun
gfxTextRun *GetTextRun(gfxContext *aCtx,
const nsString &aText);