--- a/gfx/thebes/src/gfxTextRunWordCache.cpp
+++ b/gfx/thebes/src/gfxTextRunWordCache.cpp
@@ -155,21 +155,33 @@ protected:
// 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);
- void AddWord(gfxTextRun *aTextRun, PRUint32 aDestOffset,
- gfxTextRun *aNewRun, PRUint32 aSourceOffset, PRUint32 aLength,
- PRUint32 aHash, const gfxFontGroup::Parameters *aParams);
+ PRUint32 aStart, PRUint32 aEnd, PRUint32 aHash,
+ nsTArray<DeferredWord>* aDeferredWords);
+ void FinishTextRun(gfxTextRun *aTextRun, gfxTextRun *aNewRun,
+ const gfxFontGroup::Parameters *aParams,
+ const nsTArray<DeferredWord>& aDeferredWords,
+ PRBool aSuccessful);
void RemoveWord(gfxTextRun *aTextRun, PRUint32 aStart,
PRUint32 aEnd, PRUint32 aHash);
nsTHashtable<CacheHashEntry> mCache;
#ifdef DEBUG
static PLDHashOperator PR_CALLBACK CacheDumpEntry(CacheHashEntry* aEntry, void* userArg);
#endif
@@ -218,121 +230,211 @@ static PRBool
IsWordBoundary(PRUnichar aChar)
{
return IsBoundarySpace(aChar) || gfxFontGroup::IsInvalidChar(aChar);
}
/**
* Looks up a word in the cache. If the word is found in the cache
* (which could be from an existing textrun or an earlier word in the same
- * textrun), we copy the glyphs from the word into the textrun.
+ * textrun), we copy the glyphs from the word into the textrun, unless
+ * aDeferredWords is non-null (meaning that all words from now on must be
+ * copied later instead of now), in which case we add the word to be copied
+ * to the list.
+ *
+ * If the word is not found in the cache then we add it to the cache with
+ * aFirstFont as the key, on the assumption that the word will be matched
+ * by aFirstFont. If this assumption is false we fix up the cache later in
+ * FinishTextRun. We make this assumption for two reasons:
+ * 1) it's usually true so it saves an extra cache lookup if we had to insert
+ * the entry later
+ * 2) we need to record words that appear in the string in some kind
+ * of hash table so we can detect and use them if they appear later in the
+ * (in general the string might be huge and contain many repeated words).
+ * We might as well use the master hash table for this.
*
* @return true if the word was found in the cache, false otherwise.
*/
PRBool
TextRunWordCache::LookupWord(gfxTextRun *aTextRun, gfxFont *aFirstFont,
- PRUint32 aStart, PRUint32 aEnd, PRUint32 aHash)
+ PRUint32 aStart, PRUint32 aEnd, PRUint32 aHash,
+ nsTArray<DeferredWord>* aDeferredWords)
{
if (aEnd <= aStart)
return PR_TRUE;
- // Look for an entry keyed by font or fontgroup
CacheHashKey key(aTextRun, aFirstFont, aStart, aEnd - aStart, aHash);
- CacheHashEntry *existingEntry = mCache.GetEntry(key);
- if (!existingEntry) {
+ CacheHashEntry *fontEntry = mCache.PutEntry(key);
+ if (!fontEntry)
+ return PR_FALSE;
+ CacheHashEntry *existingEntry = nsnull;
+
+ if (fontEntry->mTextRun) {
+ existingEntry = fontEntry;
+ } else {
+ PR_LOG(gWordCacheLog, PR_LOG_DEBUG, ("%p(%d-%d,%d): added using font", aTextRun, aStart, aEnd - aStart, aHash));
key.mFontOrGroup = aTextRun->GetFontGroup();
- existingEntry = mCache.GetEntry(key);
+ CacheHashEntry *groupEntry = mCache.GetEntry(key);
+ if (groupEntry) {
+ existingEntry = groupEntry;
+ mCache.RawRemoveEntry(fontEntry);
+ PR_LOG(gWordCacheLog, PR_LOG_DEBUG, ("%p(%d-%d,%d): removed using font", aTextRun, aStart, aEnd - aStart, aHash));
+ fontEntry = nsnull;
+ }
+ }
+ // At this point, either existingEntry is non-null and points to (surprise!)
+ // an entry for a word in an existing textrun, or fontEntry points
+ // to a cache entry for this word with aFirstFont, which needs to be
+ // filled in or removed.
+
+ if (existingEntry) {
+ if (aDeferredWords) {
+ DeferredWord word = { existingEntry->mTextRun,
+ existingEntry->mWordOffset, aStart, aEnd - aStart, aHash };
+ aDeferredWords->AppendElement(word);
+ } else {
+ aTextRun->CopyGlyphDataFrom(existingEntry->mTextRun,
+ existingEntry->mWordOffset, aEnd - aStart, aStart, PR_FALSE);
+ }
+ return PR_TRUE;
}
- if (!existingEntry)
- return PR_FALSE;
-
- // Copy the glyphs from the word into the text run
- aTextRun->CopyGlyphDataFrom(existingEntry->mTextRun,
- existingEntry->mWordOffset,
- aEnd - aStart, aStart, PR_FALSE);
- return PR_TRUE;
+#ifdef DEBUG
+ ++aTextRun->mCachedWords;
+#endif
+ // Set up the cache entry so that if later in this textrun we hit this
+ // entry, we'll copy within our own textrun
+ fontEntry->mTextRun = aTextRun;
+ fontEntry->mWordOffset = aStart;
+ fontEntry->mHashedByFont = PR_TRUE;
+ return PR_FALSE;
}
/**
- * Processes a word that doesn't yet exist in the cache. The word is copied
- * from the source textrun to the output textrun. (The source may be an
- * earlier word in the output textrun.)
+ * Processes all deferred words. Each word is copied from the source
+ * textrun to the output textrun. (The source may be an earlier word in the
+ * output textrun.) If the word was not matched by the textrun's fontgroup's
+ * first font, then we remove the entry we optimistically added to the cache
+ * with that font in the key, and add a new entry keyed with the fontgroup
+ * instead.
+ *
+ * @param aSuccessful if false, then we don't do any word copies and we don't
+ * add anything to the cache, but we still remove all the optimistic cache
+ * entries.
*/
void
-TextRunWordCache::AddWord(gfxTextRun *aTextRun, PRUint32 aDestOffset,
- gfxTextRun *aNewRun, PRUint32 aSourceOffset,
- PRUint32 aLength, PRUint32 aHash,
- const gfxFontGroup::Parameters *aParams)
+TextRunWordCache::FinishTextRun(gfxTextRun *aTextRun, gfxTextRun *aNewRun,
+ const gfxFontGroup::Parameters *aParams,
+ const nsTArray<DeferredWord>& aDeferredWords,
+ PRBool aSuccessful)
{
- gfxTextRun *source = aNewRun;
- PRUint32 sourceOffset = aSourceOffset;
- PRUint32 destOffset = aDestOffset;
- PRUint32 length = aLength;
- nsAutoPtr<gfxTextRun> tmpTextRun;
-
- // If the word starts inside a cluster we don't want this word
- // in the cache.
- PRBool wordStartsInsideCluster =
- aNewRun && !aNewRun->IsClusterStart(aSourceOffset);
+ aTextRun->SetFlagBits(gfxTextRunWordCache::TEXT_IN_CACHE);
- if (!wordStartsInsideCluster) {
- CacheHashKey key(aTextRun,
- GetWordFontOrGroup(aNewRun, aSourceOffset, aLength),
- aDestOffset, aLength, aHash);
- CacheHashEntry *entry = mCache.PutEntry(key);
- NS_ASSERTION(!entry->mTextRun, "Entry shouldn't have existed!");
-
- if (entry) {
- aTextRun->SetFlagBits(gfxTextRunWordCache::TEXT_IN_CACHE);
-
- entry->mTextRun = aTextRun;
- entry->mWordOffset = aDestOffset;
- gfxFontGroup *fontGroup = aTextRun->GetFontGroup();
- entry->mHashedByFont = key.mFontOrGroup != fontGroup;
+ PRUint32 i;
+ gfxFontGroup *fontGroup = aTextRun->GetFontGroup();
+ gfxFont *font = fontGroup->GetFontAt(0);
+ // copy deferred words from various sources into destination textrun
+ for (i = 0; i < aDeferredWords.Length(); ++i) {
+ const DeferredWord *word = &aDeferredWords[i];
+ gfxTextRun *source = word->mSourceTextRun;
+ if (!source) {
+ source = aNewRun;
+ }
+ // If the word starts inside a cluster we don't want this word
+ // in the cache, so we'll remove the associated cache entry
+ PRBool wordStartsInsideCluster =
+ !source->IsClusterStart(word->mSourceOffset);
+ if (source == aNewRun) {
+ // We created a cache entry for this word based on the assumption
+ // that the word matches GetFontAt(0). If this assumption is false,
+ // we need to remove that cache entry and replace it with an entry
+ // keyed off the fontgroup.
+ PRBool rekeyWithFontGroup =
+ GetWordFontOrGroup(aNewRun, word->mSourceOffset, word->mLength) != font;
+ if (!aSuccessful || wordStartsInsideCluster || rekeyWithFontGroup) {
+ // We need to remove the current placeholder cache entry
+ CacheHashKey key(aTextRun, font, word->mDestOffset, word->mLength,
+ word->mHash);
+ NS_ASSERTION(mCache.GetEntry(key),
+ "This entry should have been added previously!");
+ mCache.RemoveEntry(key);
+#ifdef DEBUG
+ --aTextRun->mCachedWords;
+#endif
+ PR_LOG(gWordCacheLog, PR_LOG_DEBUG, ("%p(%d-%d,%d): removed using font", aTextRun, word->mDestOffset, word->mLength, word->mHash));
+
+ if (aSuccessful && !wordStartsInsideCluster) {
+ key.mFontOrGroup = fontGroup;
+ CacheHashEntry *groupEntry = mCache.PutEntry(key);
+ if (groupEntry) {
#ifdef DEBUG
- ++aTextRun->mCachedWords;
- PR_LOG(gWordCacheLog, PR_LOG_DEBUG,
- ("%p(%d-%d,%d): added using %s",
- aTextRun, aDestOffset, aLength, aHash,
- entry->mHashedByFont ? "font" :"fontgroup"));
- NS_ASSERTION(mCache.GetEntry(key),
- "We should find the thing we just added!");
+ ++aTextRun->mCachedWords;
#endif
+ PR_LOG(gWordCacheLog, PR_LOG_DEBUG, ("%p(%d-%d,%d): added using fontgroup", aTextRun, word->mDestOffset, word->mLength, word->mHash));
+ groupEntry->mTextRun = aTextRun;
+ groupEntry->mWordOffset = word->mDestOffset;
+ groupEntry->mHashedByFont = PR_FALSE;
+ NS_ASSERTION(mCache.GetEntry(key),
+ "We should find the thing we just added!");
+ }
+ }
+ }
+ }
+ if (aSuccessful) {
+ // Copy the word. If the source is aNewRun, then
+ // allow CopyGlyphDataFrom to steal the internal data of
+ // aNewRun since that's only temporary anyway.
+ PRUint32 sourceOffset = word->mSourceOffset;
+ PRUint32 destOffset = word->mDestOffset;
+ PRUint32 length = word->mLength;
+ nsAutoPtr<gfxTextRun> tmpTextRun;
+ PRBool stealData = source == aNewRun;
+ if (wordStartsInsideCluster) {
+ NS_ASSERTION(sourceOffset > 0, "How can the first character be inside a cluster?");
+ if (destOffset > 0 && IsBoundarySpace(aTextRun->GetChar(destOffset - 1))) {
+ // We should copy over data for the preceding space
+ // as well. The glyphs have probably been attached
+ // to that character.
+ --sourceOffset;
+ --destOffset;
+ ++length;
+ } else {
+ // URK! This case sucks! We have combining marks
+ // at the start of the text. We had to prepend a space
+ // just so we could detect these are combining marks
+ // (so we can keep this "word" out of the cache).
+ // But now the data in aNewRun is no use to us. We
+ // need to find out what the platform would do
+ // if the marks were at the start of the text.
+ tmpTextRun = aNewRun->GetFontGroup()->MakeTextRun(
+ aTextRun->GetTextUnicode(), length, aParams,
+ aNewRun->GetFlags());
+ source = tmpTextRun;
+ sourceOffset = 0;
+ stealData = PR_TRUE;
+ }
+ }
+ aTextRun->CopyGlyphDataFrom(source, sourceOffset, length,
+ destOffset, stealData);
+ // Fill in additional spaces
+ PRUint32 endCharIndex;
+ if (i + 1 < aDeferredWords.Length()) {
+ endCharIndex = aDeferredWords[i + 1].mDestOffset;
+ } else {
+ endCharIndex = aTextRun->GetLength();
+ }
+ PRUint32 charIndex;
+ for (charIndex = word->mDestOffset + word->mLength;
+ charIndex < endCharIndex; ++charIndex) {
+ if (IsBoundarySpace(aTextRun->GetChar(charIndex))) {
+ aTextRun->SetSpaceGlyph(font, aParams->mContext, charIndex);
+ }
+ }
}
}
- else { // wordStartsInsideCluster
- NS_ASSERTION(sourceOffset > 0, "How can the first character be inside a cluster?");
- if (destOffset > 0 && IsBoundarySpace(aTextRun->GetChar(destOffset - 1))) {
- // We should copy over data for the preceding space
- // as well. The glyphs have probably been attached
- // to that character.
- --sourceOffset;
- --destOffset;
- ++length;
- } else {
- // URK! This case sucks! We have combining marks
- // at the start of the text. We had to prepend a space
- // just so we could detect these are combining marks
- // (so we can keep this "word" out of the cache).
- // But now the data in aNewRun is no use to us. We
- // need to find out what the platform would do
- // if the marks were at the start of the text.
- tmpTextRun = aNewRun->GetFontGroup()->
- MakeTextRun(aTextRun->GetTextUnicode(), length, aParams,
- aNewRun->GetFlags());
- source = tmpTextRun;
- sourceOffset = 0;
- }
- }
- // Copy the word.
- // Allow CopyGlyphDataFrom to steal the internal data of
- // aNewRun or tmpTextRun since they are only temporary anyway.
- aTextRun->CopyGlyphDataFrom(source, sourceOffset, length,
- destOffset, PR_TRUE);
}
static gfxTextRun *
MakeBlankTextRun(const void* aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
const gfxFontGroup::Parameters *aParams,
PRUint32 aFlags)
{
@@ -365,64 +467,70 @@ TextRunWordCache::MakeTextRun(const PRUn
#ifdef DEBUG
textRun->mCachedWords = 0;
#endif
gfxFont *font = aFontGroup->GetFontAt(0);
nsresult rv = textRun->AddGlyphRun(font, 0);
NS_ENSURE_SUCCESS(rv, nsnull);
- gfxTextRunFactory::Parameters params =
- { aParams->mContext, nsnull, nsnull, nsnull, 0, aParams->mAppUnitsPerDevUnit };
nsAutoTArray<PRUnichar,200> tempString;
+ nsAutoTArray<DeferredWord,50> deferredWords;
PRUint32 i;
PRUint32 wordStart = 0;
PRUint32 hash = 0;
for (i = 0; i <= aLength; ++i) {
PRUnichar ch = i < aLength ? aText[i] : ' ';
if (IsWordBoundary(ch)) {
- PRBool hit = LookupWord(textRun, font, wordStart, i, hash);
+ PRBool hit = LookupWord(textRun, font, wordStart, i, hash,
+ deferredWords.Length() == 0 ? nsnull : &deferredWords);
if (!hit) {
// Always put a space before the word so we can detect
// combining characters at the start of a word
tempString.AppendElement(' ');
PRUint32 offset = tempString.Length();
PRUint32 length = i - wordStart;
-
PRUnichar *chars = tempString.AppendElements(length);
- if (!chars)
+ if (!chars) {
+ FinishTextRun(textRun, nsnull, nsnull, deferredWords, PR_FALSE);
return nsnull;
+ }
memcpy(chars, aText + wordStart, length*sizeof(PRUnichar));
-
- // create textrun for this unknown word
- nsAutoPtr<gfxTextRun> newRun;
- newRun = aFontGroup->
- MakeTextRun(tempString.Elements(), tempString.Length(),
- ¶ms,
- aFlags | gfxTextRunFactory::TEXT_IS_PERSISTENT);
-
- if (newRun) {
- AddWord(textRun, wordStart, newRun, offset, length, hash,
- aParams);
- }
- tempString.Clear();
+ DeferredWord word = { nsnull, offset, wordStart, length, hash };
+ deferredWords.AppendElement(word);
}
- if (IsBoundarySpace(ch) && i < aLength) {
- textRun->SetSpaceGlyph(font, aParams->mContext, i);
- } // else we should set this character to be invisible missing,
- // but it already is because the textrun is blank!
-
+ if (deferredWords.Length() == 0) {
+ if (IsBoundarySpace(ch) && i < aLength) {
+ textRun->SetSpaceGlyph(font, aParams->mContext, i);
+ } // else we should set this character to be invisible missing,
+ // but it already is because the textrun is blank!
+ }
hash = 0;
wordStart = i + 1;
} else {
hash = HashMix(hash, ch);
}
}
+ if (deferredWords.Length() == 0) {
+ // We got everything from the cache, so we're done. No point in calling
+ // FinishTextRun.
+ // This textrun is not referenced by the cache.
+ return textRun.forget();
+ }
+
+ // create textrun for unknown words
+ gfxTextRunFactory::Parameters params =
+ { aParams->mContext, nsnull, nsnull, nsnull, 0, aParams->mAppUnitsPerDevUnit };
+ nsAutoPtr<gfxTextRun> newRun;
+ newRun = aFontGroup->MakeTextRun(tempString.Elements(), tempString.Length(),
+ ¶ms, aFlags | gfxTextRunFactory::TEXT_IS_PERSISTENT);
+
+ FinishTextRun(textRun, newRun, aParams, deferredWords, newRun != nsnull);
return textRun.forget();
}
gfxTextRun *
TextRunWordCache::MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
const gfxFontGroup::Parameters *aParams,
PRUint32 aFlags)
@@ -442,51 +550,70 @@ TextRunWordCache::MakeTextRun(const PRUi
#ifdef DEBUG
textRun->mCachedWords = 0;
#endif
gfxFont *font = aFontGroup->GetFontAt(0);
nsresult rv = textRun->AddGlyphRun(font, 0);
NS_ENSURE_SUCCESS(rv, nsnull);
- gfxTextRunFactory::Parameters params =
- { aParams->mContext, nsnull, nsnull, nsnull, 0, aParams->mAppUnitsPerDevUnit };
+ nsAutoTArray<PRUint8,200> tempString;
+ nsAutoTArray<DeferredWord,50> deferredWords;
PRUint32 i;
PRUint32 wordStart = 0;
PRUint32 hash = 0;
for (i = 0; i <= aLength; ++i) {
PRUint8 ch = i < aLength ? aText[i] : ' ';
if (IsWordBoundary(ch)) {
- PRBool hit = LookupWord(textRun, font, wordStart, i, hash);
+ PRBool hit = LookupWord(textRun, font, wordStart, i, hash,
+ deferredWords.Length() == 0 ? nsnull : &deferredWords);
if (!hit) {
+ if (tempString.Length() > 0) {
+ tempString.AppendElement(' ');
+ }
+ PRUint32 offset = tempString.Length();
PRUint32 length = i - wordStart;
-
- // create textrun for this unknown word
- nsAutoPtr<gfxTextRun> newRun;
- newRun = aFontGroup->
- MakeTextRun(aText + wordStart, length, ¶ms,
- aFlags | gfxTextRunFactory::TEXT_IS_PERSISTENT);
- if (newRun) {
- AddWord(textRun, wordStart, newRun, 0, length, hash,
- aParams);
+ PRUint8 *chars = tempString.AppendElements(length);
+ if (!chars) {
+ FinishTextRun(textRun, nsnull, nsnull, deferredWords, PR_FALSE);
+ return nsnull;
}
+ memcpy(chars, aText + wordStart, length*sizeof(PRUint8));
+ DeferredWord word = { nsnull, offset, wordStart, length, hash };
+ deferredWords.AppendElement(word);
}
- if (IsBoundarySpace(ch) && i < aLength) {
- textRun->SetSpaceGlyph(font, aParams->mContext, i);
- } // else we should set this character to be invisible missing,
- // but it already is because the textrun is blank!
-
+ if (deferredWords.Length() == 0) {
+ if (IsBoundarySpace(ch) && i < aLength) {
+ textRun->SetSpaceGlyph(font, aParams->mContext, i);
+ } // else we should set this character to be invisible missing,
+ // but it already is because the textrun is blank!
+ }
hash = 0;
wordStart = i + 1;
} else {
hash = HashMix(hash, ch);
}
}
+ if (deferredWords.Length() == 0) {
+ // We got everything from the cache, so we're done. No point in calling
+ // FinishTextRun.
+ // This textrun is not referenced by the cache.
+ return textRun.forget();
+ }
+
+ // create textrun for unknown words
+ gfxTextRunFactory::Parameters params =
+ { aParams->mContext, nsnull, nsnull, nsnull, 0, aParams->mAppUnitsPerDevUnit };
+ nsAutoPtr<gfxTextRun> newRun;
+ newRun = aFontGroup->MakeTextRun(tempString.Elements(), tempString.Length(),
+ ¶ms, aFlags | gfxTextRunFactory::TEXT_IS_PERSISTENT);
+
+ FinishTextRun(textRun, newRun, aParams, deferredWords, newRun != nsnull);
return textRun.forget();
}
void
TextRunWordCache::RemoveWord(gfxTextRun *aTextRun, PRUint32 aStart,
PRUint32 aEnd, PRUint32 aHash)
{
if (aEnd <= aStart)
@@ -607,17 +734,17 @@ TextRunWordCache::CacheHashEntry::HashKe
PLDHashOperator PR_CALLBACK
TextRunWordCache::CacheDumpEntry(CacheHashEntry* aEntry, void* userArg)
{
FILE* output = static_cast<FILE*>(userArg);
if (!aEntry->mTextRun) {
fprintf(output, "<EMPTY>\n");
return PL_DHASH_NEXT;
}
- fprintf(output, "Word at %p:%d => ", static_cast<void*>(aEntry->mTextRun), aEntry->mWordOffset);
+ fprintf(output, "Word at %x:%d => ", aEntry->mTextRun, aEntry->mWordOffset);
aEntry->mTextRun->Dump(output);
fprintf(output, " (hashed by %s)\n", aEntry->mHashedByFont ? "font" : "fontgroup");
return PL_DHASH_NEXT;
}
void
TextRunWordCache::Dump()
{