/* -*- 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) 2005-2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Stuart Parmenter * Masayuki Nakano * John Daggett * Jonathan Kew * * 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 ***** */ #ifdef MOZ_LOGGING #define FORCE_PR_LOG /* Allow logging in the release build */ #endif #include "prlog.h" #include "nsServiceManagerUtils.h" #include "nsReadableUtils.h" #include "nsExpirationTracker.h" #include "nsILanguageAtomService.h" #include "gfxFont.h" #include "gfxPlatform.h" #include "gfxAtoms.h" #include "prtypes.h" #include "gfxTypes.h" #include "gfxContext.h" #include "gfxFontMissingGlyphs.h" #include "gfxUserFontSet.h" #include "gfxPlatformFontList.h" #include "gfxScriptItemizer.h" #include "gfxUnicodeProperties.h" #include "nsMathUtils.h" #include "nsBidiUtils.h" #include "nsUnicodeRange.h" #include "nsCompressedCharMap.h" #include "nsStyleConsts.h" #include "mozilla/Preferences.h" #include "cairo.h" #include "gfxFontTest.h" #include "harfbuzz/hb-blob.h" #include "nsCRT.h" using namespace mozilla; gfxFontCache *gfxFontCache::gGlobalCache = nsnull; #ifdef DEBUG_roc #define DEBUG_TEXT_RUN_STORAGE_METRICS #endif #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS static PRUint32 gTextRunStorageHighWaterMark = 0; static PRUint32 gTextRunStorage = 0; static PRUint32 gFontCount = 0; static PRUint32 gGlyphExtentsCount = 0; static PRUint32 gGlyphExtentsWidthsTotalSize = 0; static PRUint32 gGlyphExtentsSetupEagerSimple = 0; static PRUint32 gGlyphExtentsSetupEagerTight = 0; static PRUint32 gGlyphExtentsSetupLazyTight = 0; static PRUint32 gGlyphExtentsSetupFallBackToTight = 0; #endif gfxFontEntry::~gfxFontEntry() { delete mUserFontData; } PRBool gfxFontEntry::IsSymbolFont() { return mSymbolFont; } PRBool gfxFontEntry::TestCharacterMap(PRUint32 aCh) { if (!mCmapInitialized) { ReadCMAP(); } return mCharacterMap.test(aCh); } nsresult gfxFontEntry::InitializeUVSMap() { // mUVSOffset will not be initialized // until cmap is initialized. if (!mCmapInitialized) { ReadCMAP(); } if (!mUVSOffset) { return NS_ERROR_FAILURE; } if (!mUVSData) { const PRUint32 kCmapTag = TRUETYPE_TAG('c','m','a','p'); AutoFallibleTArray buffer; if (GetFontTable(kCmapTag, buffer) != NS_OK) { mUVSOffset = 0; // don't bother to read the table again return NS_ERROR_FAILURE; } PRUint8* uvsData; nsresult rv = gfxFontUtils::ReadCMAPTableFormat14( buffer.Elements() + mUVSOffset, buffer.Length() - mUVSOffset, uvsData); if (NS_FAILED(rv)) { mUVSOffset = 0; // don't bother to read the table again return rv; } mUVSData = uvsData; } return NS_OK; } PRUint16 gfxFontEntry::GetUVSGlyph(PRUint32 aCh, PRUint32 aVS) { InitializeUVSMap(); if (mUVSData) { return gfxFontUtils::MapUVSToGlyphFormat14(mUVSData, aCh, aVS); } return 0; } nsresult gfxFontEntry::ReadCMAP() { mCmapInitialized = PR_TRUE; return NS_OK; } nsString gfxFontEntry::FamilyName() const { NS_ASSERTION(mFamily, "orphaned font entry"); if (mFamily) { return mFamily->Name(); } else { return nsString(); } } nsString gfxFontEntry::RealFaceName() { FallibleTArray nameTable; nsresult rv = GetFontTable(TRUETYPE_TAG('n','a','m','e'), nameTable); if (NS_SUCCEEDED(rv)) { nsAutoString name; rv = gfxFontUtils::GetFullNameFromTable(nameTable, name); if (NS_SUCCEEDED(rv)) { return name; } } return Name(); } already_AddRefed gfxFontEntry::FindOrMakeFont(const gfxFontStyle *aStyle, PRBool aNeedsBold) { // the font entry name is the psname, not the family name nsRefPtr font = gfxFontCache::GetCache()->Lookup(this, aStyle); if (!font) { gfxFont *newFont = CreateFontInstance(aStyle, aNeedsBold); if (!newFont) return nsnull; if (!newFont->Valid()) { delete newFont; return nsnull; } font = newFont; gfxFontCache::GetCache()->AddNew(font); } gfxFont *f = nsnull; font.swap(f); return f; } /** * FontTableBlobData * * See FontTableHashEntry for the general strategy. */ class gfxFontEntry::FontTableBlobData { public: // Adopts the content of aBuffer. // Pass a non-null aHashEntry only if it should be cleared if/when this // FontTableBlobData is deleted. FontTableBlobData(FallibleTArray& aBuffer, FontTableHashEntry *aHashEntry) : mHashEntry(aHashEntry), mHashtable() { MOZ_COUNT_CTOR(FontTableBlobData); mTableData.SwapElements(aBuffer); } ~FontTableBlobData() { MOZ_COUNT_DTOR(FontTableBlobData); if (mHashEntry) { if (mHashtable) { mHashtable->RemoveEntry(mHashEntry->GetKey()); } else { mHashEntry->Clear(); } } } // Useful for creating blobs const char *GetTable() const { return reinterpret_cast(mTableData.Elements()); } PRUint32 GetTableLength() const { return mTableData.Length(); } // Tell this FontTableBlobData to remove the HashEntry when this is // destroyed. void ManageHashEntry(nsTHashtable *aHashtable) { mHashtable = aHashtable; } // Disconnect from the HashEntry (because the blob has already been // removed from the hashtable). void ForgetHashEntry() { mHashEntry = nsnull; } private: // The font table data block, owned (via adoption) FallibleTArray mTableData; // The blob destroy function needs to know the hashtable entry, FontTableHashEntry *mHashEntry; // and the owning hashtable, so that it can remove the entry. nsTHashtable *mHashtable; // not implemented FontTableBlobData(const FontTableBlobData&); }; void gfxFontEntry::FontTableHashEntry::SaveTable(FallibleTArray& aTable) { Clear(); // adopts elements of aTable FontTableBlobData *data = new FontTableBlobData(aTable, nsnull); mBlob = hb_blob_create(data->GetTable(), data->GetTableLength(), HB_MEMORY_MODE_READONLY, DeleteFontTableBlobData, data); } hb_blob_t * gfxFontEntry::FontTableHashEntry:: ShareTableAndGetBlob(FallibleTArray& aTable, nsTHashtable *aHashtable) { Clear(); // adopts elements of aTable mSharedBlobData = new FontTableBlobData(aTable, this); mBlob = hb_blob_create(mSharedBlobData->GetTable(), mSharedBlobData->GetTableLength(), HB_MEMORY_MODE_READONLY, DeleteFontTableBlobData, mSharedBlobData); if (!mSharedBlobData) { // The FontTableBlobData was destroyed during hb_blob_create(). // The (empty) blob is still be held in the hashtable with a strong // reference. return hb_blob_reference(mBlob); } // Tell the FontTableBlobData to remove this hash entry when destroyed. // The hashtable does not keep a strong reference. mSharedBlobData->ManageHashEntry(aHashtable); return mBlob; } void gfxFontEntry::FontTableHashEntry::Clear() { // If the FontTableBlobData is managing the hash entry, then the blob is // not owned by this HashEntry; otherwise there is strong reference to the // blob that must be removed. if (mSharedBlobData) { mSharedBlobData->ForgetHashEntry(); mSharedBlobData = nsnull; } else if (mBlob) { hb_blob_destroy(mBlob); } mBlob = nsnull; } // a hb_destroy_func for hb_blob_create /* static */ void gfxFontEntry::FontTableHashEntry::DeleteFontTableBlobData(void *aBlobData) { delete static_cast(aBlobData); } hb_blob_t * gfxFontEntry::FontTableHashEntry::GetBlob() const { return hb_blob_reference(mBlob); } PRBool gfxFontEntry::GetExistingFontTable(PRUint32 aTag, hb_blob_t **aBlob) { if (!mFontTableCache.IsInitialized()) { // we do this here rather than on fontEntry construction // because not all shapers will access the table cache at all mFontTableCache.Init(10); } FontTableHashEntry *entry = mFontTableCache.GetEntry(aTag); if (!entry) { return PR_FALSE; } *aBlob = entry->GetBlob(); return PR_TRUE; } hb_blob_t * gfxFontEntry::ShareFontTableAndGetBlob(PRUint32 aTag, FallibleTArray* aBuffer) { if (NS_UNLIKELY(!mFontTableCache.IsInitialized())) { // we do this here rather than on fontEntry construction // because not all shapers will access the table cache at all mFontTableCache.Init(10); } FontTableHashEntry *entry = mFontTableCache.PutEntry(aTag); if (NS_UNLIKELY(!entry)) { // OOM return nsnull; } if (!aBuffer) { // ensure the entry is null entry->Clear(); return nsnull; } return entry->ShareTableAndGetBlob(*aBuffer, &mFontTableCache); } ////////////////////////////////////////////////////////////////////////////// // // class gfxFontFamily // ////////////////////////////////////////////////////////////////////////////// // we consider faces with mStandardFace == PR_TRUE to be "greater than" those with PR_FALSE, // because during style matching, later entries will replace earlier ones class FontEntryStandardFaceComparator { public: PRBool Equals(const nsRefPtr& a, const nsRefPtr& b) const { return a->mStandardFace == b->mStandardFace; } PRBool LessThan(const nsRefPtr& a, const nsRefPtr& b) const { return (a->mStandardFace == PR_FALSE && b->mStandardFace == PR_TRUE); } }; void gfxFontFamily::SortAvailableFonts() { mAvailableFonts.Sort(FontEntryStandardFaceComparator()); } PRBool gfxFontFamily::HasOtherFamilyNames() { // need to read in other family names to determine this if (!mOtherFamilyNamesInitialized) { ReadOtherFamilyNames(gfxPlatformFontList::PlatformFontList()); // sets mHasOtherFamilyNames } return mHasOtherFamilyNames; } gfxFontEntry* gfxFontFamily::FindFontForStyle(const gfxFontStyle& aFontStyle, PRBool& aNeedsSyntheticBold) { if (!mHasStyles) FindStyleVariations(); // collect faces for the family, if not already done NS_ASSERTION(mAvailableFonts.Length() > 0, "font family with no faces!"); aNeedsSyntheticBold = PR_FALSE; PRInt8 baseWeight = aFontStyle.ComputeWeight(); PRBool wantBold = baseWeight >= 6; // If the family has only one face, we simply return it; no further checking needed if (mAvailableFonts.Length() == 1) { gfxFontEntry *fe = mAvailableFonts[0]; aNeedsSyntheticBold = wantBold && !fe->IsBold(); return fe; } PRBool wantItalic = (aFontStyle.style & (FONT_STYLE_ITALIC | FONT_STYLE_OBLIQUE)) != 0; // Most families are "simple", having just Regular/Bold/Italic/BoldItalic, // or some subset of these. In this case, we have exactly 4 entries in mAvailableFonts, // stored in the above order; note that some of the entries may be NULL. // We can then pick the required entry based on whether the request is for // bold or non-bold, italic or non-italic, without running the more complex // matching algorithm used for larger families with many weights and/or widths. if (mIsSimpleFamily) { // Family has no more than the "standard" 4 faces, at fixed indexes; // calculate which one we want. // Note that we cannot simply return it as not all 4 faces are necessarily present. PRUint8 faceIndex = (wantItalic ? kItalicMask : 0) | (wantBold ? kBoldMask : 0); // if the desired style is available, return it directly gfxFontEntry *fe = mAvailableFonts[faceIndex]; if (fe) { // no need to set aNeedsSyntheticBold here as we matched the boldness request return fe; } // order to check fallback faces in a simple family, depending on requested style static const PRUint8 simpleFallbacks[4][3] = { { kBoldFaceIndex, kItalicFaceIndex, kBoldItalicFaceIndex }, // fallbacks for Regular { kRegularFaceIndex, kBoldItalicFaceIndex, kItalicFaceIndex },// Bold { kBoldItalicFaceIndex, kRegularFaceIndex, kBoldFaceIndex }, // Italic { kItalicFaceIndex, kBoldFaceIndex, kRegularFaceIndex } // BoldItalic }; const PRUint8 *order = simpleFallbacks[faceIndex]; for (PRUint8 trial = 0; trial < 3; ++trial) { // check remaining faces in order of preference to find the first that actually exists fe = mAvailableFonts[order[trial]]; if (fe) { aNeedsSyntheticBold = wantBold && !fe->IsBold(); return fe; } } // this can't happen unless we have totally broken the font-list manager! NS_NOTREACHED("no face found in simple font family!"); return nsnull; } // This is a large/rich font family, so we do full style- and weight-matching: // first collect a list of weights that are the best match for the requested // font-stretch and font-style, then pick the best weight match among those // available. gfxFontEntry *weightList[10] = { 0 }; PRBool foundWeights = FindWeightsForStyle(weightList, wantItalic, aFontStyle.stretch); if (!foundWeights) { return nsnull; } // First find a match for the best weight PRInt8 matchBaseWeight = 0; PRInt8 i = baseWeight; // Need to special case when normal face doesn't exist but medium does. // In that case, use medium otherwise weights < 400 if (baseWeight == 4 && !weightList[4]) { i = 5; // medium } // Loop through weights, since one exists loop will terminate PRInt8 direction = (baseWeight > 5) ? 1 : -1; for (; ; i += direction) { if (weightList[i]) { matchBaseWeight = i; break; } // If we've reached one side without finding a font, // start over and go the other direction until we find a match if (i == 1 || i == 9) { i = baseWeight; direction = -direction; } } NS_ASSERTION(matchBaseWeight != 0, "weight mapping should always find at least one font in a family"); gfxFontEntry *matchFE = weightList[matchBaseWeight]; NS_ASSERTION(matchFE, "weight mapping should always find at least one font in a family"); if (!matchFE->IsBold() && baseWeight >= 6) { aNeedsSyntheticBold = PR_TRUE; } return matchFE; } void gfxFontFamily::CheckForSimpleFamily() { if (mAvailableFonts.Length() > 4 || mAvailableFonts.Length() == 0) { return; // can't be "simple" if there are >4 faces; // if none then the family is unusable anyway } PRInt16 firstStretch = mAvailableFonts[0]->Stretch(); gfxFontEntry *faces[4] = { 0 }; for (PRUint8 i = 0; i < mAvailableFonts.Length(); ++i) { gfxFontEntry *fe = mAvailableFonts[i]; if (fe->Stretch() != firstStretch) { return; // font-stretch doesn't match, don't treat as simple family } PRUint8 faceIndex = (fe->IsItalic() ? kItalicMask : 0) | (fe->Weight() >= 600 ? kBoldMask : 0); if (faces[faceIndex]) { return; // two faces resolve to the same slot; family isn't "simple" } faces[faceIndex] = fe; } // we have successfully slotted the available faces into the standard // 4-face framework mAvailableFonts.SetLength(4); for (PRUint8 i = 0; i < 4; ++i) { if (mAvailableFonts[i].get() != faces[i]) { mAvailableFonts[i].swap(faces[i]); } } mIsSimpleFamily = PR_TRUE; } static inline PRUint32 StyleDistance(gfxFontEntry *aFontEntry, PRBool anItalic, PRInt16 aStretch) { // Compute a measure of the "distance" between the requested style // and the given fontEntry, // considering italicness and font-stretch but not weight. // TODO (refine CSS spec...): discuss priority of italic vs stretch; // whether penalty for stretch mismatch should depend on actual difference in values; // whether a sign mismatch in stretch should increase the effective distance return (aFontEntry->IsItalic() != anItalic ? 1 : 0) + (aFontEntry->mStretch != aStretch ? 10 : 0); } PRBool gfxFontFamily::FindWeightsForStyle(gfxFontEntry* aFontsForWeights[], PRBool anItalic, PRInt16 aStretch) { PRUint32 foundWeights = 0; PRUint32 bestMatchDistance = 0xffffffff; for (PRUint32 i = 0; i < mAvailableFonts.Length(); i++) { // this is not called for "simple" families, and therefore it does not // need to check the mAvailableFonts entries for NULL gfxFontEntry *fe = mAvailableFonts[i]; PRUint32 distance = StyleDistance(fe, anItalic, aStretch); if (distance <= bestMatchDistance) { PRInt8 wt = fe->mWeight / 100; NS_ASSERTION(wt >= 1 && wt < 10, "invalid weight in fontEntry"); if (!aFontsForWeights[wt]) { // record this as a possible candidate for weight matching aFontsForWeights[wt] = fe; ++foundWeights; } else { PRUint32 prevDistance = StyleDistance(aFontsForWeights[wt], anItalic, aStretch); if (prevDistance >= distance) { // replacing a weight we already found, so don't increment foundWeights aFontsForWeights[wt] = fe; } } bestMatchDistance = distance; } } NS_ASSERTION(foundWeights > 0, "Font family containing no faces?"); if (foundWeights == 1) { // no need to cull entries if we only found one weight return PR_TRUE; } // we might have recorded some faces that were a partial style match, but later found // others that were closer; in this case, we need to cull the poorer matches from the // weight list we'll return for (PRUint32 i = 0; i < 10; ++i) { if (aFontsForWeights[i] && StyleDistance(aFontsForWeights[i], anItalic, aStretch) > bestMatchDistance) { aFontsForWeights[i] = 0; } } return (foundWeights > 0); } void gfxFontFamily::LocalizedName(nsAString& aLocalizedName) { // just return the primary name; subclasses should override aLocalizedName = mName; } void gfxFontFamily::FindFontForChar(FontSearch *aMatchData) { if (!mHasStyles) FindStyleVariations(); // xxx - optimization point - keep a bit vector with the union of supported unicode ranges // by all fonts for this family and bail immediately if the character is not in any of // this family's cmaps // iterate over fonts PRUint32 numFonts = mAvailableFonts.Length(); for (PRUint32 i = 0; i < numFonts; i++) { gfxFontEntry *fe = mAvailableFonts[i]; // skip certain fonts during system fallback if (!fe || fe->SkipDuringSystemFallback()) continue; PRInt32 rank = 0; if (fe->TestCharacterMap(aMatchData->mCh)) { rank += 20; aMatchData->mCount++; #ifdef PR_LOGGING PRLogModuleInfo *log = gfxPlatform::GetLog(eGfxLog_textrun); if (NS_UNLIKELY(log)) { PRUint32 charRange = gfxFontUtils::CharRangeBit(aMatchData->mCh); PRUint32 unicodeRange = FindCharUnicodeRange(aMatchData->mCh); PRUint32 hbscript = gfxUnicodeProperties::GetScriptCode(aMatchData->mCh); PR_LOG(log, PR_LOG_DEBUG,\ ("(textrun-systemfallback-fonts) char: u+%6.6x " "char-range: %d unicode-range: %d script: %d match: [%s]\n", aMatchData->mCh, charRange, unicodeRange, hbscript, NS_ConvertUTF16toUTF8(fe->Name()).get())); } #endif } // if we didn't match any characters don't bother wasting more time with this face. if (rank == 0) continue; // omitting from original windows code -- family name, lang group, pitch // not available in current FontEntry implementation if (aMatchData->mFontToMatch) { const gfxFontStyle *style = aMatchData->mFontToMatch->GetStyle(); // italics PRBool wantItalic = ((style->style & (FONT_STYLE_ITALIC | FONT_STYLE_OBLIQUE)) != 0); if (fe->IsItalic() == wantItalic) { rank += 5; } // weight PRInt32 targetWeight = style->ComputeWeight() * 100; PRInt32 entryWeight = fe->Weight(); if (entryWeight == targetWeight) { rank += 5; } else { PRUint32 diffWeight = abs(entryWeight - targetWeight); if (diffWeight <= 100) // favor faces close in weight rank += 2; } } else { // if no font to match, prefer non-bold, non-italic fonts if (!fe->IsItalic()) { rank += 3; } if (!fe->IsBold()) { rank += 2; } } // xxx - add whether AAT font with morphing info for specific lang groups if (rank > aMatchData->mMatchRank || (rank == aMatchData->mMatchRank && Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0)) { aMatchData->mBestMatch = fe; aMatchData->mMatchRank = rank; } } } // returns true if other names were found, false otherwise PRBool gfxFontFamily::ReadOtherFamilyNamesForFace(gfxPlatformFontList *aPlatformFontList, FallibleTArray& aNameTable, PRBool useFullName) { const PRUint8 *nameData = aNameTable.Elements(); PRUint32 dataLength = aNameTable.Length(); const gfxFontUtils::NameHeader *nameHeader = reinterpret_cast(nameData); PRUint32 nameCount = nameHeader->count; if (nameCount * sizeof(gfxFontUtils::NameRecord) > dataLength) { NS_WARNING("invalid font (name records)"); return PR_FALSE; } const gfxFontUtils::NameRecord *nameRecord = reinterpret_cast(nameData + sizeof(gfxFontUtils::NameHeader)); PRUint32 stringsBase = PRUint32(nameHeader->stringOffset); PRBool foundNames = PR_FALSE; for (PRUint32 i = 0; i < nameCount; i++, nameRecord++) { PRUint32 nameLen = nameRecord->length; PRUint32 nameOff = nameRecord->offset; // offset from base of string storage if (stringsBase + nameOff + nameLen > dataLength) { NS_WARNING("invalid font (name table strings)"); return PR_FALSE; } PRUint16 nameID = nameRecord->nameID; if ((useFullName && nameID == gfxFontUtils::NAME_ID_FULL) || (!useFullName && (nameID == gfxFontUtils::NAME_ID_FAMILY || nameID == gfxFontUtils::NAME_ID_PREFERRED_FAMILY))) { nsAutoString otherFamilyName; PRBool ok = gfxFontUtils::DecodeFontName(nameData + stringsBase + nameOff, nameLen, PRUint32(nameRecord->platformID), PRUint32(nameRecord->encodingID), PRUint32(nameRecord->languageID), otherFamilyName); // add if not same as canonical family name if (ok && otherFamilyName != mName) { aPlatformFontList->AddOtherFamilyName(this, otherFamilyName); foundNames = PR_TRUE; } } } return foundNames; } void gfxFontFamily::ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList) { if (mOtherFamilyNamesInitialized) return; mOtherFamilyNamesInitialized = PR_TRUE; FindStyleVariations(); // read in other family names for the first face in the list PRUint32 i, numFonts = mAvailableFonts.Length(); const PRUint32 kNAME = TRUETYPE_TAG('n','a','m','e'); AutoFallibleTArray buffer; for (i = 0; i < numFonts; ++i) { gfxFontEntry *fe = mAvailableFonts[i]; if (!fe) continue; if (fe->GetFontTable(kNAME, buffer) != NS_OK) continue; mHasOtherFamilyNames = ReadOtherFamilyNamesForFace(aPlatformFontList, buffer); break; } // read in other names for the first face in the list with the assumption // that if extra names don't exist in that face then they don't exist in // other faces for the same font if (!mHasOtherFamilyNames) return; // read in names for all faces, needed to catch cases where fonts have // family names for individual weights (e.g. Hiragino Kaku Gothic Pro W6) for ( ; i < numFonts; i++) { gfxFontEntry *fe = mAvailableFonts[i]; if (!fe) continue; if (fe->GetFontTable(kNAME, buffer) != NS_OK) continue; ReadOtherFamilyNamesForFace(aPlatformFontList, buffer); } } void gfxFontFamily::ReadFaceNames(gfxPlatformFontList *aPlatformFontList, PRBool aNeedFullnamePostscriptNames) { // if all needed names have already been read, skip if (mOtherFamilyNamesInitialized && (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) return; FindStyleVariations(); PRUint32 i, numFonts = mAvailableFonts.Length(); const PRUint32 kNAME = TRUETYPE_TAG('n','a','m','e'); AutoFallibleTArray buffer; nsAutoString fullname, psname; PRBool firstTime = PR_TRUE, readAllFaces = PR_FALSE; for (i = 0; i < numFonts; ++i) { gfxFontEntry *fe = mAvailableFonts[i]; if (!fe) continue; if (fe->GetFontTable(kNAME, buffer) != NS_OK) continue; if (aNeedFullnamePostscriptNames) { if (gfxFontUtils::ReadCanonicalName( buffer, gfxFontUtils::NAME_ID_FULL, fullname) == NS_OK) { aPlatformFontList->AddFullname(fe, fullname); } if (gfxFontUtils::ReadCanonicalName( buffer, gfxFontUtils::NAME_ID_POSTSCRIPT, psname) == NS_OK) { aPlatformFontList->AddPostscriptName(fe, psname); } } if (!mOtherFamilyNamesInitialized && (firstTime || readAllFaces)) { PRBool foundOtherName = ReadOtherFamilyNamesForFace(aPlatformFontList, buffer); // if the first face has a different name, scan all faces, otherwise // assume the family doesn't have other names if (firstTime && foundOtherName) { mHasOtherFamilyNames = PR_TRUE; readAllFaces = PR_TRUE; } firstTime = PR_FALSE; } // if not reading in any more names, skip other faces if (!readAllFaces && !aNeedFullnamePostscriptNames) break; } mFaceNamesInitialized = PR_TRUE; mOtherFamilyNamesInitialized = PR_TRUE; } gfxFontEntry* gfxFontFamily::FindFont(const nsAString& aPostscriptName) { // find the font using a simple linear search PRUint32 numFonts = mAvailableFonts.Length(); for (PRUint32 i = 0; i < numFonts; i++) { gfxFontEntry *fe = mAvailableFonts[i].get(); if (fe && fe->Name() == aPostscriptName) return fe; } return nsnull; } nsresult gfxFontCache::Init() { NS_ASSERTION(!gGlobalCache, "Where did this come from?"); gGlobalCache = new gfxFontCache(); return gGlobalCache ? NS_OK : NS_ERROR_OUT_OF_MEMORY; } void gfxFontCache::Shutdown() { delete gGlobalCache; gGlobalCache = nsnull; #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS printf("Textrun storage high water mark=%d\n", gTextRunStorageHighWaterMark); printf("Total number of fonts=%d\n", gFontCount); printf("Total glyph extents allocated=%d (size %d)\n", gGlyphExtentsCount, int(gGlyphExtentsCount*sizeof(gfxGlyphExtents))); printf("Total glyph extents width-storage size allocated=%d\n", gGlyphExtentsWidthsTotalSize); printf("Number of simple glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerSimple); printf("Number of tight glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerTight); printf("Number of tight glyph extents lazily requested=%d\n", gGlyphExtentsSetupLazyTight); printf("Number of simple glyph extent setups that fell back to tight=%d\n", gGlyphExtentsSetupFallBackToTight); #endif } PRBool gfxFontCache::HashEntry::KeyEquals(const KeyTypePointer aKey) const { return aKey->mFontEntry == mFont->GetFontEntry() && aKey->mStyle->Equals(*mFont->GetStyle()); } already_AddRefed gfxFontCache::Lookup(const gfxFontEntry *aFontEntry, const gfxFontStyle *aStyle) { Key key(aFontEntry, aStyle); HashEntry *entry = mFonts.GetEntry(key); if (!entry) return nsnull; gfxFont *font = entry->mFont; NS_ADDREF(font); return font; } void gfxFontCache::AddNew(gfxFont *aFont) { Key key(aFont->GetFontEntry(), aFont->GetStyle()); HashEntry *entry = mFonts.PutEntry(key); if (!entry) return; gfxFont *oldFont = entry->mFont; entry->mFont = aFont; // If someone's asked us to replace an existing font entry, then that's a // bit weird, but let it happen, and expire the old font if it's not used. if (oldFont && oldFont->GetExpirationState()->IsTracked()) { // if oldFont == aFont, recount should be > 0, // so we shouldn't be here. NS_ASSERTION(aFont != oldFont, "new font is tracked for expiry!"); NotifyExpired(oldFont); } } void gfxFontCache::NotifyReleased(gfxFont *aFont) { nsresult rv = AddObject(aFont); if (NS_FAILED(rv)) { // We couldn't track it for some reason. Kill it now. DestroyFont(aFont); } // Note that we might have fonts that aren't in the hashtable, perhaps because // of OOM adding to the hashtable or because someone did an AddNew where // we already had a font. These fonts are added to the expiration tracker // anyway, even though Lookup can't resurrect them. Eventually they will // expire and be deleted. } void gfxFontCache::NotifyExpired(gfxFont *aFont) { RemoveObject(aFont); DestroyFont(aFont); } void gfxFontCache::DestroyFont(gfxFont *aFont) { Key key(aFont->GetFontEntry(), aFont->GetStyle()); HashEntry *entry = mFonts.GetEntry(key); if (entry && entry->mFont == aFont) mFonts.RemoveEntry(key); NS_ASSERTION(aFont->GetRefCount() == 0, "Destroying with non-zero ref count!"); delete aFont; } void gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther, PRBool aOtherIsOnLeft) { mAscent = PR_MAX(mAscent, aOther.mAscent); mDescent = PR_MAX(mDescent, aOther.mDescent); if (aOtherIsOnLeft) { mBoundingBox = (mBoundingBox + gfxPoint(aOther.mAdvanceWidth, 0)).Union(aOther.mBoundingBox); } else { mBoundingBox = mBoundingBox.Union(aOther.mBoundingBox + gfxPoint(mAdvanceWidth, 0)); } mAdvanceWidth += aOther.mAdvanceWidth; } gfxFont::gfxFont(gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle, AntialiasOption anAAOption) : mFontEntry(aFontEntry), mIsValid(PR_TRUE), mStyle(*aFontStyle), mAdjustedSize(0.0), mFUnitsConvFactor(0.0f), mSyntheticBoldOffset(0), mAntialiasOption(anAAOption), mPlatformShaper(nsnull), mHarfBuzzShaper(nsnull) { #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS ++gFontCount; #endif } gfxFont::~gfxFont() { PRUint32 i; // We destroy the contents of mGlyphExtentsArray explicitly instead of // using nsAutoPtr because VC++ can't deal with nsTArrays of nsAutoPtrs // of classes that lack a proper copy constructor for (i = 0; i < mGlyphExtentsArray.Length(); ++i) { delete mGlyphExtentsArray[i]; } } hb_blob_t * gfxFont::GetFontTable(PRUint32 aTag) { hb_blob_t *blob; if (mFontEntry->GetExistingFontTable(aTag, &blob)) return blob; FallibleTArray buffer; PRBool haveTable = NS_SUCCEEDED(mFontEntry->GetFontTable(aTag, buffer)); return mFontEntry->ShareFontTableAndGetBlob(aTag, haveTable ? &buffer : nsnull); } /** * A helper function in case we need to do any rounding or other * processing here. */ #define ToDeviceUnits(aAppUnits, aDevUnitsPerAppUnit) \ (double(aAppUnits)*double(aDevUnitsPerAppUnit)) struct GlyphBuffer { #define GLYPH_BUFFER_SIZE (2048/sizeof(cairo_glyph_t)) cairo_glyph_t mGlyphBuffer[GLYPH_BUFFER_SIZE]; unsigned int mNumGlyphs; GlyphBuffer() : mNumGlyphs(0) { } cairo_glyph_t *AppendGlyph() { return &mGlyphBuffer[mNumGlyphs++]; } void Flush(cairo_t *aCR, PRBool aDrawToPath, PRBool aReverse, PRBool aFinish = PR_FALSE) { // Ensure there's enough room for at least two glyphs in the // buffer (because we may allocate two glyphs between flushes) if (!aFinish && mNumGlyphs + 2 <= GLYPH_BUFFER_SIZE) return; if (aReverse) { for (PRUint32 i = 0; i < mNumGlyphs/2; ++i) { cairo_glyph_t tmp = mGlyphBuffer[i]; mGlyphBuffer[i] = mGlyphBuffer[mNumGlyphs - 1 - i]; mGlyphBuffer[mNumGlyphs - 1 - i] = tmp; } } if (aDrawToPath) cairo_glyph_path(aCR, mGlyphBuffer, mNumGlyphs); else cairo_show_glyphs(aCR, mGlyphBuffer, mNumGlyphs); mNumGlyphs = 0; } #undef GLYPH_BUFFER_SIZE }; void gfxFont::Draw(gfxTextRun *aTextRun, PRUint32 aStart, PRUint32 aEnd, gfxContext *aContext, PRBool aDrawToPath, gfxPoint *aPt, Spacing *aSpacing) { if (aStart >= aEnd) return; const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs(); const PRUint32 appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit(); const double devUnitsPerAppUnit = 1.0/double(appUnitsPerDevUnit); PRBool isRTL = aTextRun->IsRightToLeft(); double direction = aTextRun->GetDirection(); // double-strike in direction of run double synBoldDevUnitOffsetAppUnits = direction * (double) mSyntheticBoldOffset * appUnitsPerDevUnit; PRUint32 i; // Current position in appunits double x = aPt->x; double y = aPt->y; PRBool success = SetupCairoFont(aContext); if (NS_UNLIKELY(!success)) return; GlyphBuffer glyphs; cairo_glyph_t *glyph; cairo_t *cr = aContext->GetCairo(); if (aSpacing) { x += direction*aSpacing[0].mBefore; } for (i = aStart; i < aEnd; ++i) { const gfxTextRun::CompressedGlyph *glyphData = &charGlyphs[i]; if (glyphData->IsSimpleGlyph()) { glyph = glyphs.AppendGlyph(); glyph->index = glyphData->GetSimpleGlyph(); double advance = glyphData->GetSimpleAdvance(); // Perhaps we should put a scale in the cairo context instead of // doing this scaling here... // Multiplying by the reciprocal may introduce tiny error here, // but we assume cairo is going to round coordinates at some stage // and this is faster double glyphX; if (isRTL) { x -= advance; glyphX = x; } else { glyphX = x; x += advance; } glyph->x = ToDeviceUnits(glyphX, devUnitsPerAppUnit); glyph->y = ToDeviceUnits(y, devUnitsPerAppUnit); // synthetic bolding by drawing with a one-pixel offset if (mSyntheticBoldOffset) { cairo_glyph_t *doubleglyph; doubleglyph = glyphs.AppendGlyph(); doubleglyph->index = glyph->index; doubleglyph->x = ToDeviceUnits(glyphX + synBoldDevUnitOffsetAppUnits, devUnitsPerAppUnit); doubleglyph->y = glyph->y; } glyphs.Flush(cr, aDrawToPath, isRTL); } else { PRUint32 glyphCount = glyphData->GetGlyphCount(); if (glyphCount > 0) { const gfxTextRun::DetailedGlyph *details = aTextRun->GetDetailedGlyphs(i); NS_ASSERTION(details, "detailedGlyph should not be missing!"); for (PRUint32 j = 0; j < glyphCount; ++j, ++details) { double advance = details->mAdvance; if (glyphData->IsMissing()) { // default ignorable characters will have zero advance width. // we don't have to draw the hexbox for them if (!aDrawToPath && advance > 0) { double glyphX = x; if (isRTL) { glyphX -= advance; } gfxPoint pt(ToDeviceUnits(glyphX, devUnitsPerAppUnit), ToDeviceUnits(y, devUnitsPerAppUnit)); gfxFloat advanceDevUnits = ToDeviceUnits(advance, devUnitsPerAppUnit); gfxFloat height = GetMetrics().maxAscent; gfxRect glyphRect(pt.x, pt.y - height, advanceDevUnits, height); gfxFontMissingGlyphs::DrawMissingGlyph(aContext, glyphRect, details->mGlyphID); } } else { glyph = glyphs.AppendGlyph(); glyph->index = details->mGlyphID; double glyphX = x + details->mXOffset; if (isRTL) { glyphX -= advance; } glyph->x = ToDeviceUnits(glyphX, devUnitsPerAppUnit); glyph->y = ToDeviceUnits(y + details->mYOffset, devUnitsPerAppUnit); // synthetic bolding by drawing with a one-pixel offset if (mSyntheticBoldOffset) { cairo_glyph_t *doubleglyph; doubleglyph = glyphs.AppendGlyph(); doubleglyph->index = glyph->index; doubleglyph->x = ToDeviceUnits(glyphX + synBoldDevUnitOffsetAppUnits, devUnitsPerAppUnit); doubleglyph->y = glyph->y; } glyphs.Flush(cr, aDrawToPath, isRTL); } x += direction*advance; } } } if (aSpacing) { double space = aSpacing[i - aStart].mAfter; if (i + 1 < aEnd) { space += aSpacing[i + 1 - aStart].mBefore; } x += direction*space; } } if (gfxFontTestStore::CurrentStore()) { /* This assumes that the tests won't have anything that results * in more than GLYPH_BUFFER_SIZE glyphs. Do this before we * flush, since that'll blow away the num_glyphs. */ gfxFontTestStore::CurrentStore()->AddItem(GetName(), glyphs.mGlyphBuffer, glyphs.mNumGlyphs); } // draw any remaining glyphs glyphs.Flush(cr, aDrawToPath, isRTL, PR_TRUE); *aPt = gfxPoint(x, y); } static void UnionRange(gfxFloat aX, gfxFloat* aDestMin, gfxFloat* aDestMax) { *aDestMin = PR_MIN(*aDestMin, aX); *aDestMax = PR_MAX(*aDestMax, aX); } // We get precise glyph extents if the textrun creator requested them, or // if the font is a user font --- in which case the author may be relying // on overflowing glyphs. static PRBool NeedsGlyphExtents(gfxFont *aFont, gfxTextRun *aTextRun) { return (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX) || aFont->GetFontEntry()->IsUserFont(); } static PRBool NeedsGlyphExtents(gfxTextRun *aTextRun) { if (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX) return PR_TRUE; PRUint32 numRuns; const gfxTextRun::GlyphRun *glyphRuns = aTextRun->GetGlyphRuns(&numRuns); for (PRUint32 i = 0; i < numRuns; ++i) { if (glyphRuns[i].mFont->GetFontEntry()->IsUserFont()) return PR_TRUE; } return PR_FALSE; } gfxFont::RunMetrics gfxFont::Measure(gfxTextRun *aTextRun, PRUint32 aStart, PRUint32 aEnd, BoundingBoxType aBoundingBoxType, gfxContext *aRefContext, Spacing *aSpacing) { // If aBoundingBoxType is TIGHT_HINTED_OUTLINE_EXTENTS // and the underlying cairo font may be antialiased, // we need to create a copy in order to avoid getting cached extents. // This is only used by MathML layout at present. if (aBoundingBoxType == TIGHT_HINTED_OUTLINE_EXTENTS && mAntialiasOption != kAntialiasNone) { if (!mNonAAFont) { mNonAAFont = CopyWithAntialiasOption(kAntialiasNone); } // if font subclass doesn't implement CopyWithAntialiasOption(), // it will return null and we'll proceed to use the existing font if (mNonAAFont) { return mNonAAFont->Measure(aTextRun, aStart, aEnd, TIGHT_HINTED_OUTLINE_EXTENTS, aRefContext, aSpacing); } } const PRUint32 appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit(); // Current position in appunits const gfxFont::Metrics& fontMetrics = GetMetrics(); RunMetrics metrics; metrics.mAscent = fontMetrics.maxAscent*appUnitsPerDevUnit; metrics.mDescent = fontMetrics.maxDescent*appUnitsPerDevUnit; if (aStart == aEnd) { // exit now before we look at aSpacing[0], which is undefined metrics.mBoundingBox = gfxRect(0, -metrics.mAscent, 0, metrics.mAscent + metrics.mDescent); return metrics; } gfxFloat advanceMin = 0, advanceMax = 0; const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs(); PRBool isRTL = aTextRun->IsRightToLeft(); double direction = aTextRun->GetDirection(); PRBool needsGlyphExtents = NeedsGlyphExtents(this, aTextRun); gfxGlyphExtents *extents = (aBoundingBoxType == LOOSE_INK_EXTENTS && !needsGlyphExtents && !aTextRun->HasDetailedGlyphs()) ? nsnull : GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit()); double x = 0; if (aSpacing) { x += direction*aSpacing[0].mBefore; } PRUint32 i; for (i = aStart; i < aEnd; ++i) { const gfxTextRun::CompressedGlyph *glyphData = &charGlyphs[i]; if (glyphData->IsSimpleGlyph()) { double advance = glyphData->GetSimpleAdvance(); // Only get the real glyph horizontal extent if we were asked // for the tight bounding box or we're in quality mode if ((aBoundingBoxType != LOOSE_INK_EXTENTS || needsGlyphExtents) && extents) { PRUint32 glyphIndex = glyphData->GetSimpleGlyph(); PRUint16 extentsWidth = extents->GetContainedGlyphWidthAppUnits(glyphIndex); if (extentsWidth != gfxGlyphExtents::INVALID_WIDTH && aBoundingBoxType == LOOSE_INK_EXTENTS) { UnionRange(x, &advanceMin, &advanceMax); UnionRange(x + direction*extentsWidth, &advanceMin, &advanceMax); } else { gfxRect glyphRect; if (!extents->GetTightGlyphExtentsAppUnits(this, aRefContext, glyphIndex, &glyphRect)) { glyphRect = gfxRect(0, metrics.mBoundingBox.Y(), advance, metrics.mBoundingBox.Height()); } if (isRTL) { glyphRect -= gfxPoint(advance, 0); } glyphRect += gfxPoint(x, 0); metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect); } } x += direction*advance; } else { PRUint32 glyphCount = glyphData->GetGlyphCount(); if (glyphCount > 0) { const gfxTextRun::DetailedGlyph *details = aTextRun->GetDetailedGlyphs(i); NS_ASSERTION(details != nsnull, "detaiedGlyph record should not be missing!"); PRUint32 j; for (j = 0; j < glyphCount; ++j, ++details) { PRUint32 glyphIndex = details->mGlyphID; gfxPoint glyphPt(x + details->mXOffset, details->mYOffset); double advance = details->mAdvance; gfxRect glyphRect; if (glyphData->IsMissing() || !extents || !extents->GetTightGlyphExtentsAppUnits(this, aRefContext, glyphIndex, &glyphRect)) { // We might have failed to get glyph extents due to // OOM or something glyphRect = gfxRect(0, -metrics.mAscent, advance, metrics.mAscent + metrics.mDescent); } if (isRTL) { glyphRect -= gfxPoint(advance, 0); } glyphRect += gfxPoint(x, 0); metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect); x += direction*advance; } } } // Every other glyph type is ignored if (aSpacing) { double space = aSpacing[i - aStart].mAfter; if (i + 1 < aEnd) { space += aSpacing[i + 1 - aStart].mBefore; } x += direction*space; } } if (aBoundingBoxType == LOOSE_INK_EXTENTS) { UnionRange(x, &advanceMin, &advanceMax); gfxRect fontBox(advanceMin, -metrics.mAscent, advanceMax - advanceMin, metrics.mAscent + metrics.mDescent); metrics.mBoundingBox = metrics.mBoundingBox.Union(fontBox); } if (isRTL) { metrics.mBoundingBox -= gfxPoint(x, 0); } metrics.mAdvanceWidth = x*direction; return metrics; } #define MAX_SHAPING_LENGTH 32760 // slightly less than 32K, trying to avoid // over-stressing platform shapers #define BACKTRACK_LIMIT 1024 // If we can't find a space or a cluster start // within 1K chars, just chop arbitrarily. // Limiting backtrack here avoids pathological // behavior on long runs with no whitespace. PRBool gfxFont::SplitAndInitTextRun(gfxContext *aContext, gfxTextRun *aTextRun, const PRUnichar *aString, PRUint32 aRunStart, PRUint32 aRunLength, PRInt32 aRunScript) { PRBool ok; #ifdef PR_LOGGING PRLogModuleInfo *log = (mStyle.systemFont ? gfxPlatform::GetLog(eGfxLog_textrunui) : gfxPlatform::GetLog(eGfxLog_textrun)); if (NS_UNLIKELY(log)) { nsCAutoString lang; mStyle.language->ToUTF8String(lang); PR_LOG(log, PR_LOG_DEBUG,\ ("(%s-fontmatching) font: [%s] lang: %s script: %d len: %d " "TEXTRUN [%s] ENDTEXTRUN\n", (mStyle.systemFont ? "textrunui" : "textrun"), NS_ConvertUTF16toUTF8(GetName()).get(), lang.get(), aRunScript, aRunLength, NS_ConvertUTF16toUTF8(aString + aRunStart, aRunLength).get())); } #endif do { // Because various shaping backends struggle with very long runs, // we look for appropriate break locations (preferring whitespace), // and shape sub-runs of no more than 32K characters at a time. // See bug 606714 (CoreText), and similar Uniscribe issues. // This loop always executes at least once, and "processes" up to // MAX_RUN_LENGTH_FOR_SHAPING characters, updating aRunStart and // aRunLength accordingly. It terminates when the entire run has // been processed, or when shaping fails. PRUint32 thisRunLength; ok = PR_FALSE; if (aRunLength <= MAX_SHAPING_LENGTH) { thisRunLength = aRunLength; } else { // We're splitting this font run because it's very long PRUint32 offset = aRunStart + MAX_SHAPING_LENGTH; PRUint32 clusterStart = 0; while (offset > aRunStart + MAX_SHAPING_LENGTH - BACKTRACK_LIMIT) { if (aTextRun->IsClusterStart(offset)) { if (!clusterStart) { clusterStart = offset; } if (aString[offset] == ' ' || aString[offset - 1] == ' ') { break; } } --offset; } if (offset > MAX_SHAPING_LENGTH - BACKTRACK_LIMIT) { // we found a space, so break the run there thisRunLength = offset - aRunStart; } else if (clusterStart != 0) { // didn't find a space, but we found a cluster start thisRunLength = clusterStart - aRunStart; } else { // otherwise we'll simply break at MAX_SHAPING_LENGTH chars, // which may interfere with shaping behavior (but in practice // only pathological cases will lack ANY whitespace or cluster // boundaries, so we don't really care; it won't affect any // "real" text) thisRunLength = MAX_SHAPING_LENGTH; } } ok = InitTextRun(aContext, aTextRun, aString, aRunStart, thisRunLength, aRunScript); aRunStart += thisRunLength; aRunLength -= thisRunLength; } while (ok && aRunLength > 0); NS_WARN_IF_FALSE(ok, "shaper failed, expect scrambled or missing text"); return ok; } PRBool gfxFont::InitTextRun(gfxContext *aContext, gfxTextRun *aTextRun, const PRUnichar *aString, PRUint32 aRunStart, PRUint32 aRunLength, PRInt32 aRunScript, PRBool aPreferPlatformShaping) { PRBool ok = PR_FALSE; if (mHarfBuzzShaper && !aPreferPlatformShaping) { if (gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aRunScript)) { ok = mHarfBuzzShaper->InitTextRun(aContext, aTextRun, aString, aRunStart, aRunLength, aRunScript); } } if (!ok) { if (!mPlatformShaper) { CreatePlatformShaper(); NS_ASSERTION(mPlatformShaper, "no platform shaper available!"); } if (mPlatformShaper) { ok = mPlatformShaper->InitTextRun(aContext, aTextRun, aString, aRunStart, aRunLength, aRunScript); } } return ok; } gfxGlyphExtents * gfxFont::GetOrCreateGlyphExtents(PRUint32 aAppUnitsPerDevUnit) { PRUint32 i; for (i = 0; i < mGlyphExtentsArray.Length(); ++i) { if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit) return mGlyphExtentsArray[i]; } gfxGlyphExtents *glyphExtents = new gfxGlyphExtents(aAppUnitsPerDevUnit); if (glyphExtents) { mGlyphExtentsArray.AppendElement(glyphExtents); // Initialize the extents of a space glyph, assuming that spaces don't // render anything! glyphExtents->SetContainedGlyphWidthAppUnits(GetSpaceGlyph(), 0); } return glyphExtents; } void gfxFont::SetupGlyphExtents(gfxContext *aContext, PRUint32 aGlyphID, PRBool aNeedTight, gfxGlyphExtents *aExtents) { gfxMatrix matrix = aContext->CurrentMatrix(); aContext->IdentityMatrix(); cairo_glyph_t glyph; glyph.index = aGlyphID; glyph.x = 0; glyph.y = 0; cairo_text_extents_t extents; cairo_glyph_extents(aContext->GetCairo(), &glyph, 1, &extents); aContext->SetMatrix(matrix); const Metrics& fontMetrics = GetMetrics(); PRUint32 appUnitsPerDevUnit = aExtents->GetAppUnitsPerDevUnit(); if (!aNeedTight && extents.x_bearing >= 0 && extents.y_bearing >= -fontMetrics.maxAscent && extents.height + extents.y_bearing <= fontMetrics.maxDescent) { PRUint32 appUnitsWidth = PRUint32(NS_ceil((extents.x_bearing + extents.width)*appUnitsPerDevUnit)); if (appUnitsWidth < gfxGlyphExtents::INVALID_WIDTH) { aExtents->SetContainedGlyphWidthAppUnits(aGlyphID, PRUint16(appUnitsWidth)); return; } } #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS if (!aNeedTight) { ++gGlyphExtentsSetupFallBackToTight; } #endif double d2a = appUnitsPerDevUnit; gfxRect bounds(extents.x_bearing*d2a, extents.y_bearing*d2a, extents.width*d2a, extents.height*d2a); aExtents->SetTightGlyphExtents(aGlyphID, bounds); } // Try to initialize font metrics by reading sfnt tables directly; // set mIsValid=TRUE and return TRUE on success. // Return FALSE if the gfxFontEntry subclass does not // implement GetFontTable(), or for non-sfnt fonts where tables are // not available. PRBool gfxFont::InitMetricsFromSfntTables(Metrics& aMetrics) { mIsValid = PR_FALSE; // font is NOT valid in case of early return const PRUint32 kHeadTableTag = TRUETYPE_TAG('h','e','a','d'); const PRUint32 kHheaTableTag = TRUETYPE_TAG('h','h','e','a'); const PRUint32 kPostTableTag = TRUETYPE_TAG('p','o','s','t'); const PRUint32 kOS_2TableTag = TRUETYPE_TAG('O','S','/','2'); if (mFUnitsConvFactor == 0.0) { // If the conversion factor from FUnits is not yet set, // 'head' table is required; otherwise we cannot read any metrics // because we don't know unitsPerEm AutoFallibleTArray headData; if (NS_FAILED(mFontEntry->GetFontTable(kHeadTableTag, headData)) || headData.Length() < sizeof(HeadTable)) { return PR_FALSE; // no 'head' table -> not an sfnt } HeadTable *head = reinterpret_cast(headData.Elements()); PRUint32 unitsPerEm = head->unitsPerEm; if (!unitsPerEm) { return PR_TRUE; // is an sfnt, but not valid } mFUnitsConvFactor = mAdjustedSize / unitsPerEm; } // 'hhea' table is required to get vertical extents AutoFallibleTArray hheaData; if (NS_FAILED(mFontEntry->GetFontTable(kHheaTableTag, hheaData)) || hheaData.Length() < sizeof(HheaTable)) { return PR_FALSE; // no 'hhea' table -> not an sfnt } HheaTable *hhea = reinterpret_cast(hheaData.Elements()); #define SET_UNSIGNED(field,src) aMetrics.field = PRUint16(src) * mFUnitsConvFactor #define SET_SIGNED(field,src) aMetrics.field = PRInt16(src) * mFUnitsConvFactor SET_UNSIGNED(maxAdvance, hhea->advanceWidthMax); SET_SIGNED(maxAscent, hhea->ascender); SET_SIGNED(maxDescent, -PRInt16(hhea->descender)); SET_SIGNED(externalLeading, hhea->lineGap); // 'post' table is required for underline metrics AutoFallibleTArray postData; if (NS_FAILED(mFontEntry->GetFontTable(kPostTableTag, postData))) { return PR_TRUE; // no 'post' table -> sfnt is not valid } if (postData.Length() < offsetof(PostTable, underlineThickness) + sizeof(PRUint16)) { return PR_TRUE; // bad post table -> sfnt is not valid } PostTable *post = reinterpret_cast(postData.Elements()); SET_SIGNED(underlineOffset, post->underlinePosition); SET_UNSIGNED(underlineSize, post->underlineThickness); // 'OS/2' table is optional, if not found we'll estimate xHeight // and aveCharWidth by measuring glyphs AutoFallibleTArray os2data; if (NS_SUCCEEDED(mFontEntry->GetFontTable(kOS_2TableTag, os2data))) { OS2Table *os2 = reinterpret_cast(os2data.Elements()); if (os2data.Length() >= offsetof(OS2Table, sxHeight) + sizeof(PRInt16) && PRUint16(os2->version) >= 2) { // version 2 and later includes the x-height field SET_SIGNED(xHeight, os2->sxHeight); // PR_ABS because of negative xHeight seen in Kokonor (Tibetan) font aMetrics.xHeight = PR_ABS(aMetrics.xHeight); } // this should always be present if (os2data.Length() >= offsetof(OS2Table, yStrikeoutPosition) + sizeof(PRInt16)) { SET_SIGNED(aveCharWidth, os2->xAvgCharWidth); SET_SIGNED(subscriptOffset, os2->ySubscriptYOffset); SET_SIGNED(superscriptOffset, os2->ySuperscriptYOffset); SET_SIGNED(strikeoutSize, os2->yStrikeoutSize); SET_SIGNED(strikeoutOffset, os2->yStrikeoutPosition); } } mIsValid = PR_TRUE; return PR_TRUE; } static double RoundToNearestMultiple(double aValue, double aFraction) { return floor(aValue/aFraction + 0.5) * aFraction; } void gfxFont::CalculateDerivedMetrics(Metrics& aMetrics) { aMetrics.maxAscent = NS_ceil(RoundToNearestMultiple(aMetrics.maxAscent, 1/1024.0)); aMetrics.maxDescent = NS_ceil(RoundToNearestMultiple(aMetrics.maxDescent, 1/1024.0)); if (aMetrics.xHeight <= 0) { // only happens if we couldn't find either font metrics // or a char to measure; // pick an arbitrary value that's better than zero aMetrics.xHeight = aMetrics.maxAscent * DEFAULT_XHEIGHT_FACTOR; } aMetrics.maxHeight = aMetrics.maxAscent + aMetrics.maxDescent; if (aMetrics.maxHeight - aMetrics.emHeight > 0.0) { aMetrics.internalLeading = aMetrics.maxHeight - aMetrics.emHeight; } else { aMetrics.internalLeading = 0.0; } aMetrics.emAscent = aMetrics.maxAscent * aMetrics.emHeight / aMetrics.maxHeight; aMetrics.emDescent = aMetrics.emHeight - aMetrics.emAscent; if (GetFontEntry()->IsFixedPitch()) { // Some Quartz fonts are fixed pitch, but there's some glyph with a bigger // advance than the average character width... this forces // those fonts to be recognized like fixed pitch fonts by layout. aMetrics.maxAdvance = aMetrics.aveCharWidth; } if (!aMetrics.subscriptOffset) { aMetrics.subscriptOffset = aMetrics.xHeight; } if (!aMetrics.superscriptOffset) { aMetrics.superscriptOffset = aMetrics.xHeight; } if (!aMetrics.strikeoutOffset) { aMetrics.strikeoutOffset = aMetrics.xHeight * 0.5; } if (!aMetrics.strikeoutSize) { aMetrics.strikeoutSize = aMetrics.underlineSize; } } void gfxFont::SanitizeMetrics(gfxFont::Metrics *aMetrics, PRBool aIsBadUnderlineFont) { // Even if this font size is zero, this font is created with non-zero size. // However, for layout and others, we should return the metrics of zero size font. if (mStyle.size == 0) { memset(aMetrics, 0, sizeof(gfxFont::Metrics)); return; } // MS (P)Gothic and MS (P)Mincho are not having suitable values in their super script offset. // If the values are not suitable, we should use x-height instead of them. // See https://bugzilla.mozilla.org/show_bug.cgi?id=353632 if (aMetrics->superscriptOffset <= 0 || aMetrics->superscriptOffset >= aMetrics->maxAscent) { aMetrics->superscriptOffset = aMetrics->xHeight; } // And also checking the case of sub script offset. The old gfx for win has checked this too. if (aMetrics->subscriptOffset <= 0 || aMetrics->subscriptOffset >= aMetrics->maxAscent) { aMetrics->subscriptOffset = aMetrics->xHeight; } aMetrics->underlineSize = PR_MAX(1.0, aMetrics->underlineSize); aMetrics->strikeoutSize = PR_MAX(1.0, aMetrics->strikeoutSize); aMetrics->underlineOffset = PR_MIN(aMetrics->underlineOffset, -1.0); if (aMetrics->maxAscent < 1.0) { // We cannot draw strikeout line and overline in the ascent... aMetrics->underlineSize = 0; aMetrics->underlineOffset = 0; aMetrics->strikeoutSize = 0; aMetrics->strikeoutOffset = 0; return; } /** * Some CJK fonts have bad underline offset. Therefore, if this is such font, * we need to lower the underline offset to bottom of *em* descent. * However, if this is system font, we should not do this for the rendering compatibility with * another application's UI on the platform. * XXX Should not use this hack if the font size is too small? * Such text cannot be read, this might be used for tight CSS rendering? (E.g., Acid2) */ if (!mStyle.systemFont && aIsBadUnderlineFont) { // First, we need 2 pixels between baseline and underline at least. Because many CJK characters // put their glyphs on the baseline, so, 1 pixel is too close for CJK characters. aMetrics->underlineOffset = PR_MIN(aMetrics->underlineOffset, -2.0); // Next, we put the underline to bottom of below of the descent space. if (aMetrics->internalLeading + aMetrics->externalLeading > aMetrics->underlineSize) { aMetrics->underlineOffset = PR_MIN(aMetrics->underlineOffset, -aMetrics->emDescent); } else { aMetrics->underlineOffset = PR_MIN(aMetrics->underlineOffset, aMetrics->underlineSize - aMetrics->emDescent); } } // If underline positioned is too far from the text, descent position is preferred so that underline // will stay within the boundary. else if (aMetrics->underlineSize - aMetrics->underlineOffset > aMetrics->maxDescent) { if (aMetrics->underlineSize > aMetrics->maxDescent) aMetrics->underlineSize = PR_MAX(aMetrics->maxDescent, 1.0); // The max underlineOffset is 1px (the min underlineSize is 1px, and min maxDescent is 0px.) aMetrics->underlineOffset = aMetrics->underlineSize - aMetrics->maxDescent; } // If strikeout line is overflowed from the ascent, the line should be resized and moved for // that being in the ascent space. // Note that the strikeoutOffset is *middle* of the strikeout line position. gfxFloat halfOfStrikeoutSize = NS_floor(aMetrics->strikeoutSize / 2.0 + 0.5); if (halfOfStrikeoutSize + aMetrics->strikeoutOffset > aMetrics->maxAscent) { if (aMetrics->strikeoutSize > aMetrics->maxAscent) { aMetrics->strikeoutSize = PR_MAX(aMetrics->maxAscent, 1.0); halfOfStrikeoutSize = NS_floor(aMetrics->strikeoutSize / 2.0 + 0.5); } gfxFloat ascent = NS_floor(aMetrics->maxAscent + 0.5); aMetrics->strikeoutOffset = PR_MAX(halfOfStrikeoutSize, ascent / 2.0); } // If overline is larger than the ascent, the line should be resized. if (aMetrics->underlineSize > aMetrics->maxAscent) { aMetrics->underlineSize = aMetrics->maxAscent; } } gfxFloat gfxFont::SynthesizeSpaceWidth(PRUint32 aCh) { // return an appropriate width for various Unicode space characters // that we "fake" if they're not actually present in the font; // returns negative value if the char is not a known space. switch (aCh) { case 0x2000: // en quad case 0x2002: return GetAdjustedSize() / 2; // en space case 0x2001: // em quad case 0x2003: return GetAdjustedSize(); // em space case 0x2004: return GetAdjustedSize() / 3; // three-per-em space case 0x2005: return GetAdjustedSize() / 4; // four-per-em space case 0x2006: return GetAdjustedSize() / 6; // six-per-em space case 0x2007: return GetMetrics().zeroOrAveCharWidth; // figure space case 0x2008: return GetMetrics().spaceWidth; // punctuation space case 0x2009: return GetAdjustedSize() / 5; // thin space case 0x200a: return GetAdjustedSize() / 10; // hair space case 0x202f: return GetAdjustedSize() / 5; // narrow no-break space default: return -1.0; } } gfxGlyphExtents::~gfxGlyphExtents() { #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS gGlyphExtentsWidthsTotalSize += mContainedGlyphWidths.ComputeSize(); gGlyphExtentsCount++; #endif MOZ_COUNT_DTOR(gfxGlyphExtents); } PRBool gfxGlyphExtents::GetTightGlyphExtentsAppUnits(gfxFont *aFont, gfxContext *aContext, PRUint32 aGlyphID, gfxRect *aExtents) { HashEntry *entry = mTightGlyphExtents.GetEntry(aGlyphID); if (!entry) { if (!aContext) { NS_WARNING("Could not get glyph extents (no aContext)"); return PR_FALSE; } aFont->SetupCairoFont(aContext); #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS ++gGlyphExtentsSetupLazyTight; #endif aFont->SetupGlyphExtents(aContext, aGlyphID, PR_TRUE, this); entry = mTightGlyphExtents.GetEntry(aGlyphID); if (!entry) { NS_WARNING("Could not get glyph extents"); return PR_FALSE; } } *aExtents = gfxRect(entry->x, entry->y, entry->width, entry->height); return PR_TRUE; } gfxGlyphExtents::GlyphWidths::~GlyphWidths() { PRUint32 i; for (i = 0; i < mBlocks.Length(); ++i) { PtrBits bits = mBlocks[i]; if (bits && !(bits & 0x1)) { delete[] reinterpret_cast(bits); } } } #ifdef DEBUG PRUint32 gfxGlyphExtents::GlyphWidths::ComputeSize() { PRUint32 i; PRUint32 size = mBlocks.Capacity()*sizeof(PtrBits); for (i = 0; i < mBlocks.Length(); ++i) { PtrBits bits = mBlocks[i]; if (bits && !(bits & 0x1)) { size += BLOCK_SIZE*sizeof(PRUint16); } } return size; } #endif void gfxGlyphExtents::GlyphWidths::Set(PRUint32 aGlyphID, PRUint16 aWidth) { PRUint32 block = aGlyphID >> BLOCK_SIZE_BITS; PRUint32 len = mBlocks.Length(); if (block >= len) { PtrBits *elems = mBlocks.AppendElements(block + 1 - len); if (!elems) return; memset(elems, 0, sizeof(PtrBits)*(block + 1 - len)); } PtrBits bits = mBlocks[block]; PRUint32 glyphOffset = aGlyphID & (BLOCK_SIZE - 1); if (!bits) { mBlocks[block] = MakeSingle(glyphOffset, aWidth); return; } PRUint16 *newBlock; if (bits & 0x1) { // Expand the block to a real block. We could avoid this by checking // glyphOffset == GetGlyphOffset(bits), but that never happens so don't bother newBlock = new PRUint16[BLOCK_SIZE]; if (!newBlock) return; PRUint32 i; for (i = 0; i < BLOCK_SIZE; ++i) { newBlock[i] = INVALID_WIDTH; } newBlock[GetGlyphOffset(bits)] = GetWidth(bits); mBlocks[block] = reinterpret_cast(newBlock); } else { newBlock = reinterpret_cast(bits); } newBlock[glyphOffset] = aWidth; } void gfxGlyphExtents::SetTightGlyphExtents(PRUint32 aGlyphID, const gfxRect& aExtentsAppUnits) { HashEntry *entry = mTightGlyphExtents.PutEntry(aGlyphID); if (!entry) return; entry->x = aExtentsAppUnits.X(); entry->y = aExtentsAppUnits.Y(); entry->width = aExtentsAppUnits.Width(); entry->height = aExtentsAppUnits.Height(); } gfxFontGroup::gfxFontGroup(const nsAString& aFamilies, const gfxFontStyle *aStyle, gfxUserFontSet *aUserFontSet) : mFamilies(aFamilies), mStyle(*aStyle), mUnderlineOffset(UNDERLINE_OFFSET_NOT_SET) { mUserFontSet = nsnull; SetUserFontSet(aUserFontSet); mSkipDrawing = PR_FALSE; mPageLang = gfxPlatform::GetFontPrefLangFor(mStyle.language); BuildFontList(); } void gfxFontGroup::BuildFontList() { // "#if" to be removed once all platforms are moved to gfxPlatformFontList interface // and subclasses of gfxFontGroup eliminated #if defined(XP_MACOSX) || defined(XP_WIN) || defined(ANDROID) ForEachFont(FindPlatformFont, this); if (mFonts.Length() == 0) { PRBool needsBold; gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList(); gfxFontEntry *defaultFont = pfl->GetDefaultFont(&mStyle, needsBold); NS_ASSERTION(defaultFont, "invalid default font returned by GetDefaultFont"); if (defaultFont) { nsRefPtr font = defaultFont->FindOrMakeFont(&mStyle, needsBold); if (font) { mFonts.AppendElement(font); } } if (mFonts.Length() == 0) { // Try for a "font of last resort...." // Because an empty font list would be Really Bad for later code // that assumes it will be able to get valid metrics for layout, // just look for the first usable font and put in the list. // (see bug 554544) nsAutoTArray,200> families; pfl->GetFontFamilyList(families); for (PRUint32 i = 0; i < families.Length(); ++i) { gfxFontEntry *fe = families[i]->FindFontForStyle(mStyle, needsBold); if (fe) { nsRefPtr font = fe->FindOrMakeFont(&mStyle, needsBold); if (font) { mFonts.AppendElement(font); break; } } } } if (mFonts.Length() == 0) { // an empty font list at this point is fatal; we're not going to // be able to do even the most basic layout operations char msg[256]; // CHECK buffer length if revising message below sprintf(msg, "unable to find a usable font (%.220s)", NS_ConvertUTF16toUTF8(mFamilies).get()); NS_RUNTIMEABORT(msg); } } if (!mStyle.systemFont) { for (PRUint32 i = 0; i < mFonts.Length(); ++i) { gfxFont* font = mFonts[i]; if (font->GetFontEntry()->mIsBadUnderlineFont) { gfxFloat first = mFonts[0]->GetMetrics().underlineOffset; gfxFloat bad = font->GetMetrics().underlineOffset; mUnderlineOffset = PR_MIN(first, bad); break; } } } #endif } PRBool gfxFontGroup::FindPlatformFont(const nsAString& aName, const nsACString& aGenericName, void *aClosure) { gfxFontGroup *fontGroup = static_cast(aClosure); const gfxFontStyle *fontStyle = fontGroup->GetStyle(); PRBool needsBold; gfxFontEntry *fe = nsnull; // First, look up in the user font set... // If the fontSet matches the family, we must not look for a platform // font of the same name, even if we fail to actually get a fontEntry // here; we'll fall back to the next name in the CSS font-family list. PRBool foundFamily = PR_FALSE; gfxUserFontSet *fs = fontGroup->GetUserFontSet(); if (fs) { // If the fontSet matches the family, but the font has not yet finished // loading (nor has its load timeout fired), the fontGroup should wait // for the download, and not actually draw its text yet. PRBool waitForUserFont = PR_FALSE; fe = fs->FindFontEntry(aName, *fontStyle, foundFamily, needsBold, waitForUserFont); if (!fe && waitForUserFont) { fontGroup->mSkipDrawing = PR_TRUE; } } // Not known in the user font set ==> check system fonts if (!foundFamily) { fe = gfxPlatformFontList::PlatformFontList()-> FindFontForFamily(aName, fontStyle, needsBold); } // add to the font group, unless it's already there if (fe && !fontGroup->HasFont(fe)) { nsRefPtr font = fe->FindOrMakeFont(fontStyle, needsBold); if (font) { fontGroup->mFonts.AppendElement(font); } } return PR_TRUE; } PRBool gfxFontGroup::HasFont(const gfxFontEntry *aFontEntry) { for (PRUint32 i = 0; i < mFonts.Length(); ++i) { if (mFonts.ElementAt(i)->GetFontEntry() == aFontEntry) return PR_TRUE; } return PR_FALSE; } gfxFontGroup::~gfxFontGroup() { mFonts.Clear(); SetUserFontSet(nsnull); } gfxFontGroup * gfxFontGroup::Copy(const gfxFontStyle *aStyle) { return new gfxFontGroup(mFamilies, aStyle, mUserFontSet); } PRBool gfxFontGroup::IsInvalidChar(PRUnichar ch) { if (ch >= 32) { return ch == 0x0085/*NEL*/ || ((ch & 0xFF00) == 0x2000 /* Unicode control character */ && (ch == 0x200B/*ZWSP*/ || ch == 0x2028/*LSEP*/ || ch == 0x2029/*PSEP*/ || IS_BIDI_CONTROL_CHAR(ch))); } // We could just blacklist all control characters, but it seems better // to only blacklist the ones we know cause problems for native font // engines. return ch == 0x0B || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\f' || (ch >= 0x1c && ch <= 0x1f); } PRBool gfxFontGroup::ForEachFont(FontCreationCallback fc, void *closure) { return ForEachFontInternal(mFamilies, mStyle.language, PR_TRUE, PR_TRUE, fc, closure); } PRBool gfxFontGroup::ForEachFont(const nsAString& aFamilies, nsIAtom *aLanguage, FontCreationCallback fc, void *closure) { return ForEachFontInternal(aFamilies, aLanguage, PR_FALSE, PR_TRUE, fc, closure); } struct ResolveData { ResolveData(gfxFontGroup::FontCreationCallback aCallback, nsACString& aGenericFamily, void *aClosure) : mCallback(aCallback), mGenericFamily(aGenericFamily), mClosure(aClosure) { } gfxFontGroup::FontCreationCallback mCallback; nsCString mGenericFamily; void *mClosure; }; PRBool gfxFontGroup::ForEachFontInternal(const nsAString& aFamilies, nsIAtom *aLanguage, PRBool aResolveGeneric, PRBool aResolveFontName, FontCreationCallback fc, void *closure) { const PRUnichar kSingleQuote = PRUnichar('\''); const PRUnichar kDoubleQuote = PRUnichar('\"'); const PRUnichar kComma = PRUnichar(','); nsIAtom *groupAtom = nsnull; nsCAutoString groupString; if (aLanguage) { if (!gLangService) { CallGetService(NS_LANGUAGEATOMSERVICE_CONTRACTID, &gLangService); } if (gLangService) { nsresult rv; groupAtom = gLangService->GetLanguageGroup(aLanguage, &rv); } } if (!groupAtom) { groupAtom = gfxAtoms::x_unicode; } groupAtom->ToUTF8String(groupString); nsPromiseFlatString families(aFamilies); const PRUnichar *p, *p_end; families.BeginReading(p); families.EndReading(p_end); nsAutoString family; nsCAutoString lcFamily; nsAutoString genericFamily; while (p < p_end) { while (nsCRT::IsAsciiSpace(*p) || *p == kComma) if (++p == p_end) return PR_TRUE; PRBool generic; if (*p == kSingleQuote || *p == kDoubleQuote) { // quoted font family PRUnichar quoteMark = *p; if (++p == p_end) return PR_TRUE; const PRUnichar *nameStart = p; // XXX What about CSS character escapes? while (*p != quoteMark) if (++p == p_end) return PR_TRUE; family = Substring(nameStart, p); generic = PR_FALSE; genericFamily.SetIsVoid(PR_TRUE); while (++p != p_end && *p != kComma) /* nothing */ ; } else { // unquoted font family const PRUnichar *nameStart = p; while (++p != p_end && *p != kComma) /* nothing */ ; family = Substring(nameStart, p); family.CompressWhitespace(PR_FALSE, PR_TRUE); if (aResolveGeneric && (family.LowerCaseEqualsLiteral("serif") || family.LowerCaseEqualsLiteral("sans-serif") || family.LowerCaseEqualsLiteral("monospace") || family.LowerCaseEqualsLiteral("cursive") || family.LowerCaseEqualsLiteral("fantasy"))) { generic = PR_TRUE; ToLowerCase(NS_LossyConvertUTF16toASCII(family), lcFamily); nsCAutoString prefName("font.name."); prefName.Append(lcFamily); prefName.AppendLiteral("."); prefName.Append(groupString); nsAdoptingString value = Preferences::GetString(prefName.get()); if (value) { CopyASCIItoUTF16(lcFamily, genericFamily); family = value; } } else { generic = PR_FALSE; genericFamily.SetIsVoid(PR_TRUE); } } if (generic) { ForEachFontInternal(family, groupAtom, PR_FALSE, aResolveFontName, fc, closure); } else if (!family.IsEmpty()) { NS_LossyConvertUTF16toASCII gf(genericFamily); if (aResolveFontName) { ResolveData data(fc, gf, closure); PRBool aborted = PR_FALSE, needsBold; nsresult rv = NS_OK; PRBool foundFamily = PR_FALSE; PRBool waitForUserFont = PR_FALSE; if (mUserFontSet && mUserFontSet->FindFontEntry(family, mStyle, foundFamily, needsBold, waitForUserFont)) { gfxFontGroup::FontResolverProc(family, &data); } else { if (waitForUserFont) { mSkipDrawing = PR_TRUE; } if (!foundFamily) { gfxPlatform *pf = gfxPlatform::GetPlatform(); rv = pf->ResolveFontName(family, gfxFontGroup::FontResolverProc, &data, aborted); } } if (NS_FAILED(rv) || aborted) return PR_FALSE; } else { if (!fc(family, gf, closure)) return PR_FALSE; } } if (generic && aResolveGeneric) { nsCAutoString prefName("font.name-list."); prefName.Append(lcFamily); prefName.AppendLiteral("."); prefName.Append(groupString); nsAdoptingString value = Preferences::GetString(prefName.get()); if (value) { ForEachFontInternal(value, groupAtom, PR_FALSE, aResolveFontName, fc, closure); } } ++p; // may advance past p_end } return PR_TRUE; } PRBool gfxFontGroup::FontResolverProc(const nsAString& aName, void *aClosure) { ResolveData *data = reinterpret_cast(aClosure); return (data->mCallback)(aName, data->mGenericFamily, data->mClosure); } gfxTextRun * gfxFontGroup::MakeEmptyTextRun(const Parameters *aParams, PRUint32 aFlags) { aFlags |= TEXT_IS_8BIT | TEXT_IS_ASCII | TEXT_IS_PERSISTENT; return gfxTextRun::Create(aParams, nsnull, 0, this, aFlags); } gfxTextRun * gfxFontGroup::MakeSpaceTextRun(const Parameters *aParams, PRUint32 aFlags) { aFlags |= TEXT_IS_8BIT | TEXT_IS_ASCII | TEXT_IS_PERSISTENT; static const PRUint8 space = ' '; nsAutoPtr textRun; textRun = gfxTextRun::Create(aParams, &space, 1, this, aFlags); if (!textRun) return nsnull; gfxFont *font = GetFontAt(0); if (NS_UNLIKELY(GetStyle()->size == 0)) { // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle // them, and always create at least size 1 fonts, i.e. they still // render something for size 0 fonts. textRun->AddGlyphRun(font, gfxTextRange::kFontGroup, 0, PR_FALSE); } else { textRun->SetSpaceGlyph(font, aParams->mContext, 0); } // Note that the gfxGlyphExtents glyph bounds storage for the font will // always contain an entry for the font's space glyph, so we don't have // to call FetchGlyphExtents here. return textRun.forget(); } #define UNICODE_LRO 0x202d #define UNICODE_RLO 0x202e #define UNICODE_PDF 0x202c inline void AppendDirectionalIndicatorStart(PRUint32 aFlags, nsAString& aString) { static const PRUnichar overrides[2] = { UNICODE_LRO, UNICODE_RLO }; aString.Append(overrides[(aFlags & gfxTextRunFactory::TEXT_IS_RTL) != 0]); aString.Append(' '); } inline void AppendDirectionalIndicatorEnd(PRBool aNeedDirection, nsAString& aString) { // append a space (always, for consistent treatment of last char, // and a direction control if required (we skip this for 8-bit text, // which is known to be unidirectional LTR, unless the direction was // forced RTL via overrides) aString.Append(' '); if (!aNeedDirection) return; aString.Append('.'); aString.Append(UNICODE_PDF); } gfxTextRun * gfxFontGroup::MakeTextRun(const PRUint8 *aString, PRUint32 aLength, const Parameters *aParams, PRUint32 aFlags) { NS_ASSERTION(aLength > 0, "should use MakeEmptyTextRun for zero-length text"); NS_ASSERTION(aFlags & TEXT_IS_8BIT, "should be marked 8bit"); gfxTextRun *textRun = gfxTextRun::Create(aParams, aString, aLength, this, aFlags); if (!textRun) return nsnull; nsDependentCSubstring cString(reinterpret_cast(aString), reinterpret_cast(aString) + aLength); nsAutoString utf16; AppendASCIItoUTF16(cString, utf16); InitTextRun(aParams->mContext, textRun, utf16.get(), utf16.Length()); textRun->FetchGlyphExtents(aParams->mContext); return textRun; } gfxTextRun * gfxFontGroup::MakeTextRun(const PRUnichar *aString, PRUint32 aLength, const Parameters *aParams, PRUint32 aFlags) { NS_ASSERTION(aLength > 0, "should use MakeEmptyTextRun for zero-length text"); gfxTextRun *textRun = gfxTextRun::Create(aParams, aString, aLength, this, aFlags); if (!textRun) return nsnull; gfxPlatform::GetPlatform()->SetupClusterBoundaries(textRun, aString); InitTextRun(aParams->mContext, textRun, aString, aLength); textRun->FetchGlyphExtents(aParams->mContext); return textRun; } void gfxFontGroup::InitTextRun(gfxContext *aContext, gfxTextRun *aTextRun, const PRUnichar *aString, PRUint32 aLength) { // split into script runs so that script can potentially influence // the font matching process below gfxScriptItemizer scriptRuns(aString, aLength); #ifdef PR_LOGGING PRLogModuleInfo *log = (mStyle.systemFont ? gfxPlatform::GetLog(eGfxLog_textrunui) : gfxPlatform::GetLog(eGfxLog_textrun)); #endif PRUint32 runStart = 0, runLimit = aLength; PRInt32 runScript = HB_SCRIPT_LATIN; while (scriptRuns.Next(runStart, runLimit, runScript)) { #ifdef PR_LOGGING if (NS_UNLIKELY(log)) { nsCAutoString lang; mStyle.language->ToUTF8String(lang); PRUint32 runLen = runLimit - runStart; PR_LOG(log, PR_LOG_DEBUG,\ ("(%s) fontgroup: [%s] lang: %s script: %d len %d " "weight: %d width: %d style: %s " "TEXTRUN [%s] ENDTEXTRUN\n", (mStyle.systemFont ? "textrunui" : "textrun"), NS_ConvertUTF16toUTF8(mFamilies).get(), lang.get(), runScript, runLen, PRUint32(mStyle.weight), PRUint32(mStyle.stretch), (mStyle.style & FONT_STYLE_ITALIC ? "italic" : (mStyle.style & FONT_STYLE_OBLIQUE ? "oblique" : "normal")), NS_ConvertUTF16toUTF8(aString + runStart, runLen).get())); } #endif InitScriptRun(aContext, aTextRun, aString, aLength, runStart, runLimit, runScript); } aTextRun->SortGlyphRuns(); } void gfxFontGroup::InitScriptRun(gfxContext *aContext, gfxTextRun *aTextRun, const PRUnichar *aString, PRUint32 aTotalLength, PRUint32 aScriptRunStart, PRUint32 aScriptRunEnd, PRInt32 aRunScript) { gfxFont *mainFont = mFonts[0].get(); PRUint32 runStart = aScriptRunStart; nsAutoTArray fontRanges; ComputeRanges(fontRanges, aString, aScriptRunStart, aScriptRunEnd, aRunScript); PRUint32 numRanges = fontRanges.Length(); for (PRUint32 r = 0; r < numRanges; r++) { const gfxTextRange& range = fontRanges[r]; PRUint32 matchedLength = range.Length(); gfxFont *matchedFont = (range.font ? range.font.get() : nsnull); // create the glyph run for this range if (matchedFont) { aTextRun->AddGlyphRun(matchedFont, range.matchType, runStart, (matchedLength > 0)); } else { aTextRun->AddGlyphRun(mainFont, gfxTextRange::kFontGroup, runStart, (matchedLength > 0)); } if (matchedFont) { // do glyph layout and record the resulting positioned glyphs if (!matchedFont->SplitAndInitTextRun(aContext, aTextRun, aString, runStart, matchedLength, aRunScript)) { // glyph layout failed! treat as missing glyphs matchedFont = nsnull; } } if (!matchedFont) { for (PRUint32 index = runStart; index < runStart + matchedLength; index++) { // Record the char code so we can draw a box with the Unicode value PRUint32 ch = aString[index]; if (NS_IS_HIGH_SURROGATE(ch) && index + 1 < aScriptRunEnd && NS_IS_LOW_SURROGATE(aString[index+1])) { aTextRun->SetMissingGlyph(index, SURROGATE_TO_UCS4(aString[index], aString[index+1])); index++; } else { gfxFloat wid = mainFont->SynthesizeSpaceWidth(ch); if (wid >= 0.0) { nscoord advance = aTextRun->GetAppUnitsPerDevUnit() * NS_floor(wid + 0.5); gfxTextRun::CompressedGlyph g; if (gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance)) { aTextRun->SetSimpleGlyph(index, g.SetSimpleGlyph(advance, mainFont->GetSpaceGlyph())); } else { gfxTextRun::DetailedGlyph detailedGlyph; detailedGlyph.mGlyphID = mainFont->GetSpaceGlyph(); detailedGlyph.mAdvance = advance; detailedGlyph.mXOffset = detailedGlyph.mYOffset = 0; g.SetComplex(PR_TRUE, PR_TRUE, 1); aTextRun->SetGlyphs(index, g, &detailedGlyph); } } else { aTextRun->SetMissingGlyph(index, ch); } } } } runStart += matchedLength; } // It's possible for CoreText to omit glyph runs if it decides they contain // only invisibles (e.g., U+FEFF, see reftest 474417-1). In this case, we // need to eliminate them from the glyph run array to avoid drawing "partial // ligatures" with the wrong font. aTextRun->SanitizeGlyphRuns(); } already_AddRefed gfxFontGroup::FindFontForChar(PRUint32 aCh, PRUint32 aPrevCh, PRInt32 aRunScript, gfxFont *aPrevMatchedFont, PRUint8 *aMatchType) { nsRefPtr selectedFont; // if this character or the previous one is a join-causer, // use the same font as the previous range if we can if (gfxFontUtils::IsJoinCauser(aCh) || gfxFontUtils::IsJoinCauser(aPrevCh)) { if (aPrevMatchedFont && aPrevMatchedFont->HasCharacter(aCh)) { selectedFont = aPrevMatchedFont; return selectedFont.forget(); } } // if this character is a variation selector, // use the previous font regardless of whether it supports VS or not. // otherwise the text run will be divided. if (gfxFontUtils::IsVarSelector(aCh)) { if (aPrevMatchedFont) { selectedFont = aPrevMatchedFont; return selectedFont.forget(); } // VS alone. it's meaningless to search different fonts return nsnull; } // 1. check fonts in the font group for (PRUint32 i = 0; i < FontListLength(); i++) { nsRefPtr font = GetFontAt(i); if (font->HasCharacter(aCh)) { *aMatchType = gfxTextRange::kFontGroup; return font.forget(); } } // if character is in Private Use Area, don't do matching against pref or system fonts if ((aCh >= 0xE000 && aCh <= 0xF8FF) || (aCh >= 0xF0000 && aCh <= 0x10FFFD)) return nsnull; // 2. search pref fonts if ((selectedFont = WhichPrefFontSupportsChar(aCh))) { *aMatchType = gfxTextRange::kPrefsFallback; return selectedFont.forget(); } // 3. use fallback fonts // -- before searching for something else check the font used for the previous character if (!selectedFont && aPrevMatchedFont && aPrevMatchedFont->HasCharacter(aCh)) { *aMatchType = gfxTextRange::kSystemFallback; selectedFont = aPrevMatchedFont; return selectedFont.forget(); } // for known "space" characters, don't do a full system-fallback search; // we'll synthesize appropriate-width spaces instead of missing-glyph boxes if (gfxUnicodeProperties::GetGeneralCategory(aCh) == HB_CATEGORY_SPACE_SEPARATOR && GetFontAt(0)->SynthesizeSpaceWidth(aCh) >= 0.0) { return nsnull; } // -- otherwise look for other stuff if (!selectedFont) { *aMatchType = gfxTextRange::kSystemFallback; selectedFont = WhichSystemFontSupportsChar(aCh); return selectedFont.forget(); } return nsnull; } void gfxFontGroup::ComputeRanges(nsTArray& aRanges, const PRUnichar *aString, PRUint32 begin, PRUint32 end, PRInt32 aRunScript) { const PRUnichar *str = aString + begin; PRUint32 len = end - begin; aRanges.Clear(); if (len == 0) { return; } PRUint32 prevCh = 0; gfxFont *prevFont = nsnull; PRUint8 matchType = 0; for (PRUint32 i = 0; i < len; i++) { const PRUint32 origI = i; // save off in case we increase for surrogate // set up current ch PRUint32 ch = str[i]; if ((i+1 < len) && NS_IS_HIGH_SURROGATE(ch) && NS_IS_LOW_SURROGATE(str[i+1])) { i++; ch = SURROGATE_TO_UCS4(ch, str[i]); } // find the font for this char nsRefPtr font = FindFontForChar(ch, prevCh, aRunScript, prevFont, &matchType); prevCh = ch; if (aRanges.Length() == 0) { // first char ==> make a new range aRanges.AppendElement(gfxTextRange(0, 1, font, matchType)); prevFont = font; } else { // if font has changed, make a new range gfxTextRange& prevRange = aRanges[aRanges.Length() - 1]; if (prevRange.font != font || prevRange.matchType != matchType) { // close out the previous range prevRange.end = origI; aRanges.AppendElement(gfxTextRange(origI, i+1, font, matchType)); // update prevFont for the next match, *unless* we switched // fonts on a ZWJ, in which case propagating the changed font // is probably not a good idea (see bug 619511) if (!gfxFontUtils::IsJoinCauser(ch)) { prevFont = font; } } } } aRanges[aRanges.Length()-1].end = len; } gfxUserFontSet* gfxFontGroup::GetUserFontSet() { return mUserFontSet; } void gfxFontGroup::SetUserFontSet(gfxUserFontSet *aUserFontSet) { NS_IF_RELEASE(mUserFontSet); mUserFontSet = aUserFontSet; NS_IF_ADDREF(mUserFontSet); mCurrGeneration = GetGeneration(); } PRUint64 gfxFontGroup::GetGeneration() { if (!mUserFontSet) return 0; return mUserFontSet->GetGeneration(); } void gfxFontGroup::UpdateFontList() { if (mUserFontSet && mCurrGeneration != GetGeneration()) { // xxx - can probably improve this to detect when all fonts were found, so no need to update list mFonts.Clear(); mUnderlineOffset = UNDERLINE_OFFSET_NOT_SET; mSkipDrawing = PR_FALSE; // bug 548184 - need to clean up FT2, OS/2 platform code to use BuildFontList #if defined(XP_MACOSX) || defined(XP_WIN) || defined(ANDROID) BuildFontList(); #else ForEachFont(FindPlatformFont, this); #endif mCurrGeneration = GetGeneration(); } } struct PrefFontCallbackData { PrefFontCallbackData(nsTArray >& aFamiliesArray) : mPrefFamilies(aFamiliesArray) {} nsTArray >& mPrefFamilies; static PRBool AddFontFamilyEntry(eFontPrefLang aLang, const nsAString& aName, void *aClosure) { PrefFontCallbackData *prefFontData = static_cast(aClosure); gfxFontFamily *family = gfxPlatformFontList::PlatformFontList()->FindFamily(aName); if (family) { prefFontData->mPrefFamilies.AppendElement(family); } return PR_TRUE; } }; already_AddRefed gfxFontGroup::WhichPrefFontSupportsChar(PRUint32 aCh) { gfxFont *font; // FindCharUnicodeRange only supports BMP character points and there are no non-BMP fonts in prefs if (aCh > 0xFFFF) return nsnull; // get the pref font list if it hasn't been set up already PRUint32 unicodeRange = FindCharUnicodeRange(aCh); eFontPrefLang charLang = gfxPlatform::GetPlatform()->GetFontPrefLangFor(unicodeRange); // if the last pref font was the first family in the pref list, no need to recheck through a list of families if (mLastPrefFont && charLang == mLastPrefLang && mLastPrefFirstFont && mLastPrefFont->HasCharacter(aCh)) { font = mLastPrefFont; NS_ADDREF(font); return font; } // based on char lang and page lang, set up list of pref lang fonts to check eFontPrefLang prefLangs[kMaxLenPrefLangList]; PRUint32 i, numLangs = 0; gfxPlatform::GetPlatform()->GetLangPrefs(prefLangs, numLangs, charLang, mPageLang); for (i = 0; i < numLangs; i++) { nsAutoTArray, 5> families; eFontPrefLang currentLang = prefLangs[i]; gfxPlatformFontList *fontList = gfxPlatformFontList::PlatformFontList(); // get the pref families for a single pref lang if (!fontList->GetPrefFontFamilyEntries(currentLang, &families)) { eFontPrefLang prefLangsToSearch[1] = { currentLang }; PrefFontCallbackData prefFontData(families); gfxPlatform::ForEachPrefFont(prefLangsToSearch, 1, PrefFontCallbackData::AddFontFamilyEntry, &prefFontData); fontList->SetPrefFontFamilyEntries(currentLang, families); } // find the first pref font that includes the character PRUint32 j, numPrefs; numPrefs = families.Length(); for (j = 0; j < numPrefs; j++) { // look up the appropriate face gfxFontFamily *family = families[j]; if (!family) continue; // if a pref font is used, it's likely to be used again in the same text run. // the style doesn't change so the face lookup can be cached rather than calling // FindOrMakeFont repeatedly. speeds up FindFontForChar lookup times for subsequent // pref font lookups if (family == mLastPrefFamily && mLastPrefFont->HasCharacter(aCh)) { font = mLastPrefFont; NS_ADDREF(font); return font; } PRBool needsBold; gfxFontEntry *fe = family->FindFontForStyle(mStyle, needsBold); // if ch in cmap, create and return a gfxFont if (fe && fe->TestCharacterMap(aCh)) { nsRefPtr prefFont = fe->FindOrMakeFont(&mStyle, needsBold); if (!prefFont) continue; mLastPrefFamily = family; mLastPrefFont = prefFont; mLastPrefLang = charLang; mLastPrefFirstFont = (i == 0 && j == 0); return prefFont.forget(); } } } return nsnull; } already_AddRefed gfxFontGroup::WhichSystemFontSupportsChar(PRUint32 aCh) { gfxFontEntry *fe = gfxPlatformFontList::PlatformFontList()->FindFontForChar(aCh, GetFontAt(0)); if (fe) { nsRefPtr font = fe->FindOrMakeFont(&mStyle, PR_FALSE); // ignore bolder considerations in system fallback case... return font.forget(); } return nsnull; } /*static*/ void gfxFontGroup::Shutdown() { NS_IF_RELEASE(gLangService); } nsILanguageAtomService* gfxFontGroup::gLangService = nsnull; #define DEFAULT_PIXEL_FONT_SIZE 16.0f /*static*/ void gfxFontStyle::ParseFontFeatureSettings(const nsString& aFeatureString, nsTArray& aFeatures) { aFeatures.Clear(); PRUint32 offset = 0; while (offset < aFeatureString.Length()) { // skip whitespace while (offset < aFeatureString.Length() && nsCRT::IsAsciiSpace(aFeatureString[offset])) { ++offset; } PRInt32 limit = aFeatureString.FindChar(',', offset); if (limit < 0) { limit = aFeatureString.Length(); } // check that we have enough text for a 4-char tag, // the '=' sign, and at least one digit if (offset + 6 <= PRUint32(limit) && aFeatureString[offset+4] == '=') { gfxFontFeature setting; setting.mTag = ((aFeatureString[offset] & 0xff) << 24) + ((aFeatureString[offset+1] & 0xff) << 16) + ((aFeatureString[offset+2] & 0xff) << 8) + (aFeatureString[offset+3] & 0xff); nsString valString; aFeatureString.Mid(valString, offset+5, limit-offset-5); PRInt32 rv; setting.mValue = valString.ToInteger(&rv); if (rv == NS_OK) { // we keep the features array sorted so that we can // use nsTArray<>::Equals() to compare feature lists aFeatures.InsertElementSorted(setting); } } offset = limit + 1; } } /*static*/ PRUint32 gfxFontStyle::ParseFontLanguageOverride(const nsString& aLangTag) { if (!aLangTag.Length() || aLangTag.Length() > 4) { return NO_FONT_LANGUAGE_OVERRIDE; } PRUint32 index, result = 0; for (index = 0; index < aLangTag.Length(); ++index) { PRUnichar ch = aLangTag[index]; if (!nsCRT::IsAscii(ch)) { // valid tags are pure ASCII return NO_FONT_LANGUAGE_OVERRIDE; } result = (result << 8) + ch; } while (index++ < 4) { result = (result << 8) + 0x20; } return result; } gfxFontStyle::gfxFontStyle() : style(FONT_STYLE_NORMAL), systemFont(PR_TRUE), printerFont(PR_FALSE), weight(FONT_WEIGHT_NORMAL), stretch(NS_FONT_STRETCH_NORMAL), size(DEFAULT_PIXEL_FONT_SIZE), sizeAdjust(0.0f), language(gfxAtoms::x_western), languageOverride(NO_FONT_LANGUAGE_OVERRIDE) { } gfxFontStyle::gfxFontStyle(PRUint8 aStyle, PRUint16 aWeight, PRInt16 aStretch, gfxFloat aSize, nsIAtom *aLanguage, float aSizeAdjust, PRPackedBool aSystemFont, PRPackedBool aPrinterFont, const nsString& aFeatureSettings, const nsString& aLanguageOverride): style(aStyle), systemFont(aSystemFont), printerFont(aPrinterFont), weight(aWeight), stretch(aStretch), size(aSize), sizeAdjust(aSizeAdjust), language(aLanguage), languageOverride(ParseFontLanguageOverride(aLanguageOverride)) { ParseFontFeatureSettings(aFeatureSettings, featureSettings); if (weight > 900) weight = 900; if (weight < 100) weight = 100; if (size >= FONT_MAX_SIZE) { size = FONT_MAX_SIZE; sizeAdjust = 0.0; } else if (size < 0.0) { NS_WARNING("negative font size"); size = 0.0; } if (!language) { NS_WARNING("null language"); language = gfxAtoms::x_western; } } gfxFontStyle::gfxFontStyle(const gfxFontStyle& aStyle) : style(aStyle.style), systemFont(aStyle.systemFont), printerFont(aStyle.printerFont), weight(aStyle.weight), stretch(aStyle.stretch), size(aStyle.size), sizeAdjust(aStyle.sizeAdjust), language(aStyle.language), languageOverride(aStyle.languageOverride) { featureSettings.AppendElements(aStyle.featureSettings); } PRInt8 gfxFontStyle::ComputeWeight() const { PRInt8 baseWeight = (weight + 50) / 100; if (baseWeight < 0) baseWeight = 0; if (baseWeight > 9) baseWeight = 9; return baseWeight; } PRBool gfxTextRun::GlyphRunIterator::NextRun() { if (mNextIndex >= mTextRun->mGlyphRuns.Length()) return PR_FALSE; mGlyphRun = &mTextRun->mGlyphRuns[mNextIndex]; if (mGlyphRun->mCharacterOffset >= mEndOffset) return PR_FALSE; mStringStart = PR_MAX(mStartOffset, mGlyphRun->mCharacterOffset); PRUint32 last = mNextIndex + 1 < mTextRun->mGlyphRuns.Length() ? mTextRun->mGlyphRuns[mNextIndex + 1].mCharacterOffset : mTextRun->mCharacterCount; mStringEnd = PR_MIN(mEndOffset, last); ++mNextIndex; return PR_TRUE; } #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS static void AccountStorageForTextRun(gfxTextRun *aTextRun, PRInt32 aSign) { // Ignores detailed glyphs... we don't know when those have been constructed // Also ignores gfxSkipChars dynamic storage (which won't be anything // for preformatted text) // Also ignores GlyphRun array, again because it hasn't been constructed // by the time this gets called. If there's only one glyphrun that's stored // directly in the textrun anyway so no additional overhead. PRUint32 length = aTextRun->GetLength(); PRInt32 bytes = length * sizeof(gfxTextRun::CompressedGlyph); if (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_IS_PERSISTENT) { bytes += length * ((aTextRun->GetFlags() & gfxTextRunFactory::TEXT_IS_8BIT) ? 1 : 2); bytes += sizeof(gfxTextRun::CompressedGlyph) - 1; bytes &= ~(sizeof(gfxTextRun::CompressedGlyph) - 1); } bytes += sizeof(gfxTextRun); gTextRunStorage += bytes*aSign; gTextRunStorageHighWaterMark = PR_MAX(gTextRunStorageHighWaterMark, gTextRunStorage); } #endif // Helper for textRun creation to preallocate storage for glyphs and text; // this function returns a pointer to the newly-allocated glyph storage, // AND modifies the aText parameter if TEXT_IS_PERSISTENT was not set. // In that case, the text is appended to the glyph storage, so a single // delete[] operation in the textRun destructor will free both. // Returns nsnull if allocation fails. gfxTextRun::CompressedGlyph * gfxTextRun::AllocateStorage(const void*& aText, PRUint32 aLength, PRUint32 aFlags) { // Here, we rely on CompressedGlyph being the largest unit we care about for // allocation/alignment of either glyph data or text, so we allocate an array // of CompressedGlyphs, then take the last chunk of that and cast a pointer to // PRUint8* or PRUnichar* for text storage. // always need to allocate storage for the glyph data PRUint64 allocCount = aLength; // if the text is not persistent, we also need space for a copy if (!(aFlags & gfxTextRunFactory::TEXT_IS_PERSISTENT)) { // figure out number of extra CompressedGlyph elements we need to // get sufficient space for the text if (aFlags & gfxTextRunFactory::TEXT_IS_8BIT) { allocCount += (aLength + sizeof(CompressedGlyph)-1) / sizeof(CompressedGlyph); } else { allocCount += (aLength*sizeof(PRUnichar) + sizeof(CompressedGlyph)-1) / sizeof(CompressedGlyph); } } // allocate the storage we need, returning nsnull on failure rather than // throwing an exception (because web content can create huge runs) CompressedGlyph *storage = new (std::nothrow) CompressedGlyph[allocCount]; if (!storage) { NS_WARNING("failed to allocate glyph/text storage for text run!"); return nsnull; } // copy the text if we need to keep a copy in the textrun if (!(aFlags & gfxTextRunFactory::TEXT_IS_PERSISTENT)) { if (aFlags & gfxTextRunFactory::TEXT_IS_8BIT) { PRUint8 *newText = reinterpret_cast(storage + aLength); memcpy(newText, aText, aLength); aText = newText; } else { PRUnichar *newText = reinterpret_cast(storage + aLength); memcpy(newText, aText, aLength*sizeof(PRUnichar)); aText = newText; } } return storage; } gfxTextRun * gfxTextRun::Create(const gfxTextRunFactory::Parameters *aParams, const void *aText, PRUint32 aLength, gfxFontGroup *aFontGroup, PRUint32 aFlags) { CompressedGlyph *glyphStorage = AllocateStorage(aText, aLength, aFlags); if (!glyphStorage) { return nsnull; } return new gfxTextRun(aParams, aText, aLength, aFontGroup, aFlags, glyphStorage); } gfxTextRun::gfxTextRun(const gfxTextRunFactory::Parameters *aParams, const void *aText, PRUint32 aLength, gfxFontGroup *aFontGroup, PRUint32 aFlags, CompressedGlyph *aGlyphStorage) : mCharacterGlyphs(aGlyphStorage), mUserData(aParams->mUserData), mFontGroup(aFontGroup), mAppUnitsPerDevUnit(aParams->mAppUnitsPerDevUnit), mFlags(aFlags), mCharacterCount(aLength), mHashCode(0) { NS_ASSERTION(mAppUnitsPerDevUnit != 0, "Invalid app unit scale"); MOZ_COUNT_CTOR(gfxTextRun); NS_ADDREF(mFontGroup); if (aParams->mSkipChars) { mSkipChars.TakeFrom(aParams->mSkipChars); } if (mFlags & gfxTextRunFactory::TEXT_IS_8BIT) { mText.mSingle = static_cast(aText); } else { mText.mDouble = static_cast(aText); } #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS AccountStorageForTextRun(this, 1); #endif mUserFontSetGeneration = mFontGroup->GetGeneration(); mSkipDrawing = mFontGroup->ShouldSkipDrawing(); } gfxTextRun::~gfxTextRun() { #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS AccountStorageForTextRun(this, -1); #endif #ifdef DEBUG // Make it easy to detect a dead text run mFlags = 0xFFFFFFFF; #endif // this will also delete the text, if it is owned by the run, // because we merge the storage allocations delete [] mCharacterGlyphs; NS_RELEASE(mFontGroup); MOZ_COUNT_DTOR(gfxTextRun); } PRBool gfxTextRun::SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength, PRUint8 *aBreakBefore, gfxContext *aRefContext) { NS_ASSERTION(aStart + aLength <= mCharacterCount, "Overflow"); if (!mCharacterGlyphs) return PR_TRUE; PRUint32 changed = 0; PRUint32 i; for (i = 0; i < aLength; ++i) { PRUint8 canBreak = aBreakBefore[i]; if (canBreak && !mCharacterGlyphs[aStart + i].IsClusterStart()) { // This can happen ... there is no guarantee that our linebreaking rules // align with the platform's idea of what constitutes a cluster. NS_WARNING("Break suggested inside cluster!"); canBreak = CompressedGlyph::FLAG_BREAK_TYPE_NONE; } changed |= mCharacterGlyphs[aStart + i].SetCanBreakBefore(canBreak); } return changed != 0; } gfxTextRun::LigatureData gfxTextRun::ComputeLigatureData(PRUint32 aPartStart, PRUint32 aPartEnd, PropertyProvider *aProvider) { NS_ASSERTION(aPartStart < aPartEnd, "Computing ligature data for empty range"); NS_ASSERTION(aPartEnd <= mCharacterCount, "Character length overflow"); LigatureData result; CompressedGlyph *charGlyphs = mCharacterGlyphs; PRUint32 i; for (i = aPartStart; !charGlyphs[i].IsLigatureGroupStart(); --i) { NS_ASSERTION(i > 0, "Ligature at the start of the run??"); } result.mLigatureStart = i; for (i = aPartStart + 1; i < mCharacterCount && !charGlyphs[i].IsLigatureGroupStart(); ++i) { } result.mLigatureEnd = i; PRInt32 ligatureWidth = GetAdvanceForGlyphs(result.mLigatureStart, result.mLigatureEnd); // Count the number of started clusters we have seen PRUint32 totalClusterCount = 0; PRUint32 partClusterIndex = 0; PRUint32 partClusterCount = 0; for (i = result.mLigatureStart; i < result.mLigatureEnd; ++i) { // Treat the first character of the ligature as the start of a // cluster for our purposes of allocating ligature width to its // characters. if (i == result.mLigatureStart || charGlyphs[i].IsClusterStart()) { ++totalClusterCount; if (i < aPartStart) { ++partClusterIndex; } else if (i < aPartEnd) { ++partClusterCount; } } } NS_ASSERTION(totalClusterCount > 0, "Ligature involving no clusters??"); result.mPartAdvance = ligatureWidth*partClusterIndex/totalClusterCount; result.mPartWidth = ligatureWidth*partClusterCount/totalClusterCount; if (partClusterCount == 0) { // nothing to draw result.mClipBeforePart = result.mClipAfterPart = PR_TRUE; } else { // Determine whether we should clip before or after this part when // drawing its slice of the ligature. // We need to clip before the part if any cluster is drawn before // this part. result.mClipBeforePart = partClusterIndex > 0; // We need to clip after the part if any cluster is drawn after // this part. result.mClipAfterPart = partClusterIndex + partClusterCount < totalClusterCount; } if (aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) { gfxFont::Spacing spacing; if (aPartStart == result.mLigatureStart) { aProvider->GetSpacing(aPartStart, 1, &spacing); result.mPartWidth += spacing.mBefore; } if (aPartEnd == result.mLigatureEnd) { aProvider->GetSpacing(aPartEnd - 1, 1, &spacing); result.mPartWidth += spacing.mAfter; } } return result; } gfxFloat gfxTextRun::ComputePartialLigatureWidth(PRUint32 aPartStart, PRUint32 aPartEnd, PropertyProvider *aProvider) { if (aPartStart >= aPartEnd) return 0; LigatureData data = ComputeLigatureData(aPartStart, aPartEnd, aProvider); return data.mPartWidth; } PRInt32 gfxTextRun::GetAdvanceForGlyphs(PRUint32 aStart, PRUint32 aEnd) { const CompressedGlyph *glyphData = mCharacterGlyphs + aStart; PRInt32 advance = 0; PRUint32 i; for (i = aStart; i < aEnd; ++i, ++glyphData) { if (glyphData->IsSimpleGlyph()) { advance += glyphData->GetSimpleAdvance(); } else { PRUint32 glyphCount = glyphData->GetGlyphCount(); if (glyphCount == 0) { continue; } const DetailedGlyph *details = GetDetailedGlyphs(i); if (details) { PRUint32 j; for (j = 0; j < glyphCount; ++j, ++details) { advance += details->mAdvance; } } } } return advance; } static void GetAdjustedSpacing(gfxTextRun *aTextRun, PRUint32 aStart, PRUint32 aEnd, gfxTextRun::PropertyProvider *aProvider, gfxTextRun::PropertyProvider::Spacing *aSpacing) { if (aStart >= aEnd) return; aProvider->GetSpacing(aStart, aEnd - aStart, aSpacing); #ifdef DEBUG // Check to see if we have spacing inside ligatures const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs(); PRUint32 i; for (i = aStart; i < aEnd; ++i) { if (!charGlyphs[i].IsLigatureGroupStart()) { NS_ASSERTION(i == aStart || aSpacing[i - aStart].mBefore == 0, "Before-spacing inside a ligature!"); NS_ASSERTION(i - 1 <= aStart || aSpacing[i - 1 - aStart].mAfter == 0, "After-spacing inside a ligature!"); } } #endif } PRBool gfxTextRun::GetAdjustedSpacingArray(PRUint32 aStart, PRUint32 aEnd, PropertyProvider *aProvider, PRUint32 aSpacingStart, PRUint32 aSpacingEnd, nsTArray *aSpacing) { if (!aProvider || !(mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) return PR_FALSE; if (!aSpacing->AppendElements(aEnd - aStart)) return PR_FALSE; memset(aSpacing->Elements(), 0, sizeof(gfxFont::Spacing)*(aSpacingStart - aStart)); GetAdjustedSpacing(this, aSpacingStart, aSpacingEnd, aProvider, aSpacing->Elements() + aSpacingStart - aStart); memset(aSpacing->Elements() + aSpacingEnd - aStart, 0, sizeof(gfxFont::Spacing)*(aEnd - aSpacingEnd)); return PR_TRUE; } void gfxTextRun::ShrinkToLigatureBoundaries(PRUint32 *aStart, PRUint32 *aEnd) { if (*aStart >= *aEnd) return; CompressedGlyph *charGlyphs = mCharacterGlyphs; while (*aStart < *aEnd && !charGlyphs[*aStart].IsLigatureGroupStart()) { ++(*aStart); } if (*aEnd < mCharacterCount) { while (*aEnd > *aStart && !charGlyphs[*aEnd].IsLigatureGroupStart()) { --(*aEnd); } } } void gfxTextRun::DrawGlyphs(gfxFont *aFont, gfxContext *aContext, PRBool aDrawToPath, gfxPoint *aPt, PRUint32 aStart, PRUint32 aEnd, PropertyProvider *aProvider, PRUint32 aSpacingStart, PRUint32 aSpacingEnd) { nsAutoTArray spacingBuffer; PRBool haveSpacing = GetAdjustedSpacingArray(aStart, aEnd, aProvider, aSpacingStart, aSpacingEnd, &spacingBuffer); aFont->Draw(this, aStart, aEnd, aContext, aDrawToPath, aPt, haveSpacing ? spacingBuffer.Elements() : nsnull); } static void ClipPartialLigature(gfxTextRun *aTextRun, gfxFloat *aLeft, gfxFloat *aRight, gfxFloat aXOrigin, gfxTextRun::LigatureData *aLigature) { if (aLigature->mClipBeforePart) { if (aTextRun->IsRightToLeft()) { *aRight = PR_MIN(*aRight, aXOrigin); } else { *aLeft = PR_MAX(*aLeft, aXOrigin); } } if (aLigature->mClipAfterPart) { gfxFloat endEdge = aXOrigin + aTextRun->GetDirection()*aLigature->mPartWidth; if (aTextRun->IsRightToLeft()) { *aLeft = PR_MAX(*aLeft, endEdge); } else { *aRight = PR_MIN(*aRight, endEdge); } } } void gfxTextRun::DrawPartialLigature(gfxFont *aFont, gfxContext *aCtx, PRUint32 aStart, PRUint32 aEnd, gfxPoint *aPt, PropertyProvider *aProvider) { if (aStart >= aEnd) return; // Need to preserve the path, otherwise this can break canvas text-on-path; // in general it seems like a good thing, as naive callers probably won't // expect gfxTextRun::Draw to implicitly destroy the current path. gfxContextPathAutoSaveRestore savePath(aCtx); // Draw partial ligature. We hack this by clipping the ligature. LigatureData data = ComputeLigatureData(aStart, aEnd, aProvider); gfxRect clipExtents = aCtx->GetClipExtents(); gfxFloat left = clipExtents.X()*mAppUnitsPerDevUnit; gfxFloat right = clipExtents.XMost()*mAppUnitsPerDevUnit; ClipPartialLigature(this, &left, &right, aPt->x, &data); aCtx->Save(); aCtx->NewPath(); // use division here to ensure that when the rect is aligned on multiples // of mAppUnitsPerDevUnit, we clip to true device unit boundaries. // Also, make sure we snap the rectangle to device pixels. aCtx->Rectangle(gfxRect(left/mAppUnitsPerDevUnit, clipExtents.Y(), (right - left)/mAppUnitsPerDevUnit, clipExtents.Height()), PR_TRUE); aCtx->Clip(); gfxFloat direction = GetDirection(); gfxPoint pt(aPt->x - direction*data.mPartAdvance, aPt->y); DrawGlyphs(aFont, aCtx, PR_FALSE, &pt, data.mLigatureStart, data.mLigatureEnd, aProvider, aStart, aEnd); aCtx->Restore(); aPt->x += direction*data.mPartWidth; } // returns true if a glyph run is using a font with synthetic bolding enabled, false otherwise static PRBool HasSyntheticBold(gfxTextRun *aRun, PRUint32 aStart, PRUint32 aLength) { gfxTextRun::GlyphRunIterator iter(aRun, aStart, aLength); while (iter.NextRun()) { gfxFont *font = iter.GetGlyphRun()->mFont; if (font && font->IsSyntheticBold()) { return PR_TRUE; } } return PR_FALSE; } // returns true if color is non-opaque (i.e. alpha != 1.0) or completely transparent, false otherwise // if true, color is set on output static PRBool HasNonOpaqueColor(gfxContext *aContext, gfxRGBA& aCurrentColor) { if (aContext->GetDeviceColor(aCurrentColor)) { if (aCurrentColor.a < 1.0 && aCurrentColor.a > 0.0) { return PR_TRUE; } } return PR_FALSE; } // helper class for double-buffering drawing with non-opaque color struct BufferAlphaColor { BufferAlphaColor(gfxContext *aContext) : mContext(aContext) { } ~BufferAlphaColor() {} void PushSolidColor(const gfxRect& aBounds, const gfxRGBA& aAlphaColor, PRUint32 appsPerDevUnit) { mContext->Save(); mContext->NewPath(); mContext->Rectangle(gfxRect(aBounds.X() / appsPerDevUnit, aBounds.Y() / appsPerDevUnit, aBounds.Width() / appsPerDevUnit, aBounds.Height() / appsPerDevUnit), PR_TRUE); mContext->Clip(); mContext->SetColor(gfxRGBA(aAlphaColor.r, aAlphaColor.g, aAlphaColor.b)); mContext->PushGroup(gfxASurface::CONTENT_COLOR_ALPHA); mAlpha = aAlphaColor.a; } void PopAlpha() { // pop the text, using the color alpha as the opacity mContext->PopGroupToSource(); mContext->SetOperator(gfxContext::OPERATOR_OVER); mContext->Paint(mAlpha); mContext->Restore(); } gfxContext *mContext; gfxFloat mAlpha; }; void gfxTextRun::AdjustAdvancesForSyntheticBold(PRUint32 aStart, PRUint32 aLength) { const PRUint32 appUnitsPerDevUnit = GetAppUnitsPerDevUnit(); PRBool isRTL = IsRightToLeft(); GlyphRunIterator iter(this, aStart, aLength); while (iter.NextRun()) { gfxFont *font = iter.GetGlyphRun()->mFont; if (font->IsSyntheticBold()) { PRUint32 synAppUnitOffset = font->GetSyntheticBoldOffset() * appUnitsPerDevUnit; PRUint32 start = iter.GetStringStart(); PRUint32 end = iter.GetStringEnd(); PRUint32 i; // iterate over glyphs, start to end for (i = start; i < end; ++i) { gfxTextRun::CompressedGlyph *glyphData = &mCharacterGlyphs[i]; if (glyphData->IsSimpleGlyph()) { // simple glyphs ==> just add the advance PRUint32 advance = glyphData->GetSimpleAdvance() + synAppUnitOffset; if (CompressedGlyph::IsSimpleAdvance(advance)) { glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph()); } else { // rare case, tested by making this the default PRUint32 glyphIndex = glyphData->GetSimpleGlyph(); glyphData->SetComplex(PR_TRUE, PR_TRUE, 1); DetailedGlyph detail = {glyphIndex, advance, 0, 0}; SetGlyphs(i, *glyphData, &detail); } } else { // complex glyphs ==> add offset at cluster/ligature boundaries PRUint32 detailedLength = glyphData->GetGlyphCount(); if (detailedLength) { gfxTextRun::DetailedGlyph *details = GetDetailedGlyphs(i); if (!details) { continue; } if (isRTL) { details[0].mAdvance += synAppUnitOffset; } else { details[detailedLength - 1].mAdvance += synAppUnitOffset; } } } } } } } void gfxTextRun::Draw(gfxContext *aContext, gfxPoint aPt, PRUint32 aStart, PRUint32 aLength, PropertyProvider *aProvider, gfxFloat *aAdvanceWidth) { NS_ASSERTION(aStart + aLength <= mCharacterCount, "Substring out of range"); gfxFloat direction = GetDirection(); if (mSkipDrawing) { // We're waiting for a user font to finish downloading; // but if the caller wants advance width, we need to compute it here if (aAdvanceWidth) { gfxTextRun::Metrics metrics = MeasureText(aStart, aLength, gfxFont::LOOSE_INK_EXTENTS, aContext, aProvider); *aAdvanceWidth = metrics.mAdvanceWidth * direction; } // return without drawing return; } gfxPoint pt = aPt; // synthetic bolding draws glyphs twice ==> colors with opacity won't draw correctly unless first drawn without alpha BufferAlphaColor syntheticBoldBuffer(aContext); gfxRGBA currentColor; PRBool needToRestore = PR_FALSE; if (HasNonOpaqueColor(aContext, currentColor) && HasSyntheticBold(this, aStart, aLength)) { needToRestore = PR_TRUE; // measure text, use the bounding box gfxTextRun::Metrics metrics = MeasureText(aStart, aLength, gfxFont::LOOSE_INK_EXTENTS, aContext, aProvider); metrics.mBoundingBox.MoveBy(aPt); syntheticBoldBuffer.PushSolidColor(metrics.mBoundingBox, currentColor, GetAppUnitsPerDevUnit()); } GlyphRunIterator iter(this, aStart, aLength); while (iter.NextRun()) { gfxFont *font = iter.GetGlyphRun()->mFont; PRUint32 start = iter.GetStringStart(); PRUint32 end = iter.GetStringEnd(); PRUint32 ligatureRunStart = start; PRUint32 ligatureRunEnd = end; ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); DrawPartialLigature(font, aContext, start, ligatureRunStart, &pt, aProvider); DrawGlyphs(font, aContext, PR_FALSE, &pt, ligatureRunStart, ligatureRunEnd, aProvider, ligatureRunStart, ligatureRunEnd); DrawPartialLigature(font, aContext, ligatureRunEnd, end, &pt, aProvider); } // composite result when synthetic bolding used if (needToRestore) { syntheticBoldBuffer.PopAlpha(); } if (aAdvanceWidth) { *aAdvanceWidth = (pt.x - aPt.x)*direction; } } void gfxTextRun::DrawToPath(gfxContext *aContext, gfxPoint aPt, PRUint32 aStart, PRUint32 aLength, PropertyProvider *aProvider, gfxFloat *aAdvanceWidth) { NS_ASSERTION(aStart + aLength <= mCharacterCount, "Substring out of range"); gfxFloat direction = GetDirection(); gfxPoint pt = aPt; GlyphRunIterator iter(this, aStart, aLength); while (iter.NextRun()) { gfxFont *font = iter.GetGlyphRun()->mFont; PRUint32 start = iter.GetStringStart(); PRUint32 end = iter.GetStringEnd(); PRUint32 ligatureRunStart = start; PRUint32 ligatureRunEnd = end; ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); NS_ASSERTION(ligatureRunStart == start, "Can't draw path starting inside ligature"); NS_ASSERTION(ligatureRunEnd == end, "Can't end drawing path inside ligature"); DrawGlyphs(font, aContext, PR_TRUE, &pt, ligatureRunStart, ligatureRunEnd, aProvider, ligatureRunStart, ligatureRunEnd); } if (aAdvanceWidth) { *aAdvanceWidth = (pt.x - aPt.x)*direction; } } void gfxTextRun::AccumulateMetricsForRun(gfxFont *aFont, PRUint32 aStart, PRUint32 aEnd, gfxFont::BoundingBoxType aBoundingBoxType, gfxContext *aRefContext, PropertyProvider *aProvider, PRUint32 aSpacingStart, PRUint32 aSpacingEnd, Metrics *aMetrics) { nsAutoTArray spacingBuffer; PRBool haveSpacing = GetAdjustedSpacingArray(aStart, aEnd, aProvider, aSpacingStart, aSpacingEnd, &spacingBuffer); Metrics metrics = aFont->Measure(this, aStart, aEnd, aBoundingBoxType, aRefContext, haveSpacing ? spacingBuffer.Elements() : nsnull); aMetrics->CombineWith(metrics, IsRightToLeft()); } void gfxTextRun::AccumulatePartialLigatureMetrics(gfxFont *aFont, PRUint32 aStart, PRUint32 aEnd, gfxFont::BoundingBoxType aBoundingBoxType, gfxContext *aRefContext, PropertyProvider *aProvider, Metrics *aMetrics) { if (aStart >= aEnd) return; // Measure partial ligature. We hack this by clipping the metrics in the // same way we clip the drawing. LigatureData data = ComputeLigatureData(aStart, aEnd, aProvider); // First measure the complete ligature Metrics metrics; AccumulateMetricsForRun(aFont, data.mLigatureStart, data.mLigatureEnd, aBoundingBoxType, aRefContext, aProvider, aStart, aEnd, &metrics); // Clip the bounding box to the ligature part gfxFloat bboxLeft = metrics.mBoundingBox.X(); gfxFloat bboxRight = metrics.mBoundingBox.XMost(); // Where we are going to start "drawing" relative to our left baseline origin gfxFloat origin = IsRightToLeft() ? metrics.mAdvanceWidth - data.mPartAdvance : 0; ClipPartialLigature(this, &bboxLeft, &bboxRight, origin, &data); metrics.mBoundingBox.x = bboxLeft; metrics.mBoundingBox.width = bboxRight - bboxLeft; // mBoundingBox is now relative to the left baseline origin for the entire // ligature. Shift it left. metrics.mBoundingBox.x -= IsRightToLeft() ? metrics.mAdvanceWidth - (data.mPartAdvance + data.mPartWidth) : data.mPartAdvance; metrics.mAdvanceWidth = data.mPartWidth; aMetrics->CombineWith(metrics, IsRightToLeft()); } gfxTextRun::Metrics gfxTextRun::MeasureText(PRUint32 aStart, PRUint32 aLength, gfxFont::BoundingBoxType aBoundingBoxType, gfxContext *aRefContext, PropertyProvider *aProvider) { NS_ASSERTION(aStart + aLength <= mCharacterCount, "Substring out of range"); Metrics accumulatedMetrics; GlyphRunIterator iter(this, aStart, aLength); while (iter.NextRun()) { gfxFont *font = iter.GetGlyphRun()->mFont; PRUint32 start = iter.GetStringStart(); PRUint32 end = iter.GetStringEnd(); PRUint32 ligatureRunStart = start; PRUint32 ligatureRunEnd = end; ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); AccumulatePartialLigatureMetrics(font, start, ligatureRunStart, aBoundingBoxType, aRefContext, aProvider, &accumulatedMetrics); // XXX This sucks. We have to get glyph extents just so we can detect // glyphs outside the font box, even when aBoundingBoxType is LOOSE, // even though in almost all cases we could get correct results just // by getting some ascent/descent from the font and using our stored // advance widths. AccumulateMetricsForRun(font, ligatureRunStart, ligatureRunEnd, aBoundingBoxType, aRefContext, aProvider, ligatureRunStart, ligatureRunEnd, &accumulatedMetrics); AccumulatePartialLigatureMetrics(font, ligatureRunEnd, end, aBoundingBoxType, aRefContext, aProvider, &accumulatedMetrics); } return accumulatedMetrics; } #define MEASUREMENT_BUFFER_SIZE 100 PRUint32 gfxTextRun::BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength, PRBool aLineBreakBefore, gfxFloat aWidth, PropertyProvider *aProvider, PRBool aSuppressInitialBreak, gfxFloat *aTrimWhitespace, Metrics *aMetrics, gfxFont::BoundingBoxType aBoundingBoxType, gfxContext *aRefContext, PRBool *aUsedHyphenation, PRUint32 *aLastBreak, PRBool aCanWordWrap, gfxBreakPriority *aBreakPriority) { aMaxLength = PR_MIN(aMaxLength, mCharacterCount - aStart); NS_ASSERTION(aStart + aMaxLength <= mCharacterCount, "Substring out of range"); PRUint32 bufferStart = aStart; PRUint32 bufferLength = PR_MIN(aMaxLength, MEASUREMENT_BUFFER_SIZE); PropertyProvider::Spacing spacingBuffer[MEASUREMENT_BUFFER_SIZE]; PRBool haveSpacing = aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING) != 0; if (haveSpacing) { GetAdjustedSpacing(this, bufferStart, bufferStart + bufferLength, aProvider, spacingBuffer); } PRPackedBool hyphenBuffer[MEASUREMENT_BUFFER_SIZE]; PRBool haveHyphenation = aProvider && (aProvider->GetHyphensOption() == NS_STYLE_HYPHENS_AUTO || (aProvider->GetHyphensOption() == NS_STYLE_HYPHENS_MANUAL && (mFlags & gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS) != 0)); if (haveHyphenation) { aProvider->GetHyphenationBreaks(bufferStart, bufferLength, hyphenBuffer); } gfxFloat width = 0; gfxFloat advance = 0; // The number of space characters that can be trimmed PRUint32 trimmableChars = 0; // The amount of space removed by ignoring trimmableChars gfxFloat trimmableAdvance = 0; PRInt32 lastBreak = -1; PRInt32 lastBreakTrimmableChars = -1; gfxFloat lastBreakTrimmableAdvance = -1; PRBool aborted = PR_FALSE; PRUint32 end = aStart + aMaxLength; PRBool lastBreakUsedHyphenation = PR_FALSE; PRUint32 ligatureRunStart = aStart; PRUint32 ligatureRunEnd = end; ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); PRUint32 i; for (i = aStart; i < end; ++i) { if (i >= bufferStart + bufferLength) { // Fetch more spacing and hyphenation data bufferStart = i; bufferLength = PR_MIN(aStart + aMaxLength, i + MEASUREMENT_BUFFER_SIZE) - i; if (haveSpacing) { GetAdjustedSpacing(this, bufferStart, bufferStart + bufferLength, aProvider, spacingBuffer); } if (haveHyphenation) { aProvider->GetHyphenationBreaks(bufferStart, bufferLength, hyphenBuffer); } } // There can't be a word-wrap break opportunity at the beginning of the // line: if the width is too small for even one character to fit, it // could be the first and last break opportunity on the line, and that // would trigger an infinite loop. if (!aSuppressInitialBreak || i > aStart) { PRBool lineBreakHere = mCharacterGlyphs[i].CanBreakBefore() == 1; PRBool hyphenation = haveHyphenation && hyphenBuffer[i - bufferStart]; PRBool wordWrapping = aCanWordWrap && *aBreakPriority <= eWordWrapBreak; if (lineBreakHere || hyphenation || wordWrapping) { gfxFloat hyphenatedAdvance = advance; if (!lineBreakHere && !wordWrapping) { hyphenatedAdvance += aProvider->GetHyphenWidth(); } if (lastBreak < 0 || width + hyphenatedAdvance - trimmableAdvance <= aWidth) { // We can break here. lastBreak = i; lastBreakTrimmableChars = trimmableChars; lastBreakTrimmableAdvance = trimmableAdvance; lastBreakUsedHyphenation = !lineBreakHere && !wordWrapping; *aBreakPriority = hyphenation || lineBreakHere ? eNormalBreak : eWordWrapBreak; } width += advance; advance = 0; if (width - trimmableAdvance > aWidth) { // No more text fits. Abort aborted = PR_TRUE; break; } } } gfxFloat charAdvance; if (i >= ligatureRunStart && i < ligatureRunEnd) { charAdvance = GetAdvanceForGlyphs(i, i + 1); if (haveSpacing) { PropertyProvider::Spacing *space = &spacingBuffer[i - bufferStart]; charAdvance += space->mBefore + space->mAfter; } } else { charAdvance = ComputePartialLigatureWidth(i, i + 1, aProvider); } advance += charAdvance; if (aTrimWhitespace) { if (GetChar(i) == ' ') { ++trimmableChars; trimmableAdvance += charAdvance; } else { trimmableAdvance = 0; trimmableChars = 0; } } } if (!aborted) { width += advance; } // There are three possibilities: // 1) all the text fit (width <= aWidth) // 2) some of the text fit up to a break opportunity (width > aWidth && lastBreak >= 0) // 3) none of the text fits before a break opportunity (width > aWidth && lastBreak < 0) PRUint32 charsFit; PRBool usedHyphenation = PR_FALSE; if (width - trimmableAdvance <= aWidth) { charsFit = aMaxLength; } else if (lastBreak >= 0) { charsFit = lastBreak - aStart; trimmableChars = lastBreakTrimmableChars; trimmableAdvance = lastBreakTrimmableAdvance; usedHyphenation = lastBreakUsedHyphenation; } else { charsFit = aMaxLength; } if (aMetrics) { *aMetrics = MeasureText(aStart, charsFit - trimmableChars, aBoundingBoxType, aRefContext, aProvider); } if (aTrimWhitespace) { *aTrimWhitespace = trimmableAdvance; } if (aUsedHyphenation) { *aUsedHyphenation = usedHyphenation; } if (aLastBreak && charsFit == aMaxLength) { if (lastBreak < 0) { *aLastBreak = PR_UINT32_MAX; } else { *aLastBreak = lastBreak - aStart; } } return charsFit; } gfxFloat gfxTextRun::GetAdvanceWidth(PRUint32 aStart, PRUint32 aLength, PropertyProvider *aProvider) { NS_ASSERTION(aStart + aLength <= mCharacterCount, "Substring out of range"); PRUint32 ligatureRunStart = aStart; PRUint32 ligatureRunEnd = aStart + aLength; ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); gfxFloat result = ComputePartialLigatureWidth(aStart, ligatureRunStart, aProvider) + ComputePartialLigatureWidth(ligatureRunEnd, aStart + aLength, aProvider); // Account for all remaining spacing here. This is more efficient than // processing it along with the glyphs. if (aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) { PRUint32 i; nsAutoTArray spacingBuffer; if (spacingBuffer.AppendElements(aLength)) { GetAdjustedSpacing(this, ligatureRunStart, ligatureRunEnd, aProvider, spacingBuffer.Elements()); for (i = 0; i < ligatureRunEnd - ligatureRunStart; ++i) { PropertyProvider::Spacing *space = &spacingBuffer[i]; result += space->mBefore + space->mAfter; } } } return result + GetAdvanceForGlyphs(ligatureRunStart, ligatureRunEnd); } PRBool gfxTextRun::SetLineBreaks(PRUint32 aStart, PRUint32 aLength, PRBool aLineBreakBefore, PRBool aLineBreakAfter, gfxFloat *aAdvanceWidthDelta, gfxContext *aRefContext) { // Do nothing because our shaping does not currently take linebreaks into // account. There is no change in advance width. if (aAdvanceWidthDelta) { *aAdvanceWidthDelta = 0; } return PR_FALSE; } PRUint32 gfxTextRun::FindFirstGlyphRunContaining(PRUint32 aOffset) { NS_ASSERTION(aOffset <= mCharacterCount, "Bad offset looking for glyphrun"); NS_ASSERTION(mCharacterCount == 0 || mGlyphRuns.Length() > 0, "non-empty text but no glyph runs present!"); if (aOffset == mCharacterCount) return mGlyphRuns.Length(); PRUint32 start = 0; PRUint32 end = mGlyphRuns.Length(); while (end - start > 1) { PRUint32 mid = (start + end)/2; if (mGlyphRuns[mid].mCharacterOffset <= aOffset) { start = mid; } else { end = mid; } } NS_ASSERTION(mGlyphRuns[start].mCharacterOffset <= aOffset, "Hmm, something went wrong, aOffset should have been found"); return start; } nsresult gfxTextRun::AddGlyphRun(gfxFont *aFont, PRUint8 aMatchType, PRUint32 aUTF16Offset, PRBool aForceNewRun) { PRUint32 numGlyphRuns = mGlyphRuns.Length(); if (!aForceNewRun && numGlyphRuns > 0) { GlyphRun *lastGlyphRun = &mGlyphRuns[numGlyphRuns - 1]; NS_ASSERTION(lastGlyphRun->mCharacterOffset <= aUTF16Offset, "Glyph runs out of order (and run not forced)"); // Don't append a run if the font is already the one we want if (lastGlyphRun->mFont == aFont && lastGlyphRun->mMatchType == aMatchType) { return NS_OK; } // If the offset has not changed, avoid leaving a zero-length run // by overwriting the last entry instead of appending... if (lastGlyphRun->mCharacterOffset == aUTF16Offset) { // ...except that if the run before the last entry had the same // font as the new one wants, merge with it instead of creating // adjacent runs with the same font if (numGlyphRuns > 1 && mGlyphRuns[numGlyphRuns - 2].mFont == aFont && mGlyphRuns[numGlyphRuns - 2].mMatchType == aMatchType) { mGlyphRuns.TruncateLength(numGlyphRuns - 1); return NS_OK; } lastGlyphRun->mFont = aFont; lastGlyphRun->mMatchType = aMatchType; return NS_OK; } } NS_ASSERTION(aForceNewRun || numGlyphRuns > 0 || aUTF16Offset == 0, "First run doesn't cover the first character (and run not forced)?"); GlyphRun *glyphRun = mGlyphRuns.AppendElement(); if (!glyphRun) return NS_ERROR_OUT_OF_MEMORY; glyphRun->mFont = aFont; glyphRun->mCharacterOffset = aUTF16Offset; glyphRun->mMatchType = aMatchType; return NS_OK; } void gfxTextRun::SortGlyphRuns() { if (mGlyphRuns.Length() <= 1) return; nsTArray runs(mGlyphRuns); GlyphRunOffsetComparator comp; runs.Sort(comp); // Now copy back, coalescing adjacent glyph runs that have the same font mGlyphRuns.Clear(); PRUint32 i; for (i = 0; i < runs.Length(); ++i) { // a GlyphRun with the same font as the previous GlyphRun can just // be skipped; the last GlyphRun will cover its character range. if (i == 0 || runs[i].mFont != runs[i - 1].mFont) { mGlyphRuns.AppendElement(runs[i]); // If two fonts have the same character offset, Sort() will have // randomized the order. NS_ASSERTION(i == 0 || runs[i].mCharacterOffset != runs[i - 1].mCharacterOffset, "Two fonts for the same run, glyph indices may not match the font"); } } } void gfxTextRun::SanitizeGlyphRuns() { if (mGlyphRuns.Length() <= 1) return; // If any glyph run starts with ligature-continuation characters, we need to advance it // to the first "real" character to avoid drawing partial ligature glyphs from wrong font // (seen with U+FEFF in reftest 474417-1, as Core Text eliminates the glyph, which makes // it appear as if a ligature has been formed) PRInt32 i, lastRunIndex = mGlyphRuns.Length() - 1; for (i = lastRunIndex; i >= 0; --i) { GlyphRun& run = mGlyphRuns[i]; while (mCharacterGlyphs[run.mCharacterOffset].IsLigatureContinuation() && run.mCharacterOffset < mCharacterCount) { run.mCharacterOffset++; } // if the run has become empty, eliminate it if ((i < lastRunIndex && run.mCharacterOffset >= mGlyphRuns[i+1].mCharacterOffset) || (i == lastRunIndex && run.mCharacterOffset == mCharacterCount)) { mGlyphRuns.RemoveElementAt(i); --lastRunIndex; } } } PRUint32 gfxTextRun::CountMissingGlyphs() { PRUint32 i; PRUint32 count = 0; for (i = 0; i < mCharacterCount; ++i) { if (mCharacterGlyphs[i].IsMissing()) { ++count; } } return count; } gfxTextRun::DetailedGlyph * gfxTextRun::AllocateDetailedGlyphs(PRUint32 aIndex, PRUint32 aCount) { NS_ASSERTION(aIndex < mCharacterCount, "Index out of range"); if (!mCharacterGlyphs) { return nsnull; } if (!mDetailedGlyphs) { mDetailedGlyphs = new DetailedGlyphStore(); } DetailedGlyph *details = mDetailedGlyphs->Allocate(aIndex, aCount); if (!details) { mCharacterGlyphs[aIndex].SetMissing(0); return nsnull; } return details; } void gfxTextRun::SetGlyphs(PRUint32 aIndex, CompressedGlyph aGlyph, const DetailedGlyph *aGlyphs) { NS_ASSERTION(!aGlyph.IsSimpleGlyph(), "Simple glyphs not handled here"); NS_ASSERTION(aIndex > 0 || (aGlyph.IsClusterStart() && aGlyph.IsLigatureGroupStart()), "First character must be the start of a cluster and can't be a ligature continuation!"); PRUint32 glyphCount = aGlyph.GetGlyphCount(); if (glyphCount > 0) { DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, glyphCount); if (!details) return; memcpy(details, aGlyphs, sizeof(DetailedGlyph)*glyphCount); } mCharacterGlyphs[aIndex] = aGlyph; } #include "ignorable.x-ccmap" DEFINE_X_CCMAP(gIgnorableCCMapExt, const); static inline PRBool IsDefaultIgnorable(PRUint32 aChar) { return CCMAP_HAS_CHAR_EXT(gIgnorableCCMapExt, aChar); } void gfxTextRun::SetMissingGlyph(PRUint32 aIndex, PRUint32 aChar) { DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1); if (!details) return; details->mGlyphID = aChar; GlyphRun *glyphRun = &mGlyphRuns[FindFirstGlyphRunContaining(aIndex)]; if (IsDefaultIgnorable(aChar)) { // Setting advance width to zero will prevent drawing the hexbox details->mAdvance = 0; } else { gfxFloat width = PR_MAX(glyphRun->mFont->GetMetrics().aveCharWidth, gfxFontMissingGlyphs::GetDesiredMinWidth(aChar)); details->mAdvance = PRUint32(width*GetAppUnitsPerDevUnit()); } details->mXOffset = 0; details->mYOffset = 0; mCharacterGlyphs[aIndex].SetMissing(1); } PRBool gfxTextRun::FilterIfIgnorable(PRUint32 aIndex) { PRUint32 ch = GetChar(aIndex); if (IsDefaultIgnorable(ch)) { DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1); if (details) { details->mGlyphID = ch; details->mAdvance = 0; details->mXOffset = 0; details->mYOffset = 0; mCharacterGlyphs[aIndex].SetMissing(1); return PR_TRUE; } } return PR_FALSE; } void gfxTextRun::CopyGlyphDataFrom(gfxTextRun *aSource, PRUint32 aStart, PRUint32 aLength, PRUint32 aDest) { NS_ASSERTION(aStart + aLength <= aSource->GetLength(), "Source substring out of range"); NS_ASSERTION(aDest + aLength <= GetLength(), "Destination substring out of range"); if (aSource->mSkipDrawing) { mSkipDrawing = PR_TRUE; } // Copy base glyph data, and DetailedGlyph data where present for (PRUint32 i = 0; i < aLength; ++i) { CompressedGlyph g = aSource->mCharacterGlyphs[i + aStart]; g.SetCanBreakBefore(mCharacterGlyphs[i + aDest].CanBreakBefore()); if (!g.IsSimpleGlyph()) { PRUint32 count = g.GetGlyphCount(); if (count > 0) { DetailedGlyph *dst = AllocateDetailedGlyphs(i + aDest, count); if (dst) { DetailedGlyph *src = aSource->GetDetailedGlyphs(i + aStart); if (src) { ::memcpy(dst, src, count * sizeof(DetailedGlyph)); } else { g.SetMissing(0); } } else { g.SetMissing(0); } } } mCharacterGlyphs[i + aDest] = g; } // Copy glyph runs GlyphRunIterator iter(aSource, aStart, aLength); #ifdef DEBUG gfxFont *lastFont = nsnull; #endif while (iter.NextRun()) { gfxFont *font = iter.GetGlyphRun()->mFont; NS_ASSERTION(font != lastFont, "Glyphruns not coalesced?"); #ifdef DEBUG lastFont = font; PRUint32 end = iter.GetStringEnd(); #endif PRUint32 start = iter.GetStringStart(); // These used to be NS_ASSERTION()s, but WARNING is more appropriate. // Although it's unusual (and not desirable), it's possible for us to assign // different fonts to a base character and a following diacritic. // Example on OSX 10.5/10.6 with default fonts installed: // data:text/html,

