gecko/gfx/thebes/gfxPlatformFontList.cpp

562 lines
19 KiB
C++

/* -*- 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 Corporation code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2006-2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Jonathan Kew <jfkthame@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* Based in part on sample code provided by Apple Computer, Inc.,
* under the following license:
*
* Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ***** END LICENSE BLOCK ***** */
#include "gfxPlatformFontList.h"
#include "gfxTextRunWordCache.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch2.h" // for pref changes callback notification
#include "nsServiceManagerUtils.h"
#include "nsUnicharUtils.h"
// font info loader constants
static const PRUint32 kDelayBeforeLoadingCmaps = 8 * 1000; // 8secs
static const PRUint32 kIntervalBetweenLoadingCmaps = 150; // 150ms
static const PRUint32 kNumFontsPerSlice = 10; // read in info 10 fonts at a time
static PRLogModuleInfo *gFontListLog = PR_NewLogModule("fontListLog");
gfxPlatformFontList *gfxPlatformFontList::sPlatformFontList = nsnull;
class gfxFontListPrefObserver : public nsIObserver {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
};
NS_IMPL_ISUPPORTS1(gfxFontListPrefObserver, nsIObserver)
NS_IMETHODIMP
gfxFontListPrefObserver::Observe(nsISupports *aSubject,
const char *aTopic,
const PRUnichar *aData)
{
NS_ASSERTION(!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID), "invalid topic");
// XXX this could be made to only clear out the cache for the prefs that were changed
// but it probably isn't that big a deal.
gfxPlatformFontList::PlatformFontList()->ClearPrefFonts();
gfxFontCache::GetCache()->AgeAllGenerations();
return NS_OK;
}
gfxPlatformFontList::gfxPlatformFontList(PRBool aNeedFullnamePostscriptNames)
: mNeedFullnamePostscriptNames(aNeedFullnamePostscriptNames),
mStartIndex(0), mIncrement(kNumFontsPerSlice), mNumFamilies(0)
{
mFontFamilies.Init(100);
mOtherFamilyNames.Init(30);
mOtherFamilyNamesInitialized = PR_FALSE;
if (mNeedFullnamePostscriptNames) {
mFullnames.Init(100);
mPostscriptNames.Init(100);
}
mFaceNamesInitialized = PR_FALSE;
mPrefFonts.Init(10);
mBadUnderlineFamilyNames.Init(10);
LoadBadUnderlineList();
// pref changes notification setup
gfxFontListPrefObserver *observer = new gfxFontListPrefObserver();
if (observer) {
nsCOMPtr<nsIPrefBranch2> pref = do_GetService(NS_PREFSERVICE_CONTRACTID);
if (pref) {
pref->AddObserver("font.", observer, PR_FALSE);
pref->AddObserver("font.name-list.", observer, PR_FALSE);
pref->AddObserver("intl.accept_languages", observer, PR_FALSE); // hmmmm...
} else {
delete observer;
}
}
}
nsresult
gfxPlatformFontList::InitFontList()
{
mFontFamilies.Clear();
mOtherFamilyNames.Clear();
mOtherFamilyNamesInitialized = PR_FALSE;
if (mNeedFullnamePostscriptNames) {
mFullnames.Clear();
mPostscriptNames.Clear();
}
mFaceNamesInitialized = PR_FALSE;
mPrefFonts.Clear();
CancelLoader();
// initialize ranges of characters for which system-wide font search should be skipped
mCodepointsWithNoFonts.reset();
mCodepointsWithNoFonts.SetRange(0,0x1f); // C0 controls
mCodepointsWithNoFonts.SetRange(0x7f,0x9f); // C1 controls
sPlatformFontList = this;
return NS_OK;
}
void
gfxPlatformFontList::GenerateFontListKey(const nsAString& aKeyName, nsAString& aResult)
{
aResult = aKeyName;
ToLowerCase(aResult);
}
void
gfxPlatformFontList::InitOtherFamilyNames()
{
mOtherFamilyNamesInitialized = PR_TRUE;
// iterate over all font families and read in other family names
mFontFamilies.Enumerate(gfxPlatformFontList::InitOtherFamilyNamesProc, this);
}
PLDHashOperator PR_CALLBACK
gfxPlatformFontList::InitOtherFamilyNamesProc(nsStringHashKey::KeyType aKey,
nsRefPtr<gfxFontFamily>& aFamilyEntry,
void* userArg)
{
gfxPlatformFontList *fc = static_cast<gfxPlatformFontList*>(userArg);
aFamilyEntry->ReadOtherFamilyNames(fc);
return PL_DHASH_NEXT;
}
void
gfxPlatformFontList::InitFaceNameLists()
{
mFaceNamesInitialized = PR_TRUE;
// iterate over all font families and read in other family names
mFontFamilies.Enumerate(gfxPlatformFontList::InitFaceNameListsProc, this);
}
PLDHashOperator PR_CALLBACK
gfxPlatformFontList::InitFaceNameListsProc(nsStringHashKey::KeyType aKey,
nsRefPtr<gfxFontFamily>& aFamilyEntry,
void* userArg)
{
gfxPlatformFontList *fc = static_cast<gfxPlatformFontList*>(userArg);
aFamilyEntry->ReadFaceNames(fc, fc->NeedFullnamePostscriptNames());
return PL_DHASH_NEXT;
}
void
gfxPlatformFontList::PreloadNamesList()
{
nsAutoTArray<nsString, 10> preloadFonts;
gfxFontUtils::GetPrefsFontList("font.preload-names-list", preloadFonts);
PRUint32 numFonts = preloadFonts.Length();
for (PRUint32 i = 0; i < numFonts; i++) {
PRBool found;
nsAutoString key;
GenerateFontListKey(preloadFonts[i], key);
// only search canonical names!
gfxFontFamily *familyEntry = mFontFamilies.GetWeak(key, &found);
if (familyEntry) {
familyEntry->ReadOtherFamilyNames(this);
}
}
}
void
gfxPlatformFontList::SetFixedPitch(const nsAString& aFamilyName)
{
gfxFontFamily *family = FindFamily(aFamilyName);
if (!family) return;
family->FindStyleVariations();
nsTArray<nsRefPtr<gfxFontEntry> >& fontlist = family->GetFontList();
PRUint32 i, numFonts = fontlist.Length();
for (i = 0; i < numFonts; i++) {
fontlist[i]->mFixedPitch = 1;
}
}
void
gfxPlatformFontList::LoadBadUnderlineList()
{
nsAutoTArray<nsString, 10> blacklist;
gfxFontUtils::GetPrefsFontList("font.blacklist.underline_offset", blacklist);
PRUint32 numFonts = blacklist.Length();
for (PRUint32 i = 0; i < numFonts; i++) {
nsAutoString key;
GenerateFontListKey(blacklist[i], key);
mBadUnderlineFamilyNames.Put(key);
}
}
PRBool
gfxPlatformFontList::ResolveFontName(const nsAString& aFontName, nsAString& aResolvedFontName)
{
gfxFontFamily *family = FindFamily(aFontName);
if (family) {
aResolvedFontName = family->Name();
return PR_TRUE;
}
return PR_FALSE;
}
struct FontListData {
FontListData(nsIAtom *aLangGroup,
const nsACString& aGenericFamily,
nsTArray<nsString>& aListOfFonts) :
mLangGroup(aLangGroup), mGenericFamily(aGenericFamily),
mListOfFonts(aListOfFonts) {}
nsIAtom *mLangGroup;
const nsACString& mGenericFamily;
nsTArray<nsString>& mListOfFonts;
};
PLDHashOperator PR_CALLBACK
gfxPlatformFontList::HashEnumFuncForFamilies(nsStringHashKey::KeyType aKey,
nsRefPtr<gfxFontFamily>& aFamilyEntry,
void *aUserArg)
{
FontListData *data = static_cast<FontListData*>(aUserArg);
// use the first variation for now. This data should be the same
// for all the variations and should probably be moved up to
// the Family
gfxFontStyle style;
style.language = data->mLangGroup;
PRBool needsBold;
nsRefPtr<gfxFontEntry> aFontEntry = aFamilyEntry->FindFontForStyle(style, needsBold);
NS_ASSERTION(aFontEntry, "couldn't find any font entry in family");
if (!aFontEntry)
return PL_DHASH_NEXT;
/* skip symbol fonts */
if (aFontEntry->IsSymbolFont())
return PL_DHASH_NEXT;
if (aFontEntry->SupportsLangGroup(data->mLangGroup) &&
aFontEntry->MatchesGenericFamily(data->mGenericFamily)) {
nsAutoString localizedFamilyName;
aFamilyEntry->LocalizedName(localizedFamilyName);
data->mListOfFonts.AppendElement(localizedFamilyName);
}
return PL_DHASH_NEXT;
}
void
gfxPlatformFontList::GetFontList(nsIAtom *aLangGroup,
const nsACString& aGenericFamily,
nsTArray<nsString>& aListOfFonts)
{
FontListData data(aLangGroup, aGenericFamily, aListOfFonts);
mFontFamilies.Enumerate(gfxPlatformFontList::HashEnumFuncForFamilies, &data);
aListOfFonts.Sort();
aListOfFonts.Compact();
}
struct FontFamilyListData {
FontFamilyListData(nsTArray<nsRefPtr<gfxFontFamily> >& aFamilyArray)
: mFamilyArray(aFamilyArray)
{}
static PLDHashOperator PR_CALLBACK AppendFamily(nsStringHashKey::KeyType aKey,
nsRefPtr<gfxFontFamily>& aFamilyEntry,
void *aUserArg)
{
FontFamilyListData *data = static_cast<FontFamilyListData*>(aUserArg);
data->mFamilyArray.AppendElement(aFamilyEntry);
return PL_DHASH_NEXT;
}
nsTArray<nsRefPtr<gfxFontFamily> >& mFamilyArray;
};
void
gfxPlatformFontList::GetFontFamilyList(nsTArray<nsRefPtr<gfxFontFamily> >& aFamilyArray)
{
FontFamilyListData data(aFamilyArray);
mFontFamilies.Enumerate(FontFamilyListData::AppendFamily, &data);
}
gfxFontEntry*
gfxPlatformFontList::FindFontForChar(const PRUint32 aCh, gfxFont *aPrevFont)
{
// is codepoint with no matching font? return null immediately
if (mCodepointsWithNoFonts.test(aCh)) {
return nsnull;
}
// TODO: optimize fallback e.g. by caching lists of fonts to try for a given
// unicode range or script
// try to short-circuit font fallback for U+FFFD, used to represent encoding errors:
// just use a platform-specific fallback system font that is guaranteed (or at least
// highly likely) to be around, or a cached family from last time U+FFFD was seen.
// this helps speed up pages with lots of encoding errors, binary-as-text, etc.
if (aCh == 0xFFFD && mReplacementCharFallbackFamily.Length() > 0) {
gfxFontEntry* fontEntry = nsnull;
PRBool needsBold; // ignored in the system fallback case
if (aPrevFont) {
fontEntry = FindFontForFamily(mReplacementCharFallbackFamily, aPrevFont->GetStyle(), needsBold);
} else {
gfxFontStyle normalStyle;
fontEntry = FindFontForFamily(mReplacementCharFallbackFamily, &normalStyle, needsBold);
}
if (fontEntry && fontEntry->TestCharacterMap(aCh))
return fontEntry;
}
FontSearch data(aCh, aPrevFont);
// iterate over all font families to find a font that support the character
mFontFamilies.Enumerate(gfxPlatformFontList::FindFontForCharProc, &data);
// no match? add to set of non-matching codepoints
if (!data.mBestMatch) {
mCodepointsWithNoFonts.set(aCh);
} else if (aCh == 0xFFFD) {
mReplacementCharFallbackFamily = data.mBestMatch->FamilyName();
}
return data.mBestMatch;
}
PLDHashOperator PR_CALLBACK
gfxPlatformFontList::FindFontForCharProc(nsStringHashKey::KeyType aKey, nsRefPtr<gfxFontFamily>& aFamilyEntry,
void *userArg)
{
FontSearch *data = static_cast<FontSearch*>(userArg);
// evaluate all fonts in this family for a match
aFamilyEntry->FindFontForChar(data);
return PL_DHASH_NEXT;
}
gfxFontFamily*
gfxPlatformFontList::FindFamily(const nsAString& aFamily)
{
nsAutoString key;
gfxFontFamily *familyEntry;
PRBool found;
GenerateFontListKey(aFamily, key);
// lookup in canonical (i.e. English) family name list
if ((familyEntry = mFontFamilies.GetWeak(key, &found))) {
return familyEntry;
}
// lookup in other family names list (mostly localized names)
if ((familyEntry = mOtherFamilyNames.GetWeak(key, &found))) {
return familyEntry;
}
// name not found and other family names not yet fully initialized so
// initialize the rest of the list and try again. this is done lazily
// since reading name table entries is expensive
if (!mOtherFamilyNamesInitialized) {
InitOtherFamilyNames();
if ((familyEntry = mOtherFamilyNames.GetWeak(key, &found))) {
return familyEntry;
}
}
return nsnull;
}
gfxFontEntry*
gfxPlatformFontList::FindFontForFamily(const nsAString& aFamily, const gfxFontStyle* aStyle, PRBool& aNeedsBold)
{
gfxFontFamily *familyEntry = FindFamily(aFamily);
aNeedsBold = PR_FALSE;
if (familyEntry)
return familyEntry->FindFontForStyle(*aStyle, aNeedsBold);
return nsnull;
}
PRBool
gfxPlatformFontList::GetPrefFontFamilyEntries(eFontPrefLang aLangGroup, nsTArray<nsRefPtr<gfxFontFamily> > *array)
{
return mPrefFonts.Get(PRUint32(aLangGroup), array);
}
void
gfxPlatformFontList::SetPrefFontFamilyEntries(eFontPrefLang aLangGroup, nsTArray<nsRefPtr<gfxFontFamily> >& array)
{
mPrefFonts.Put(PRUint32(aLangGroup), array);
}
void
gfxPlatformFontList::AddOtherFamilyName(gfxFontFamily *aFamilyEntry, nsAString& aOtherFamilyName)
{
nsAutoString key;
PRBool found;
GenerateFontListKey(aOtherFamilyName, key);
if (!mOtherFamilyNames.GetWeak(key, &found)) {
mOtherFamilyNames.Put(key, aFamilyEntry);
PR_LOG(gFontListLog, PR_LOG_DEBUG, ("(fontlist-otherfamily) canonical family: %s, other family: %s\n",
NS_ConvertUTF16toUTF8(aFamilyEntry->Name()).get(),
NS_ConvertUTF16toUTF8(aOtherFamilyName).get()));
if (mBadUnderlineFamilyNames.Contains(key))
aFamilyEntry->SetBadUnderlineFamily();
}
}
void
gfxPlatformFontList::AddFullname(gfxFontEntry *aFontEntry, nsAString& aFullname)
{
PRBool found;
if (!mFullnames.GetWeak(aFullname, &found)) {
mFullnames.Put(aFullname, aFontEntry);
PR_LOG(gFontListLog, PR_LOG_DEBUG, ("(fontlist-fullname) name: %s, fullname: %s\n",
NS_ConvertUTF16toUTF8(aFontEntry->Name()).get(),
NS_ConvertUTF16toUTF8(aFullname).get()));
}
}
void
gfxPlatformFontList::AddPostscriptName(gfxFontEntry *aFontEntry, nsAString& aPostscriptName)
{
PRBool found;
if (!mPostscriptNames.GetWeak(aPostscriptName, &found)) {
mPostscriptNames.Put(aPostscriptName, aFontEntry);
PR_LOG(gFontListLog, PR_LOG_DEBUG, ("(fontlist-postscript) name: %s, psname: %s\n",
NS_ConvertUTF16toUTF8(aFontEntry->Name()).get(),
NS_ConvertUTF16toUTF8(aPostscriptName).get()));
}
}
PRBool
gfxPlatformFontList::GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName)
{
aFamilyName.Truncate();
ResolveFontName(aFontName, aFamilyName);
return !aFamilyName.IsEmpty();
}
void
gfxPlatformFontList::InitLoader()
{
GetFontFamilyList(mFontFamiliesToLoad);
mStartIndex = 0;
mNumFamilies = mFontFamiliesToLoad.Length();
}
PRBool
gfxPlatformFontList::RunLoader()
{
PRUint32 i, endIndex = (mStartIndex + mIncrement < mNumFamilies ? mStartIndex + mIncrement : mNumFamilies);
// for each font family, load in various font info
for (i = mStartIndex; i < endIndex; i++) {
gfxFontFamily* familyEntry = mFontFamiliesToLoad[i];
// find all faces that are members of this family
familyEntry->FindStyleVariations();
if (familyEntry->GetFontList().Length() == 0) {
// failed to load any faces for this family, so discard it
nsAutoString key;
GenerateFontListKey(familyEntry->Name(), key);
mFontFamilies.Remove(key);
continue;
}
// load the cmaps
familyEntry->ReadCMAP();
// read in face names
familyEntry->ReadFaceNames(this, mNeedFullnamePostscriptNames);
// check whether the family can be considered "simple" for style matching
familyEntry->CheckForSimpleFamily();
}
mStartIndex = endIndex;
return (mStartIndex >= mNumFamilies);
}
void
gfxPlatformFontList::FinishLoader()
{
mFontFamiliesToLoad.Clear();
mNumFamilies = 0;
}