From b336fb5a00f512171966e023bdc1183f925c4206 Mon Sep 17 00:00:00 2001 From: "karlt+@karlt.net" Date: Sun, 17 Feb 2008 13:43:23 -0800 Subject: [PATCH] gfxFontGroup::MakeTextRun with individual words. b=416725, r=roc --- gfx/thebes/src/gfxTextRunWordCache.cpp | 379 ++++++++----------------- 1 file changed, 126 insertions(+), 253 deletions(-) diff --git a/gfx/thebes/src/gfxTextRunWordCache.cpp b/gfx/thebes/src/gfxTextRunWordCache.cpp index 25b65285ec8e..34c38a5bfaf3 100644 --- a/gfx/thebes/src/gfxTextRunWordCache.cpp +++ b/gfx/thebes/src/gfxTextRunWordCache.cpp @@ -160,23 +160,11 @@ protected: 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* aDeferredWords); - void FinishTextRun(gfxTextRun *aTextRun, gfxTextRun *aNewRun, - const gfxFontGroup::Parameters *aParams, - const nsTArray& aDeferredWords, - PRBool aSuccessful); + PRUint32 aStart, PRUint32 aEnd, PRUint32 aHash); + void AddWord(gfxTextRun *aTextRun, PRUint32 aDestOffset, + gfxTextRun *aNewRun, PRUint32 aSourceOffset, PRUint32 aLength, + PRUint32 aHash, const gfxFontGroup::Parameters *aParams); void RemoveWord(gfxTextRun *aTextRun, PRUint32 aStart, PRUint32 aEnd, PRUint32 aHash); @@ -235,201 +223,111 @@ IsWordBoundary(PRUnichar 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, 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. + * textrun), we copy the glyphs from the word into the textrun. * * @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, - nsTArray* aDeferredWords) + PRUint32 aStart, PRUint32 aEnd, PRUint32 aHash) { if (aEnd <= aStart) return PR_TRUE; + // Look for an entry keyed by font or fontgroup CacheHashKey key(aTextRun, aFirstFont, aStart, aEnd - aStart, aHash); - 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)); + CacheHashEntry *existingEntry = mCache.GetEntry(key); + if (!existingEntry) { key.mFontOrGroup = aTextRun->GetFontGroup(); - 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; + existingEntry = mCache.GetEntry(key); } -#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; + 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; } /** - * 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. + * 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.) */ void -TextRunWordCache::FinishTextRun(gfxTextRun *aTextRun, gfxTextRun *aNewRun, - const gfxFontGroup::Parameters *aParams, - const nsTArray& aDeferredWords, - PRBool aSuccessful) +TextRunWordCache::AddWord(gfxTextRun *aTextRun, PRUint32 aDestOffset, + gfxTextRun *aNewRun, PRUint32 aSourceOffset, + PRUint32 aLength, PRUint32 aHash, + const gfxFontGroup::Parameters *aParams) { - aTextRun->SetFlagBits(gfxTextRunWordCache::TEXT_IN_CACHE); + gfxTextRun *source = aNewRun; + PRUint32 sourceOffset = aSourceOffset; + PRUint32 destOffset = aDestOffset; + PRUint32 length = aLength; + nsAutoPtr tmpTextRun; - 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); + // If the word starts inside a cluster we don't want this word + // in the cache. + PRBool wordStartsInsideCluster = + aNewRun && !aNewRun->IsClusterStart(aSourceOffset); + + 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; #ifdef DEBUG - --aTextRun->mCachedWords; + ++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!"); #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; -#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 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 * @@ -472,38 +370,47 @@ TextRunWordCache::MakeTextRun(const PRUnichar *aText, PRUint32 aLength, nsresult rv = textRun->AddGlyphRun(font, 0); NS_ENSURE_SUCCESS(rv, nsnull); + gfxTextRunFactory::Parameters params = + { aParams->mContext, nsnull, nsnull, nsnull, 0, aParams->mAppUnitsPerDevUnit }; nsAutoTArray tempString; - nsAutoTArray 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, - deferredWords.Length() == 0 ? nsnull : &deferredWords); + PRBool hit = LookupWord(textRun, font, wordStart, i, hash); 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) { - FinishTextRun(textRun, nsnull, nsnull, deferredWords, PR_FALSE); + if (!chars) return nsnull; - } memcpy(chars, aText + wordStart, length*sizeof(PRUnichar)); - DeferredWord word = { nsnull, offset, wordStart, length, hash }; - deferredWords.AppendElement(word); + + // create textrun for this unknown word + nsAutoPtr 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(); } - 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! - } + 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 { @@ -511,21 +418,6 @@ TextRunWordCache::MakeTextRun(const PRUnichar *aText, PRUint32 aLength, } } - 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 newRun; - newRun = aFontGroup->MakeTextRun(tempString.Elements(), tempString.Length(), - ¶ms, aFlags | gfxTextRunFactory::TEXT_IS_PERSISTENT); - - FinishTextRun(textRun, newRun, aParams, deferredWords, newRun != nsnull); return textRun.forget(); } @@ -555,38 +447,34 @@ TextRunWordCache::MakeTextRun(const PRUint8 *aText, PRUint32 aLength, nsresult rv = textRun->AddGlyphRun(font, 0); NS_ENSURE_SUCCESS(rv, nsnull); - nsAutoTArray tempString; - nsAutoTArray deferredWords; + gfxTextRunFactory::Parameters params = + { aParams->mContext, nsnull, nsnull, nsnull, 0, aParams->mAppUnitsPerDevUnit }; 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, - deferredWords.Length() == 0 ? nsnull : &deferredWords); + PRBool hit = LookupWord(textRun, font, wordStart, i, hash); if (!hit) { - if (tempString.Length() > 0) { - tempString.AppendElement(' '); - } - PRUint32 offset = tempString.Length(); PRUint32 length = i - wordStart; - PRUint8 *chars = tempString.AppendElements(length); - if (!chars) { - FinishTextRun(textRun, nsnull, nsnull, deferredWords, PR_FALSE); - return nsnull; + + // create textrun for this unknown word + nsAutoPtr newRun; + newRun = aFontGroup-> + MakeTextRun(aText + wordStart, length, ¶ms, + aFlags | gfxTextRunFactory::TEXT_IS_PERSISTENT); + if (newRun) { + AddWord(textRun, wordStart, newRun, 0, length, hash, + aParams); } - memcpy(chars, aText + wordStart, length*sizeof(PRUint8)); - DeferredWord word = { nsnull, offset, wordStart, length, hash }; - deferredWords.AppendElement(word); } - 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! - } + 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 { @@ -594,21 +482,6 @@ TextRunWordCache::MakeTextRun(const PRUint8 *aText, PRUint32 aLength, } } - 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 newRun; - newRun = aFontGroup->MakeTextRun(tempString.Elements(), tempString.Length(), - ¶ms, aFlags | gfxTextRunFactory::TEXT_IS_PERSISTENT); - - FinishTextRun(textRun, newRun, aParams, deferredWords, newRun != nsnull); return textRun.forget(); } @@ -739,7 +612,7 @@ TextRunWordCache::CacheDumpEntry(CacheHashEntry* aEntry, void* userArg) fprintf(output, "\n"); return PL_DHASH_NEXT; } - fprintf(output, "Word at %x:%d => ", aEntry->mTextRun, aEntry->mWordOffset); + fprintf(output, "Word at %p:%d => ", static_cast(aEntry->mTextRun), aEntry->mWordOffset); aEntry->mTextRun->Dump(output); fprintf(output, " (hashed by %s)\n", aEntry->mHashedByFont ? "font" : "fontgroup"); return PL_DHASH_NEXT;