// &%23x043E;&%23x0486;&%23x20;&%23x043E;&%23x0486; // This means the rendering of the cluster will probably not be very good, // but it's the best we can do for now if the specified font only covered the // initial base character and not its applied marks. NS_WARN_IF_FALSE(aSource->IsClusterStart(start), "Started font run in the middle of a cluster"); NS_WARN_IF_FALSE(end == aSource->GetLength() || aSource->IsClusterStart(end), "Ended font run in the middle of a cluster"); nsresult rv = AddGlyphRun(font, iter.GetGlyphRun()->mMatchType, start - aStart + aDest, PR_FALSE); 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 textRun; textRun = mFontGroup->MakeTextRun(&space, 1, ¶ms, gfxTextRunFactory::TEXT_IS_8BIT | gfxTextRunFactory::TEXT_IS_ASCII | gfxTextRunFactory::TEXT_IS_PERSISTENT); if (!textRun || !textRun->mCharacterGlyphs) return; CopyGlyphDataFrom(textRun, 0, 1, aCharIndex); return; } AddGlyphRun(aFont, gfxTextRange::kFontGroup, aCharIndex, PR_FALSE); CompressedGlyph g; g.SetSimpleGlyph(spaceWidthAppUnits, spaceGlyph); SetSimpleGlyph(aCharIndex, g); } void gfxTextRun::FetchGlyphExtents(gfxContext *aRefContext) { PRBool needsGlyphExtents = NeedsGlyphExtents(this); if (!needsGlyphExtents && !mDetailedGlyphs) return; PRUint32 i; CompressedGlyph *charGlyphs = mCharacterGlyphs; for (i = 0; i < mGlyphRuns.Length(); ++i) { gfxFont *font = mGlyphRuns[i].mFont; PRUint32 start = mGlyphRuns[i].mCharacterOffset; PRUint32 end = i + 1 < mGlyphRuns.Length() ? mGlyphRuns[i + 1].mCharacterOffset : GetLength(); PRBool fontIsSetup = PR_FALSE; PRUint32 j; gfxGlyphExtents *extents = font->GetOrCreateGlyphExtents(mAppUnitsPerDevUnit); for (j = start; j < end; ++j) { const gfxTextRun::CompressedGlyph *glyphData = &charGlyphs[j]; if (glyphData->IsSimpleGlyph()) { // If we're in speed mode, don't set up glyph extents here; we'll // just return "optimistic" glyph bounds later if (needsGlyphExtents) { PRUint32 glyphIndex = glyphData->GetSimpleGlyph(); if (!extents->IsGlyphKnown(glyphIndex)) { if (!fontIsSetup) { font->SetupCairoFont(aRefContext); fontIsSetup = PR_TRUE; } #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS ++gGlyphExtentsSetupEagerSimple; #endif font->SetupGlyphExtents(aRefContext, glyphIndex, PR_FALSE, extents); } } } else if (!glyphData->IsMissing()) { PRUint32 glyphCount = glyphData->GetGlyphCount(); if (glyphCount == 0) { continue; } const gfxTextRun::DetailedGlyph *details = GetDetailedGlyphs(j); if (!details) { continue; } for (PRUint32 k = 0; k < glyphCount; ++k, ++details) { PRUint32 glyphIndex = details->mGlyphID; if (!extents->IsGlyphKnownWithTightExtents(glyphIndex)) { if (!fontIsSetup) { font->SetupCairoFont(aRefContext); fontIsSetup = PR_TRUE; } #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS ++gGlyphExtentsSetupEagerTight; #endif font->SetupGlyphExtents(aRefContext, glyphIndex, PR_TRUE, extents); } } } } } } gfxTextRun::ClusterIterator::ClusterIterator(gfxTextRun *aTextRun) : mTextRun(aTextRun), mCurrentChar(PRUint32(-1)) { } void gfxTextRun::ClusterIterator::Reset() { mCurrentChar = PRUint32(-1); } PRBool gfxTextRun::ClusterIterator::NextCluster() { while (++mCurrentChar < mTextRun->GetLength()) { if (mTextRun->IsClusterStart(mCurrentChar)) { return PR_TRUE; } } mCurrentChar = PRUint32(-1); return PR_FALSE; } PRUint32 gfxTextRun::ClusterIterator::ClusterLength() const { if (mCurrentChar == PRUint32(-1)) { return 0; } PRUint32 i = mCurrentChar; while (++i < mTextRun->GetLength()) { if (mTextRun->IsClusterStart(i)) { break; } } return i - mCurrentChar; } gfxFloat gfxTextRun::ClusterIterator::ClusterAdvance(PropertyProvider *aProvider) const { if (mCurrentChar == PRUint32(-1)) { return 0; } return mTextRun->GetAdvanceWidth(mCurrentChar, ClusterLength(), aProvider); } #ifdef DEBUG void gfxTextRun::Dump(FILE* aOutput) { if (!aOutput) { aOutput = stdout; } PRUint32 i; fputc('"', aOutput); for (i = 0; i < mCharacterCount; ++i) { PRUnichar ch = GetChar(i); if (ch >= 32 && ch < 128) { fputc(ch, aOutput); } else { fprintf(aOutput, "\\u%4x", ch); } } fputs("\" [", aOutput); for (i = 0; i < mGlyphRuns.Length(); ++i) { if (i > 0) { fputc(',', aOutput); } gfxFont* font = mGlyphRuns[i].mFont; const gfxFontStyle* style = font->GetStyle(); NS_ConvertUTF16toUTF8 fontName(font->GetName()); nsCAutoString lang; style->language->ToUTF8String(lang); fprintf(aOutput, "%d: %s %f/%d/%d/%s", mGlyphRuns[i].mCharacterOffset, fontName.get(), style->size, style->weight, style->style, lang.get()); } fputc(']', aOutput); } #endif