Bug 384100. Implement word-based textrun cache. r=vlad

This commit is contained in:
roc+@cs.cmu.edu 2007-06-12 13:56:04 -07:00
parent 36c86d446a
commit 622f3bf11b
7 changed files with 792 additions and 55 deletions

View File

@ -25,6 +25,7 @@ EXPORTS = gfxASurface.h \
gfxSkipChars.h \
gfxTypes.h \
gfxTextRunCache.h \
gfxTextRunWordCache.h \
$(NULL)
EXPORTS += gfxFontTest.h

View File

@ -430,6 +430,10 @@ 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,
PLATFORM_TEXT_FLAGS = 0x0000F000,
TEXTRUN_TEXT_FLAGS = 0x00000FFF,
/**
* When set, the text string pointer used to create the text run
* is guaranteed to be available during the lifetime of the text run.
@ -789,6 +793,16 @@ public:
void *GetUserData() const { return mUserData; }
void SetUserData(void *aUserData) { mUserData = aUserData; }
PRUint32 GetFlags() const { return mFlags; }
void SetFlagBits(PRUint32 aFlags) {
NS_ASSERTION(!(aFlags & ~gfxTextRunFactory::USER_TEXT_FLAGS),
"Only user flags should be mutable");
mFlags |= aFlags;
}
void ClearFlagBits(PRUint32 aFlags) {
NS_ASSERTION(!(aFlags & ~gfxTextRunFactory::USER_TEXT_FLAGS),
"Only user flags should be mutable");
mFlags &= ~aFlags;
}
const gfxSkipChars& GetSkipChars() const { return mSkipChars; }
PRUint32 GetAppUnitsPerDevUnit() const { return mAppUnitsPerDevUnit; }
gfxFontGroup *GetFontGroup() const { return mFontGroup; }
@ -796,6 +810,11 @@ public:
{ return (mFlags & gfxTextRunFactory::TEXT_IS_8BIT) ? mText.mSingle : nsnull; }
const PRUnichar *GetTextUnicode() const
{ return (mFlags & gfxTextRunFactory::TEXT_IS_8BIT) ? nsnull : mText.mDouble; }
const void *GetTextAt(PRUint32 aIndex) {
return (mFlags & gfxTextRunFactory::TEXT_IS_8BIT)
? NS_STATIC_CAST(const void *, mText.mSingle + aIndex)
: NS_STATIC_CAST(const void *, mText.mDouble + aIndex);
}
const PRUnichar GetChar(PRUint32 i) const
{ return (mFlags & gfxTextRunFactory::TEXT_IS_8BIT) ? mText.mSingle[i] : mText.mDouble[i]; }
PRUint32 GetHashCode() const { return mHashCode; }
@ -1020,6 +1039,7 @@ public:
void SetDetailedGlyphs(PRUint32 aCharIndex, const DetailedGlyph *aGlyphs,
PRUint32 aNumGlyphs);
void SetMissingGlyph(PRUint32 aCharIndex, PRUnichar aChar);
void SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext, PRUint32 aCharIndex);
// API for access to the raw glyph data, needed by gfxFont::Draw
// and gfxFont::GetBoundingBox
@ -1034,6 +1054,16 @@ public:
*aNumGlyphRuns = mGlyphRuns.Length();
return mGlyphRuns.Elements();
}
// Returns the index of the GlyphRun containing the given offset.
// Returns mGlyphRuns.Length() when aOffset is mCharacterCount.
PRUint32 FindFirstGlyphRunContaining(PRUint32 aOffset);
// Copy glyph data for a range of characters from aSource to this
// textrun. If aStealData is true then we actually steal the glyph data,
// setting the data in aSource to "missing". aDest should be in the last
// glyphrun.
virtual void CopyGlyphDataFrom(gfxTextRun *aSource, PRUint32 aStart,
PRUint32 aLength, PRUint32 aDest,
PRBool aStealData);
nsExpirationState *GetExpirationState() { return &mExpirationState; }
@ -1042,9 +1072,6 @@ private:
// Allocate aCount DetailedGlyphs for the given index
DetailedGlyph *AllocateDetailedGlyphs(PRUint32 aCharIndex, PRUint32 aCount);
// Returns the index of the GlyphRun containing the given offset.
// Returns mGlyphRuns.Length() when aOffset is mCharacterCount.
PRUint32 FindFirstGlyphRunContaining(PRUint32 aOffset);
// Computes the x-advance for a given cluster starting at aClusterOffset. Does
// not include any spacing. Result is in appunits.
PRInt32 ComputeClusterAdvance(PRUint32 aClusterOffset);
@ -1140,7 +1167,8 @@ public:
virtual gfxFontGroup *Copy(const gfxFontStyle *aStyle) = 0;
/**
* Tabs, CRs and LFs should be zero-width and invisible.
* Tabs, CRs and LFs should be zero-width and invisible. They should
* break up shaping.
*/
static PRBool IsInvisibleChar(PRUnichar ch) {
return ch == '\t' || ch == '\r' || ch == '\n';

View File

@ -0,0 +1,145 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Foundation code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Vladimir Vukicevic <vladimir@pobox.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef GFX_TEXT_RUN_WORD_CACHE_H
#define GFX_TEXT_RUN_WORD_CACHE_H
#include "gfxFont.h"
/**
* Cache individual "words" (strings delimited by white-space or white-space-like
* characters that don't involve kerning or ligatures) in textruns.
*/
class THEBES_API gfxTextRunWordCache {
public:
gfxTextRunWordCache() {
mCache.Init(100);
}
~gfxTextRunWordCache() {
NS_ASSERTION(mCache.Count() == 0, "Textrun cache not empty!");
}
/**
* Create a textrun using cached words.
* @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, PRBool *aIsInCache);
/**
* 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.
*/
gfxTextRun *MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
const gfxFontGroup::Parameters *aParams,
PRUint32 aFlags, PRBool *aIsInCache);
/**
* 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 THEBES_API CacheHashKey {
void *mFontOrGroup;
const void *mString;
PRUint32 mLength;
PRUint32 mAppUnitsPerDevUnit;
PRUint32 mStringHash;
PRPackedBool mIsDoubleByteText;
};
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,
const nsTArray<DeferredWord>& aDeferredWords,
PRBool aSuccessful);
void RemoveWord(gfxTextRun *aTextRun, PRUint32 aStart,
PRUint32 aEnd, PRUint32 aHash);
nsTHashtable<CacheHashEntry> mCache;
};
#endif /* GFX_TEXT_RUN_WORD_CACHE_H */

View File

@ -35,6 +35,7 @@ CPPSRCS = \
gfxRect.cpp \
gfxSkipChars.cpp \
gfxTextRunCache.cpp \
gfxTextRunWordCache.cpp \
$(NULL)
ifdef MOZ_TREE_CAIRO

View File

@ -531,24 +531,13 @@ gfxFontGroup::MakeSpaceTextRun(const Parameters *aParams, PRUint32 aFlags)
aFlags |= TEXT_IS_8BIT | TEXT_IS_ASCII | TEXT_IS_PERSISTENT;
static const PRUint8 space = ' ';
gfxFont *font = GetFontAt(0);
PRUint32 spaceGlyph = font->GetSpaceGlyph();
float spaceWidth = font->GetMetrics().spaceWidth;
PRUint32 spaceWidthAppUnits = NS_lroundf(spaceWidth*aParams->mAppUnitsPerDevUnit);
if (!spaceGlyph ||
!gfxTextRun::CompressedGlyph::IsSimpleGlyphID(spaceGlyph) ||
!gfxTextRun::CompressedGlyph::IsSimpleAdvance(spaceWidthAppUnits))
return MakeTextRun(&space, 1, aParams, aFlags);
nsAutoPtr<gfxTextRun> textRun;
textRun = new gfxTextRun(aParams, &space, 1, this, aFlags);
if (!textRun)
if (!textRun || !textRun->GetCharacterGlyphs())
return nsnull;
if (NS_FAILED(textRun->AddGlyphRun(font, 0)))
return nsnull;
gfxTextRun::CompressedGlyph g;
g.SetSimpleGlyph(spaceWidthAppUnits, spaceGlyph);
textRun->SetCharacterGlyph(0, g);
gfxFont *font = GetFontAt(0);
textRun->SetSpaceGlyph(font, aParams->mContext, 0);
return textRun.forget();
}
@ -691,35 +680,7 @@ gfxTextRun::Clone(const gfxTextRunFactory::Parameters *aParams, const void *aTex
if (!textRun || !textRun->mCharacterGlyphs)
return nsnull;
PRUint32 i;
for (i = 0; i < mGlyphRuns.Length(); ++i) {
if (NS_FAILED(textRun->AddGlyphRun(mGlyphRuns[i].mFont,
mGlyphRuns[i].mCharacterOffset)))
return nsnull;
}
for (i = 0; i < aLength; ++i) {
CompressedGlyph g = mCharacterGlyphs[i];
g.SetCanBreakBefore(PR_FALSE);
textRun->mCharacterGlyphs[i] = g;
}
if (mDetailedGlyphs) {
for (i = 0; i < aLength; ++i) {
DetailedGlyph *details = mDetailedGlyphs[i];
if (details) {
PRUint32 glyphCount = 1;
while (!details[glyphCount - 1].mIsLastGlyph) {
++glyphCount;
}
DetailedGlyph *dest = textRun->AllocateDetailedGlyphs(i, glyphCount);
if (!dest)
return nsnull;
memcpy(dest, details, sizeof(DetailedGlyph)*glyphCount);
}
}
}
textRun->CopyGlyphDataFrom(this, 0, mCharacterCount, 0, PR_FALSE);
return textRun.forget();
}
@ -1490,8 +1451,24 @@ gfxTextRun::FindFirstGlyphRunContaining(PRUint32 aOffset)
nsresult
gfxTextRun::AddGlyphRun(gfxFont *aFont, PRUint32 aUTF16Offset)
{
NS_ASSERTION(mGlyphRuns.Length() > 0 || aUTF16Offset == 0,
PRUint32 numGlyphRuns = mGlyphRuns.Length();
if (numGlyphRuns > 0) {
GlyphRun *lastGlyphRun = &mGlyphRuns[numGlyphRuns - 1];
NS_ASSERTION(lastGlyphRun->mCharacterOffset <= aUTF16Offset,
"Glyph runs out of order");
if (lastGlyphRun->mFont == aFont)
return NS_OK;
if (lastGlyphRun->mCharacterOffset == aUTF16Offset) {
lastGlyphRun->mFont = aFont;
return NS_OK;
}
}
NS_ASSERTION(numGlyphRuns > 0 || aUTF16Offset == 0,
"First run doesn't cover the first character?");
GlyphRun *glyphRun = mGlyphRuns.AppendElement();
if (!glyphRun)
return NS_ERROR_OUT_OF_MEMORY;
@ -1584,3 +1561,122 @@ gfxTextRun::RecordSurrogates(const PRUnichar *aString)
}
}
}
static PRUint32
CountDetailedGlyphs(gfxTextRun::DetailedGlyph *aGlyphs)
{
PRUint32 i = 0;
while (!aGlyphs[i].mIsLastGlyph) {
++i;
}
return i + 1;
}
static void
ClearCharacters(gfxTextRun::CompressedGlyph *aGlyphs, PRUint32 aLength)
{
gfxTextRun::CompressedGlyph g;
g.SetMissing();
PRUint32 i;
for (i = 0; i < aLength; ++i) {
aGlyphs[i] = g;
}
}
void
gfxTextRun::CopyGlyphDataFrom(gfxTextRun *aSource, PRUint32 aStart,
PRUint32 aLength, PRUint32 aDest,
PRBool aStealData)
{
NS_ASSERTION(aStart + aLength <= aSource->GetLength(),
"Source substring out of range");
NS_ASSERTION(aDest + aLength <= GetLength(),
"Destination substring out of range");
PRUint32 i;
for (i = 0; i < aLength; ++i) {
CompressedGlyph g = aSource->mCharacterGlyphs[i + aStart];
g.SetCanBreakBefore(PR_FALSE);
mCharacterGlyphs[i + aDest] = g;
if (aStealData) {
aSource->mCharacterGlyphs[i + aStart].SetMissing();
}
}
if (aSource->mDetailedGlyphs) {
for (i = 0; i < aLength; ++i) {
DetailedGlyph *details = aSource->mDetailedGlyphs[i + aStart];
if (details) {
if (aStealData) {
if (!mDetailedGlyphs) {
mDetailedGlyphs = new nsAutoArrayPtr<DetailedGlyph>[mCharacterCount];
if (!mDetailedGlyphs) {
ClearCharacters(&mCharacterGlyphs[aDest], aLength);
return;
}
}
mDetailedGlyphs[i + aDest] = details;
aSource->mDetailedGlyphs[i + aStart].forget();
} else {
PRUint32 glyphCount = CountDetailedGlyphs(details);
DetailedGlyph *dest = AllocateDetailedGlyphs(i + aDest, glyphCount);
if (!dest) {
ClearCharacters(&mCharacterGlyphs[aDest], aLength);
return;
}
memcpy(dest, details, sizeof(DetailedGlyph)*glyphCount);
}
} else if (mDetailedGlyphs) {
mDetailedGlyphs[i + aDest] = nsnull;
}
}
} else if (mDetailedGlyphs) {
for (i = 0; i < aLength; ++i) {
mDetailedGlyphs[i + aDest] = nsnull;
}
}
GlyphRunIterator iter(aSource, aStart, aLength);
while (iter.NextRun()) {
gfxFont *font = iter.GetGlyphRun()->mFont;
PRUint32 start = iter.GetStringStart();
PRUint32 end = iter.GetStringEnd();
NS_ASSERTION(aSource->IsClusterStart(start),
"Started word in the middle of a cluster...");
NS_ASSERTION(end == aSource->GetLength() || aSource->IsClusterStart(end),
"Ended word in the middle of a cluster...");
nsresult rv = AddGlyphRun(font, start - aStart + aDest);
if (NS_FAILED(rv))
return;
}
}
void
gfxTextRun::SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext, PRUint32 aCharIndex)
{
PRUint32 spaceGlyph = aFont->GetSpaceGlyph();
float spaceWidth = aFont->GetMetrics().spaceWidth;
PRUint32 spaceWidthAppUnits = NS_lroundf(spaceWidth*mAppUnitsPerDevUnit);
if (!spaceGlyph ||
!CompressedGlyph::IsSimpleGlyphID(spaceGlyph) ||
!CompressedGlyph::IsSimpleAdvance(spaceWidthAppUnits)) {
gfxTextRunFactory::Parameters params = {
aContext, nsnull, nsnull, nsnull, 0, mAppUnitsPerDevUnit
};
static const PRUint8 space = ' ';
nsAutoPtr<gfxTextRun> textRun;
textRun = mFontGroup->MakeTextRun(&space, 1, &params,
gfxTextRunFactory::TEXT_IS_8BIT | gfxTextRunFactory::TEXT_IS_ASCII |
gfxTextRunFactory::TEXT_IS_PERSISTENT);
if (!textRun || !textRun->mCharacterGlyphs)
return;
CopyGlyphDataFrom(textRun, 0, 1, aCharIndex, PR_TRUE);
return;
}
AddGlyphRun(aFont, aCharIndex);
CompressedGlyph g;
g.SetSimpleGlyph(spaceWidthAppUnits, spaceGlyph);
SetCharacterGlyph(aCharIndex, g);
}

View File

@ -88,13 +88,10 @@ gfxSkipChars::BuildShortcuts()
void
gfxSkipCharsIterator::SetOffsets(PRUint32 aOffset, PRBool aInOriginalString)
{
NS_ASSERTION(aOffset <= mSkipChars->mCharCount,
"Invalid offset");
if (mSkipChars->mListLength == 0) {
// Special case: all chars kept, original and stripped strings are equal
if (aOffset < 0) {
aOffset = 0;
} else if (aOffset > mSkipChars->mCharCount) {
aOffset = mSkipChars->mCharCount;
}
mOriginalStringOffset = mSkippedStringOffset = aOffset;
return;
}

View File

@ -0,0 +1,469 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Foundation code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Vladimir Vukicevic <vladimir@pobox.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "gfxTextRunWordCache.h"
static inline PRUint32
HashMix(PRUint32 aHash, PRUnichar aCh)
{
return (aHash >> 28) ^ (aHash << 4) ^ aCh;
}
// If the substring of the textrun is rendered entirely in the first font
// of the textrun's fontgroup, then return that font. Otherwise return the
// fontgroup.
static void *GetWordFontOrGroup(gfxTextRun *aTextRun, PRUint32 aOffset,
PRUint32 aLength)
{
PRUint32 glyphRunCount;
const gfxTextRun::GlyphRun *glyphRuns = aTextRun->GetGlyphRuns(&glyphRunCount);
PRUint32 glyphRunIndex = aTextRun->FindFirstGlyphRunContaining(aOffset);
gfxFontGroup *fontGroup = aTextRun->GetFontGroup();
gfxFont *firstFont = fontGroup->GetFontAt(0);
if (glyphRuns[glyphRunIndex].mFont != firstFont)
return fontGroup;
PRUint32 glyphRunEnd = glyphRunIndex == glyphRunCount - 1
? aTextRun->GetLength() : glyphRuns[glyphRunIndex + 1].mCharacterOffset;
if (aOffset + aLength <= glyphRunEnd)
return firstFont;
return fontGroup;
}
#define UNICODE_NBSP 0x00A0
// XXX should we treat NBSP or SPACE combined with other characters as a word
// boundary? Currently this does.
static PRBool
IsBoundarySpace(PRUnichar aChar)
{
return aChar == ' ' || aChar == UNICODE_NBSP;
}
static PRBool
IsWordBoundary(PRUnichar aChar)
{
return IsBoundarySpace(aChar) || gfxFontGroup::IsInvisibleChar(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.
*
* @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)
{
if (aEnd <= aStart)
return PR_TRUE;
PRUint32 length = aEnd - aStart;
CacheHashKey key =
{ aFirstFont, aTextRun->GetTextAt(aStart),
length, aTextRun->GetAppUnitsPerDevUnit(), aHash,
(aTextRun->GetFlags() & gfxTextRunFactory::TEXT_IS_8BIT) == 0 };
CacheHashEntry *fontEntry = mCache.PutEntry(key);
if (!fontEntry)
return PR_FALSE;
CacheHashEntry *existingEntry = nsnull;
if (fontEntry->mTextRun) {
existingEntry = fontEntry;
} else {
key.mFontOrGroup = aTextRun->GetFontGroup();
CacheHashEntry *groupEntry = mCache.GetEntry(key);
if (groupEntry) {
existingEntry = groupEntry;
mCache.RawRemoveEntry(fontEntry);
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;
}
// 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 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
gfxTextRunWordCache::FinishTextRun(gfxTextRun *aTextRun, gfxTextRun *aNewRun,
const nsTArray<DeferredWord>& aDeferredWords,
PRBool aSuccessful)
{
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;
// 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.
if (!aSuccessful ||
GetWordFontOrGroup(aNewRun, word->mSourceOffset, word->mLength) != font) {
CacheHashKey key =
{ font, aTextRun->GetTextAt(word->mDestOffset),
word->mLength, aTextRun->GetAppUnitsPerDevUnit(), word->mHash,
(aTextRun->GetFlags() & gfxTextRunFactory::TEXT_IS_8BIT) == 0 };
NS_ASSERTION(mCache.GetEntry(key),
"This entry should have been added previously!");
mCache.RemoveEntry(key);
if (aSuccessful) {
key.mFontOrGroup = fontGroup;
CacheHashEntry *groupEntry = mCache.PutEntry(key);
if (groupEntry) {
groupEntry->mTextRun = aTextRun;
groupEntry->mWordOffset = word->mDestOffset;
groupEntry->mHashedByFont = PR_FALSE;
}
}
}
}
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.
aTextRun->CopyGlyphDataFrom(source,
word->mSourceOffset, word->mLength, word->mDestOffset,
source == aNewRun);
}
}
}
gfxTextRun *
gfxTextRunWordCache::MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
const gfxFontGroup::Parameters *aParams,
PRUint32 aFlags, PRBool *aIsInCache)
{
nsAutoPtr<gfxTextRun> textRun;
textRun = new gfxTextRun(aParams, aText, aLength, aFontGroup, aFlags);
if (!textRun || !textRun->GetCharacterGlyphs())
return nsnull;
gfxFont *font = aFontGroup->GetFontAt(0);
nsresult rv = textRun->AddGlyphRun(font, 0);
NS_ENSURE_SUCCESS(rv, nsnull);
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,
deferredWords.Length() == 0 ? nsnull : &deferredWords);
if (!hit) {
if (tempString.Length() > 0) {
tempString.AppendElement(' ');
}
PRUint32 offset = tempString.Length();
PRUint32 length = i - wordStart;
PRUnichar *chars = tempString.AppendElements(length);
if (!chars) {
FinishTextRun(textRun, nsnull, deferredWords, PR_FALSE);
return nsnull;
}
memcpy(chars, aText + wordStart, length*sizeof(PRUnichar));
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!
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.
*aIsInCache = PR_FALSE;
return textRun.forget();
}
*aIsInCache = PR_TRUE;
// 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(),
&params, aFlags | gfxTextRunFactory::TEXT_IS_PERSISTENT);
FinishTextRun(textRun, newRun, deferredWords, newRun != nsnull);
return textRun.forget();
}
gfxTextRun *
gfxTextRunWordCache::MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
gfxFontGroup *aFontGroup,
const gfxFontGroup::Parameters *aParams,
PRUint32 aFlags, PRBool *aIsInCache)
{
aFlags |= gfxTextRunFactory::TEXT_IS_8BIT;
nsAutoPtr<gfxTextRun> textRun;
textRun = new gfxTextRun(aParams, aText, aLength, aFontGroup, aFlags);
if (!textRun || !textRun->GetCharacterGlyphs())
return nsnull;
gfxFont *font = aFontGroup->GetFontAt(0);
nsresult rv = textRun->AddGlyphRun(font, 0);
NS_ENSURE_SUCCESS(rv, nsnull);
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,
deferredWords.Length() == 0 ? nsnull : &deferredWords);
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, 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!
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.
*aIsInCache = PR_FALSE;
return textRun.forget();
}
*aIsInCache = PR_TRUE;
// 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(),
&params, aFlags | gfxTextRunFactory::TEXT_IS_PERSISTENT);
FinishTextRun(textRun, newRun, deferredWords, newRun != nsnull);
return textRun.forget();
}
void
gfxTextRunWordCache::RemoveWord(gfxTextRun *aTextRun, PRUint32 aStart,
PRUint32 aEnd, PRUint32 aHash)
{
if (aEnd <= aStart)
return;
PRUint32 length = aEnd - aStart;
CacheHashKey key =
{ GetWordFontOrGroup(aTextRun, aStart, length), aTextRun->GetTextAt(aStart),
length, aTextRun->GetAppUnitsPerDevUnit(), aHash,
(aTextRun->GetFlags() & gfxTextRunFactory::TEXT_IS_8BIT) == 0 };
CacheHashEntry *entry = mCache.GetEntry(key);
if (entry && entry->mTextRun == aTextRun) {
// XXX would like to use RawRemoveEntry here plus some extra method
// that conditionally shrinks the hashtable
mCache.RemoveEntry(key);
}
}
// Remove a textrun from the cache by looking up each word and removing it
void
gfxTextRunWordCache::RemoveTextRun(gfxTextRun *aTextRun)
{
PRUint32 i;
PRUint32 wordStart = 0;
PRUint32 hash = 0;
for (i = 0; i < aTextRun->GetLength(); ++i) {
PRUnichar ch = aTextRun->GetChar(i);
if (IsWordBoundary(ch)) {
RemoveWord(aTextRun, wordStart, i, hash);
hash = 0;
wordStart = i + 1;
} else {
hash = HashMix(hash, ch);
}
}
RemoveWord(aTextRun, wordStart, i, hash);
}
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;
}
static PRBool
IsWordEnd(gfxTextRun *aTextRun, PRUint32 aOffset)
{
PRUint32 runLength = aTextRun->GetLength();
if (aOffset == runLength)
return PR_TRUE;
if (aOffset > runLength)
return PR_FALSE;
return IsWordBoundary(aTextRun->GetChar(aOffset));
}
static void *
GetFontOrGroup(gfxFontGroup *aFontGroup, PRBool aUseFont)
{
return aUseFont
? NS_STATIC_CAST(void *, aFontGroup->GetFontAt(0))
: NS_STATIC_CAST(void *, aFontGroup);
}
PRBool
gfxTextRunWordCache::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const
{
if (!mTextRun)
return PR_FALSE;
PRUint32 length = aKey->mLength;
gfxFontGroup *fontGroup = mTextRun->GetFontGroup();
if (!IsWordEnd(mTextRun, mWordOffset + length) ||
GetFontOrGroup(fontGroup, mHashedByFont) != aKey->mFontOrGroup ||
aKey->mAppUnitsPerDevUnit != mTextRun->GetAppUnitsPerDevUnit())
return PR_FALSE;
if (mTextRun->GetFlags() & gfxFontGroup::TEXT_IS_8BIT) {
const PRUint8 *text = mTextRun->GetText8Bit() + mWordOffset;
if (!aKey->mIsDoubleByteText)
return memcmp(text, aKey->mString, length) == 0;
return CompareDifferentWidthStrings(text,
NS_STATIC_CAST(const PRUnichar *, aKey->mString), length);
} else {
const PRUnichar *text = mTextRun->GetTextUnicode() + mWordOffset;
if (aKey->mIsDoubleByteText)
return memcmp(text, aKey->mString, length*sizeof(PRUnichar)) == 0;
return CompareDifferentWidthStrings(NS_STATIC_CAST(const PRUint8 *, aKey->mString),
text, length);
}
}
PLDHashNumber
gfxTextRunWordCache::CacheHashEntry::HashKey(const KeyTypePointer aKey)
{
return aKey->mStringHash + (long)aKey->mFontOrGroup + aKey->mAppUnitsPerDevUnit +
aKey->mIsDoubleByteText;
}