gecko/gfx/thebes/src/gfxQuartzFontCache.mm

1244 lines
42 KiB
Plaintext

/* -*- Mode: ObjC; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: BSD
*
* Copyright (C) 2006 Mozilla Corporation. All rights reserved.
*
* Contributor(s):
* Vladimir Vukicevic <vladimir@pobox.com>
* Masayuki Nakano <masayuki@d-toybox.com>
* John Daggett <jdaggett@mozilla.com>
*
* 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 <Carbon.h>
#import <AppKit/AppKit.h>
#include "gfxPlatformMac.h"
#include "gfxQuartzFontCache.h"
#include "gfxAtsuiFonts.h"
#include "nsIPref.h" // for pref changes callback notification
#include "nsServiceManagerUtils.h"
// _atsFontID is private; add it in our new category to NSFont
@interface NSFont (MozillaCategory)
- (ATSUFontID)_atsFontID;
@end
// 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
#define INDEX_FONT_POSTSCRIPT_NAME 0
#define INDEX_FONT_FACE_NAME 1
#define INDEX_FONT_WEIGHT 2
#define INDEX_FONT_TRAITS 3
static const int kAppleMaxWeight = 14;
static const int gAppleWeightToCSSWeight[] = {
0,
1, // 1.
1, // 2. W1, ultralight
2, // 3. W2, extralight
3, // 4. W3, light
4, // 5. W4, semilight
5, // 6. W5, medium
6, // 7.
6, // 8. W6, semibold
7, // 9. W7, bold
8, // 10. W8, extrabold
8, // 11.
9, // 12. W9, ultrabold
9, // 13
9 // 14
};
static void GetStringForNSString(const NSString *aSrc, nsAString& aDist)
{
aDist.SetLength([aSrc length]);
[aSrc getCharacters:aDist.BeginWriting()];
}
static NSString* GetNSStringForString(const nsAString& aSrc)
{
return [NSString stringWithCharacters:aSrc.BeginReading()
length:aSrc.Length()];
}
static PRLogModuleInfo *gFontInfoLog = PR_NewLogModule("fontInfoLog");
void
gfxQuartzFontCache::GenerateFontListKey(const nsAString& aKeyName, nsAString& aResult)
{
aResult = aKeyName;
ToLowerCase(aResult);
}
/* MacOSFontEntry */
#pragma mark-
MacOSFontEntry::MacOSFontEntry(const nsAString& aPostscriptName,
PRInt32 aAppleWeight, PRUint32 aTraits, MacOSFamilyEntry *aFamily)
: gfxFontEntry(aPostscriptName), mTraits(aTraits), mFamily(aFamily), mATSUFontID(0),
mATSUIDInitialized(0)
{
mWeight = gfxQuartzFontCache::AppleWeightToCSSWeight(aAppleWeight);
mItalic = (mTraits & NSItalicFontMask ? 1 : 0);
mFixedPitch = (mTraits & NSFixedPitchFontMask ? 1 : 0);
}
const nsString&
MacOSFontEntry::FamilyName()
{
return mFamily->Name();
}
ATSUFontID MacOSFontEntry::GetFontID()
{
if (!mATSUIDInitialized) {
mATSUIDInitialized = PR_TRUE;
NSString *psname = GetNSStringForString(mName);
NSFont *font = [NSFont fontWithName:psname size:0.0];
if (font) mATSUFontID = [font _atsFontID];
}
return mATSUFontID;
}
// ATSUI requires AAT-enabled fonts to render complex scripts correctly.
// For now, simple clear out the cmap codepoints for fonts that have
// codepoints for complex scripts. (Bug 361986)
enum eComplexScript {
eComplexScriptArabic,
eComplexScriptIndic,
eComplexScriptTibetan
};
struct ScriptRange {
eComplexScript script;
PRUint32 rangeStart;
PRUint32 rangeEnd;
};
const ScriptRange gScriptsThatRequireShaping[] = {
{ eComplexScriptArabic, 0x0600, 0x077F }, // Basic Arabic and Arabic Supplement
{ eComplexScriptIndic, 0x0900, 0x0D7F }, // Indic scripts - Devanagari, Bengali, ..., Malayalam
{ eComplexScriptTibetan, 0x0F00, 0x0FFF } // Tibetan
// Thai seems to be "renderable" without AAT morphing tables
// xxx - Lao, Khmer?
};
nsresult
MacOSFontEntry::ReadCMAP()
{
OSStatus status;
ByteCount size, cmapSize;
if (mCmapInitialized) return NS_OK;
ATSUFontID fontID = GetFontID();
// attempt this once, if errors occur leave a blank cmap
mCmapInitialized = PR_TRUE;
status = ATSFontGetTable(fontID, 'cmap', 0, 0, 0, &size);
cmapSize = size;
//printf( "cmap size: %s %d\n", NS_ConvertUTF16toUTF8(mName).get(), size );
NS_ENSURE_TRUE(status == noErr, NS_ERROR_FAILURE);
nsAutoTArray<PRUint8,16384> buffer;
if (!buffer.AppendElements(size))
return NS_ERROR_OUT_OF_MEMORY;
PRUint8 *cmap = buffer.Elements();
status = ATSFontGetTable(fontID, 'cmap', 0, size, cmap, &size);
NS_ENSURE_TRUE(status == noErr, NS_ERROR_FAILURE);
nsresult rv = NS_ERROR_FAILURE;
PRPackedBool unicodeFont, symbolFont; // currently ignored
rv = gfxFontUtils::ReadCMAP(cmap, size, mCharacterMap, unicodeFont, symbolFont);
// for complex scripts, check for the presence of mort/morx
PRBool checkedForMorphTable = PR_FALSE, hasMorphTable = PR_FALSE;
PRUint32 s, numScripts = sizeof(gScriptsThatRequireShaping) / sizeof(ScriptRange);
for (s = 0; s < numScripts; s++) {
eComplexScript whichScript = gScriptsThatRequireShaping[s].script;
// check to see if the cmap includes complex script codepoints
if (mCharacterMap.TestRange(gScriptsThatRequireShaping[s].rangeStart, gScriptsThatRequireShaping[s].rangeEnd)) {
// check for mort/morx table, if haven't already
if (!checkedForMorphTable) {
status = ATSFontGetTable(fontID, 'morx', 0, 0, 0, &size);
if (status == noErr) {
checkedForMorphTable = PR_TRUE;
hasMorphTable = PR_TRUE;
} else {
// check for a mort table
status = ATSFontGetTable(fontID, 'mort', 0, 0, 0, &size);
checkedForMorphTable = PR_TRUE;
if (status == noErr) {
hasMorphTable = PR_TRUE;
}
}
}
// rude hack - the Chinese STxxx fonts on 10.4 contain morx tables and Arabic glyphs but
// lack the proper info for shaping Arabic, so exclude explicitly, ick
if (whichScript == eComplexScriptArabic && hasMorphTable) {
if (mName.CharAt(0) == 'S' && mName.CharAt(1) == 'T') {
mCharacterMap.ClearRange(gScriptsThatRequireShaping[s].rangeStart, gScriptsThatRequireShaping[s].rangeEnd);
}
}
// general exclusion - if no morph table, exclude codepoints
if (!hasMorphTable) {
mCharacterMap.ClearRange(gScriptsThatRequireShaping[s].rangeStart, gScriptsThatRequireShaping[s].rangeEnd);
}
}
}
PR_LOG(gFontInfoLog, PR_LOG_DEBUG, ("(fontinit-cmap) psname: %s, size: %d\n",
NS_ConvertUTF16toUTF8(mName).get(), mCharacterMap.GetSize()));
return rv;
}
/* MacOSFamilyEntry */
#pragma mark-
// helper class for adding other family names back into font cache
class AddOtherFamilyNameFunctor
{
public:
AddOtherFamilyNameFunctor(gfxQuartzFontCache *aFontCache) :
mFontCache(aFontCache)
{}
void operator() (MacOSFamilyEntry *aFamilyEntry, nsAString& aOtherName) {
mFontCache->AddOtherFamilyName(aFamilyEntry, aOtherName);
}
gfxQuartzFontCache *mFontCache;
};
void MacOSFamilyEntry::LocalizedName(nsAString& aLocalizedName)
{
// no other names ==> only one name, just return it
if (!HasOtherFamilyNames()) {
aLocalizedName = mName;
return;
}
NSFontManager *fontManager = [NSFontManager sharedFontManager];
// dig out the localized family name
NSString *family = GetNSStringForString(mName);
NSString *localizedFamily = [fontManager localizedNameForFamily:family face:nil];
if (localizedFamily) {
GetStringForNSString(localizedFamily, aLocalizedName);
} else {
// failed to get a localized name, just use the canonical name
aLocalizedName = mName;
}
}
PRBool MacOSFamilyEntry::HasOtherFamilyNames()
{
// need to read in other family names to determine this
if (!mOtherFamilyNamesInitialized) {
AddOtherFamilyNameFunctor addOtherNames(gfxQuartzFontCache::SharedFontCache());
ReadOtherFamilyNames(addOtherNames); // sets mHasOtherFamilyNames
}
return mHasOtherFamilyNames;
}
static const PRUint32 kTraits_NonNormalWidthMask = NSNarrowFontMask | NSExpandedFontMask |
NSCondensedFontMask | NSCompressedFontMask | NSFixedPitchFontMask;
MacOSFontEntry*
MacOSFamilyEntry::FindFont(const gfxFontStyle* aStyle, PRBool& aNeedsBold)
{
return static_cast<MacOSFontEntry*> (FindFontForStyle(*aStyle, aNeedsBold));
}
MacOSFontEntry*
MacOSFamilyEntry::FindFont(const nsString& aPostscriptName)
{
// find the font using a simple linear search
PRUint32 numFonts = mAvailableFonts.Length();
for (PRUint32 i = 0; i < numFonts; i++) {
MacOSFontEntry *fe = mAvailableFonts[i];
if (fe->Name() == aPostscriptName)
return fe;
}
return nsnull;
}
void
MacOSFamilyEntry::FindFontForChar(FontSearch *aMatchData)
{
// 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++) {
MacOSFontEntry *fe = mAvailableFonts[i];
PRInt32 rank = 0;
if (fe->TestCharacterMap(aMatchData->ch)) {
rank += 20;
}
// 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->fontToMatch) {
const gfxFontStyle *style = aMatchData->fontToMatch->GetStyle();
// italics
if (fe->IsItalic() &&
(style->style == FONT_STYLE_ITALIC || style->style == FONT_STYLE_ITALIC)) {
rank += 5;
}
// weight
PRInt8 baseWeight, weightDistance;
style->ComputeWeightAndOffset(&baseWeight, &weightDistance);
// xxx - not entirely correct, the one unit of weight distance reflects
// the "next bolder/lighter face"
PRUint32 targetWeight = (baseWeight * 100) + (weightDistance * 100);
PRUint32 entryWeight = fe->Weight() * 100;
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() && !fe->IsBold())
rank += 5;
}
// xxx - add whether AAT font with morphing info for specific lang groups
if (rank > aMatchData->matchRank
|| (rank == aMatchData->matchRank && Compare(fe->Name(), aMatchData->bestMatch->Name()) > 0))
{
aMatchData->bestMatch = fe;
aMatchData->matchRank = rank;
}
}
}
PRBool
MacOSFamilyEntry::FindFontsWithTraits(gfxFontEntry* aFontsForWeights[], PRUint32 aPosTraitsMask,
PRUint32 aNegTraitsMask)
{
PRBool found = PR_FALSE;
// iterate over fonts
PRUint32 numFonts = mAvailableFonts.Length();
for (PRUint32 i = 0; i < numFonts; i++) {
MacOSFontEntry *fe = mAvailableFonts[i];
// if traits match, add to list of fonts
PRUint32 traits = fe->Traits();
// aPosTraitsMask == 0 ==> match all
if ((!aPosTraitsMask || (traits & aPosTraitsMask)) && !(traits & aNegTraitsMask)) {
PRInt32 weight = fe->Weight();
// always prefer the first font for a given weight, helps deal a bit with
// families with lots of faces (e.g. Minion Pro)
if (!aFontsForWeights[weight]) {
aFontsForWeights[weight] = fe;
found = PR_TRUE;
}
}
}
return found;
}
PRBool
MacOSFamilyEntry::FindWeightsForStyle(gfxFontEntry* aFontsForWeights[], const gfxFontStyle& aFontStyle)
{
// short-circuit the single face per family case
if (mAvailableFonts.Length() == 1) {
MacOSFontEntry *fe = mAvailableFonts[0];
PRUint32 weight = fe->Weight();
aFontsForWeights[weight] = fe;
return PR_TRUE;
}
PRBool found = PR_FALSE;
PRBool isItalic = (aFontStyle.style == FONT_STYLE_ITALIC || aFontStyle.style == FONT_STYLE_OBLIQUE);
// match italic faces
if (isItalic) {
// first search for italic normal width fonts
found = FindFontsWithTraits(aFontsForWeights, NSItalicFontMask, kTraits_NonNormalWidthMask);
// if not found, italic any width ones
if (!found) {
found = FindFontsWithTraits(aFontsForWeights, NSItalicFontMask, 0);
}
}
// match non-italic faces, if no italic faces fall through here
if (!found) {
// look for normal width fonts
found = FindFontsWithTraits(aFontsForWeights, NSUnitalicFontMask, kTraits_NonNormalWidthMask);
// if not found, any face will do
if (!found) {
found = FindFontsWithTraits(aFontsForWeights, NSUnitalicFontMask, 0);
}
}
// still not found?!? family must only contain italic fonts when looking for a normal
// face, just use the whole list
if (!found) {
found = FindFontsWithTraits(aFontsForWeights, 0, 0);
}
NS_ASSERTION(found, "Font family containing no faces");
return found;
}
static NSString* CreateNameFromBuffer(const UInt8 *aBuf, ByteCount aLength,
FontPlatformCode aPlatformCode, FontScriptCode aScriptCode, FontLanguageCode aLangCode)
{
CFStringRef outName = NULL;
if (aPlatformCode == kFontMacintoshPlatform) {
TextEncoding encoding;
OSStatus err = GetTextEncodingFromScriptInfo(aScriptCode, aLangCode,
kTextRegionDontCare, &encoding);
if (err) {
// some fonts are sloppy about the language code (e.g Arial Hebrew, Corsiva Hebrew)
// try again without the lang code to avoid bad combinations
OSStatus err = GetTextEncodingFromScriptInfo(aScriptCode, kTextLanguageDontCare,
kTextRegionDontCare, &encoding);
if (err) return nil;
}
outName = CFStringCreateWithBytes(kCFAllocatorDefault, aBuf,
aLength, (CFStringEncoding) encoding, false);
} else if (aPlatformCode == kFontUnicodePlatform) {
outName = CFStringCreateWithCharacters(kCFAllocatorDefault, (UniChar*)aBuf, aLength/2);
} else if (aPlatformCode == kFontMicrosoftPlatform) {
if (aScriptCode == 0) {
outName = CFStringCreateWithBytes(kCFAllocatorDefault, aBuf,
aLength, kCFStringEncodingUTF16BE, false);
} else {
outName = CFStringCreateWithCharacters(kCFAllocatorDefault, (UniChar*)aBuf, aLength/2);
}
}
return (NSString*) outName;
}
// 10.4 headers only define TT/OT name table id's up to the license id (14) but 10.5 does, so use our own enum
enum {
kMozillaFontPreferredFamilyName = 16,
};
// xxx - rather than use ATSUI calls, probably faster to load name table directly,
// this avoids copying around strings that are of no interest
// returns true if other names were found, false otherwise
static PRBool ReadOtherFamilyNamesForFace(AddOtherFamilyNameFunctor& aOtherFamilyFunctor, MacOSFamilyEntry *aFamilyEntry,
NSString *familyName, ATSUFontID fontID, bool useFullName = false)
{
OSStatus err;
ItemCount i, nameCount;
PRBool foundNames = PR_FALSE;
if (fontID == kATSUInvalidFontID)
return foundNames;
err = ATSUCountFontNames(fontID, &nameCount);
if (err != noErr)
return foundNames;
for (i = 0; i < nameCount; i++) {
FontNameCode nameCode;
FontPlatformCode platformCode;
FontScriptCode scriptCode;
FontLanguageCode langCode;
const ByteCount kBufLength = 2048;
char buf[kBufLength];
ByteCount len;
err = ATSUGetIndFontName(fontID, i, kBufLength, buf, &len, &nameCode, &platformCode, &scriptCode, &langCode);
// couldn't find a font name? just continue to the next name table entry
if (err == kATSUNoFontNameErr)
continue;
// any other error, bail
if (err != noErr)
return foundNames;
if (useFullName) {
if (nameCode != kFontFullName)
continue;
} else {
if (!(nameCode == kFontFamilyName || nameCode == kMozillaFontPreferredFamilyName))
continue;
}
if (len >= kBufLength) continue;
buf[len] = 0;
NSString *name = CreateNameFromBuffer((UInt8*)buf, len, platformCode, scriptCode, langCode);
// add if not same as canonical family name or already in list of names
if (name) {
if (![name isEqualToString:familyName]) {
nsAutoString otherFamilyName;
GetStringForNSString(name, otherFamilyName);
aOtherFamilyFunctor(aFamilyEntry, otherFamilyName);
foundNames = PR_TRUE;
}
[name release];
}
}
return foundNames;
}
void
MacOSFamilyEntry::ReadOtherFamilyNames(AddOtherFamilyNameFunctor& aOtherFamilyFunctor)
{
if (mOtherFamilyNamesInitialized)
return;
mOtherFamilyNamesInitialized = PR_TRUE;
NSString *familyName = GetNSStringForString(mName);
// read in other family names for the first face in the list
MacOSFontEntry *fe = mAvailableFonts[0];
mHasOtherFamilyNames = ReadOtherFamilyNamesForFace(aOtherFamilyFunctor, this, familyName, fe->GetFontID());
// 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) {
PRUint32 numFonts, i;
// read in names for all faces, needed to catch cases where
// fonts all family names for individual weights (e.g. Hiragino Kaku Gothic Pro W6)
numFonts = mAvailableFonts.Length();
for (i = 1; i < numFonts; i++) {
fe = mAvailableFonts[i];
ReadOtherFamilyNamesForFace(aOtherFamilyFunctor, this, familyName, fe->GetFontID());
}
}
}
/* SingleFaceFamily */
#pragma mark-
void SingleFaceFamily::LocalizedName(nsAString& aLocalizedName)
{
MacOSFontEntry *fontEntry;
// use the display name of the single face
fontEntry = mAvailableFonts[0];
if (!fontEntry)
return;
NSFont *font = [NSFont fontWithName:GetNSStringForString(fontEntry->Name()) size:0.0];
if (!font)
return;
NSString *fullname = [font displayName];
if (fullname) {
GetStringForNSString(fullname, aLocalizedName);
}
}
void SingleFaceFamily::ReadOtherFamilyNames(AddOtherFamilyNameFunctor& aOtherFamilyFunctor)
{
if (mOtherFamilyNamesInitialized)
return;
mOtherFamilyNamesInitialized = PR_TRUE;
NSString *familyName = GetNSStringForString(mName);
// read in other family names for the first face in the list
MacOSFontEntry *fe = mAvailableFonts[0];
// read in other names, using the full font names as the family names
mHasOtherFamilyNames = ReadOtherFamilyNamesForFace(aOtherFamilyFunctor, this, familyName, fe->GetFontID(), true);
}
/* gfxQuartzFontCache */
#pragma mark-
gfxQuartzFontCache *gfxQuartzFontCache::sSharedFontCache = nsnull;
gfxQuartzFontCache::gfxQuartzFontCache()
: mStartIndex(0), mIncrement(kNumFontsPerSlice), mNumFamilies(0)
{
mFontFamilies.Init(100);
mOtherFamilyNames.Init(30);
mOtherFamilyNamesInitialized = PR_FALSE;
mPrefFonts.Init(10);
InitFontList();
::ATSFontNotificationSubscribe(ATSNotification,
kATSFontNotifyOptionDefault,
(void*)this, nsnull);
// pref changes notification setup
nsCOMPtr<nsIPref> pref = do_GetService(NS_PREF_CONTRACTID);
pref->RegisterCallback("font.", PrefChangedCallback, this);
pref->RegisterCallback("font.name-list.", PrefChangedCallback, this);
pref->RegisterCallback("intl.accept_languages", PrefChangedCallback, this); // hmmmm...
}
const PRUint32 kNonNormalTraits = NSItalicFontMask | NSBoldFontMask | NSNarrowFontMask | NSExpandedFontMask | NSCondensedFontMask | NSCompressedFontMask;
void
gfxQuartzFontCache::InitFontList()
{
mFontFamilies.Clear();
mOtherFamilyNames.Clear();
mOtherFamilyNamesInitialized = PR_FALSE;
mPrefFonts.Clear();
mCodepointsWithNoFonts.reset();
CancelLoader();
// iterate over available families
NSFontManager *fontManager = [NSFontManager sharedFontManager];
NSEnumerator *families = [[fontManager availableFontFamilies] objectEnumerator]; // returns "canonical", non-localized family name
nsAutoString availableFamilyName, postscriptFontName;
NSString *availableFamily = nil;
while ((availableFamily = [families nextObject])) {
// make a nsString
GetStringForNSString(availableFamily, availableFamilyName);
// create a family entry
MacOSFamilyEntry *familyEntry = new MacOSFamilyEntry(availableFamilyName);
if (!familyEntry) break;
// create a font entry for each face
NSArray *fontfaces = [fontManager availableMembersOfFontFamily:availableFamily]; // returns an array of [psname, style name, weight, traits] elements, goofy api
int faceCount = [fontfaces count];
int faceIndex;
for (faceIndex = 0; faceIndex < faceCount; faceIndex++) {
NSArray *face = [fontfaces objectAtIndex:faceIndex];
NSString *psname = [face objectAtIndex:INDEX_FONT_POSTSCRIPT_NAME];
PRInt32 weight = [[face objectAtIndex:INDEX_FONT_WEIGHT] unsignedIntValue];
PRUint32 traits = [[face objectAtIndex:INDEX_FONT_TRAITS] unsignedIntValue];
// 10.5 doesn't set NSUnitalicFontMask and NSUnboldFontMask - manually set these for consistency
if (!(traits & NSBoldFontMask))
traits |= NSUnboldFontMask;
if (!(traits & NSItalicFontMask))
traits |= NSUnitalicFontMask;
PR_LOG(gFontInfoLog, PR_LOG_DEBUG, ("(fontinit) family: %s, psname: %s, face: %s, apple-weight: %d, css-weight: %d, traits: %8.8x\n",
[availableFamily UTF8String], [psname UTF8String], [[face objectAtIndex:INDEX_FONT_FACE_NAME] UTF8String], weight, gfxQuartzFontCache::AppleWeightToCSSWeight(weight), traits));
// make a nsString
GetStringForNSString(psname, postscriptFontName);
// create a font entry
MacOSFontEntry *fontEntry = new MacOSFontEntry(postscriptFontName, weight, traits, familyEntry);
if (!fontEntry) break;
// insert into font entry array of family
familyEntry->AddFontEntry(fontEntry);
}
// add the family entry to the hash table
ToLowerCase(availableFamilyName);
mFontFamilies.Put(availableFamilyName, familyEntry);
}
InitSingleFaceList();
// to avoid full search of font name tables, seed the other names table with localized names from
// some of the prefs fonts which are accessed via their localized names. changes in the pref fonts will only cause
// a font lookup miss earlier. this is a simple optimization, it's not required for correctness
PreloadNamesList();
// clean up various minor 10.4 font problems for specific fonts
if (gfxPlatformMac::GetPlatform()->OSXVersion() < MAC_OS_X_VERSION_10_5_HEX) {
// Cocoa calls report that italic faces exist for Courier and Helvetica,
// even though only bold faces exist so test for this using ATSUI id's (10.5 has proper faces)
EliminateDuplicateFaces(NS_LITERAL_STRING("Courier"));
EliminateDuplicateFaces(NS_LITERAL_STRING("Helvetica"));
// Cocoa reports that Courier and Monaco are not fixed-pitch fonts
// so explicitly tweak these settings
SetFixedPitch(NS_LITERAL_STRING("Courier"));
SetFixedPitch(NS_LITERAL_STRING("Monaco"));
}
// initialize ranges of characters for which system-wide font search should be skipped
mCodepointsWithNoFonts.SetRange(0,0x1f); // C0 controls
mCodepointsWithNoFonts.SetRange(0x7f,0x9f); // C1 controls
InitBadUnderlineList();
// start the delayed cmap loader
StartLoader(kDelayBeforeLoadingCmaps, kIntervalBetweenLoadingCmaps);
}
void
gfxQuartzFontCache::InitOtherFamilyNames()
{
mOtherFamilyNamesInitialized = PR_TRUE;
// iterate over all font families and read in other family names
mFontFamilies.Enumerate(gfxQuartzFontCache::InitOtherFamilyNamesProc, this);
}
PLDHashOperator PR_CALLBACK gfxQuartzFontCache::InitOtherFamilyNamesProc(nsStringHashKey::KeyType aKey,
nsRefPtr<MacOSFamilyEntry>& aFamilyEntry,
void* userArg)
{
gfxQuartzFontCache *fc = (gfxQuartzFontCache*) userArg;
AddOtherFamilyNameFunctor addOtherNames(fc);
aFamilyEntry->ReadOtherFamilyNames(addOtherNames);
return PL_DHASH_NEXT;
}
void
gfxQuartzFontCache::ReadOtherFamilyNamesForFamily(const nsAString& aFamilyName)
{
MacOSFamilyEntry *familyEntry = FindFamily(aFamilyName);
if (familyEntry) {
AddOtherFamilyNameFunctor addOtherNames(this);
familyEntry->ReadOtherFamilyNames(addOtherNames);
}
}
void
gfxQuartzFontCache::InitSingleFaceList()
{
nsAutoTArray<nsString, 10> singleFaceFonts;
gfxFontUtils::GetPrefsFontList("font.single-face-list", singleFaceFonts);
PRUint32 numFonts = singleFaceFonts.Length();
for (PRUint32 i = 0; i < numFonts; i++) {
nsAutoString availableFamilyName;
// lookup the font using NSFont
NSString *faceName = GetNSStringForString(singleFaceFonts[i]);
NSFont *font = [NSFont fontWithName:faceName size:0.0];
if (font) {
NSString *availableFamily = [font familyName];
GetStringForNSString(availableFamily, availableFamilyName);
MacOSFamilyEntry *familyEntry = FindFamily(availableFamilyName);
if (familyEntry) {
MacOSFontEntry *fontEntry = familyEntry->FindFont(singleFaceFonts[i]);
if (fontEntry) {
PRBool found;
nsAutoString displayName, key;
// use the display name the canonical name
NSString *display = [font displayName];
GetStringForNSString(display, displayName);
GenerateFontListKey(displayName, key);
// add only if doesn't exist already
if (!(familyEntry = mFontFamilies.GetWeak(key, &found))) {
familyEntry = new SingleFaceFamily(displayName);
familyEntry->AddFontEntry(fontEntry);
mFontFamilies.Put(key, familyEntry);
PR_LOG(gFontInfoLog, PR_LOG_DEBUG, ("(fontinit-singleface) family: %s, psname: %s\n", [display UTF8String], [faceName UTF8String]));
}
fontEntry->mFamily = familyEntry;
}
}
}
}
}
void
gfxQuartzFontCache::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!
MacOSFamilyEntry *familyEntry = mFontFamilies.GetWeak(key, &found);
if (familyEntry) {
AddOtherFamilyNameFunctor addOtherNames(this);
familyEntry->ReadOtherFamilyNames(addOtherNames);
}
}
}
void
gfxQuartzFontCache::EliminateDuplicateFaces(const nsAString& aFamilyName)
{
MacOSFamilyEntry *family = FindFamily(aFamilyName);
if (!family) return;
nsTArray<nsRefPtr<MacOSFontEntry> >& fontlist = family->GetFontList();
PRUint32 i, bold, numFonts, italicIndex;
MacOSFontEntry *italic, *nonitalic;
PRUint32 boldtraits[2] = { 0, NSBoldFontMask };
// if normal and italic have the same ATSUI id, delete italic
// if bold and bold-italic have the same ATSUI id, delete bold-italic
// two iterations, one for normal, one for bold
for (bold = 0; bold < 2; bold++) {
numFonts = fontlist.Length();
// find the non-italic face
nonitalic = nsnull;
for (i = 0; i < numFonts; i++) {
PRUint32 traits = fontlist[i]->Traits();
if (((traits & NSBoldFontMask) == boldtraits[bold]) && !(traits & NSItalicFontMask)) {
nonitalic = fontlist[i];
break;
}
}
// find the italic face
if (nonitalic) {
italic = nsnull;
for (i = 0; i < numFonts; i++) {
PRUint32 traits = fontlist[i]->Traits();
if (((traits & NSBoldFontMask) == boldtraits[bold]) && (traits & NSItalicFontMask)) {
italic = fontlist[i];
italicIndex = i;
break;
}
}
// if italic face and non-italic face have matching ATSUI id's,
// the italic face is bogus so remove it
if (italic && italic->GetFontID() == nonitalic->GetFontID()) {
fontlist.RemoveElementAt(italicIndex);
}
}
}
}
void
gfxQuartzFontCache::SetFixedPitch(const nsAString& aFamilyName)
{
MacOSFamilyEntry *family = FindFamily(aFamilyName);
if (!family) return;
nsTArray<nsRefPtr<MacOSFontEntry> >& fontlist = family->GetFontList();
PRUint32 i, numFonts = fontlist.Length();
for (i = 0; i < numFonts; i++) {
fontlist[i]->mTraits |= NSFixedPitchFontMask;
}
}
void
gfxQuartzFontCache::InitBadUnderlineList()
{
nsAutoTArray<nsString, 10> blacklist;
gfxFontUtils::GetPrefsFontList("font.blacklist.underline_offset", blacklist);
PRUint32 numFonts = blacklist.Length();
for (PRUint32 i = 0; i < numFonts; i++) {
PRBool found;
nsAutoString key;
GenerateFontListKey(blacklist[i], key);
MacOSFamilyEntry *familyEntry = mFontFamilies.GetWeak(key, &found);
if (familyEntry)
familyEntry->mIsBadUnderlineFontFamily = 1;
}
}
PRBool
gfxQuartzFontCache::ResolveFontName(const nsAString& aFontName, nsAString& aResolvedFontName)
{
MacOSFamilyEntry *family = FindFamily(aFontName);
if (family) {
aResolvedFontName = family->Name();
return PR_TRUE;
}
return PR_FALSE;
}
PRBool
gfxQuartzFontCache::GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName)
{
MacOSFamilyEntry *family = FindFamily(aFontName);
if (family) {
family->LocalizedName(aFamilyName);
return PR_TRUE;
}
// Gecko 1.8 used Quickdraw font api's which produce a slightly different set of "family"
// names. Try to resolve based on these names, in case this is stored in an old profile
// 1.8: "Futura", "Futura Condensed" ==> 1.9: "Futura
FMFont fmFont;
// convert of a NSString
NSString *fontName = GetNSStringForString(aFontName);
// name ==> family id ==> old-style FMFont
ATSFontFamilyRef fmFontFamily = ATSFontFamilyFindFromName((CFStringRef)fontName, kATSOptionFlagsDefault);
OSStatus err = FMGetFontFromFontFamilyInstance(fmFontFamily, 0, &fmFont, nsnull);
if (err != noErr || fmFont == kInvalidFont)
return PR_FALSE;
ATSFontRef atsFont = FMGetATSFontRefFromFont(fmFont);
if (!atsFont)
return PR_FALSE;
NSString *psname;
// now lookup the Postscript name
err = ATSFontGetPostScriptName(atsFont, kATSOptionFlagsDefault, (CFStringRef*) (&psname));
if (err != noErr)
return PR_FALSE;
// given an NSFont instance, Cocoa api's return the canonical family name
NSString *canonicalfamily = [[NSFont fontWithName:psname size:0.0] familyName];
[psname release];
nsAutoString familyName;
// lookup again using the canonical family name
GetStringForNSString(canonicalfamily, familyName);
family = FindFamily(familyName);
if (family) {
family->LocalizedName(aFamilyName);
return PR_TRUE;
}
return PR_FALSE;
}
void
gfxQuartzFontCache::ATSNotification(ATSFontNotificationInfoRef aInfo,
void* aUserArg)
{
// xxx - should be carefully pruning the list of fonts, not rebuilding it from scratch
gfxQuartzFontCache *qfc = (gfxQuartzFontCache*)aUserArg;
qfc->UpdateFontList();
}
int PR_CALLBACK
gfxQuartzFontCache::PrefChangedCallback(const char *aPrefName, void *closure)
{
// 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.
gfxQuartzFontCache *qfc = static_cast<gfxQuartzFontCache *>(closure);
qfc->mPrefFonts.Clear();
return 0;
}
MacOSFontEntry*
gfxQuartzFontCache::GetDefaultFont(const gfxFontStyle* aStyle, PRBool& aNeedsBold)
{
NSString *defaultFamily = [[NSFont userFontOfSize:aStyle->size] familyName];
nsAutoString familyName;
GetStringForNSString(defaultFamily, familyName);
return FindFontForFamily(familyName, aStyle, aNeedsBold);
}
struct FontListData {
FontListData(const nsACString& aLangGroup,
const nsACString& aGenericFamily,
nsStringArray& aListOfFonts) :
mLangGroup(aLangGroup), mGenericFamily(aGenericFamily),
mListOfFonts(aListOfFonts) {}
const nsACString& mLangGroup;
const nsACString& mGenericFamily;
nsStringArray& mListOfFonts;
};
PLDHashOperator PR_CALLBACK
gfxQuartzFontCache::HashEnumFuncForFamilies(nsStringHashKey::KeyType aKey,
nsRefPtr<MacOSFamilyEntry>& aFamilyEntry,
void *aUserArg)
{
FontListData *data = (FontListData*)aUserArg;
nsAutoString localizedFamilyName;
aFamilyEntry->LocalizedName(localizedFamilyName);
data->mListOfFonts.AppendString(localizedFamilyName);
return PL_DHASH_NEXT;
}
void
gfxQuartzFontCache::GetFontList (const nsACString& aLangGroup,
const nsACString& aGenericFamily,
nsStringArray& aListOfFonts)
{
FontListData data(aLangGroup, aGenericFamily, aListOfFonts);
mFontFamilies.Enumerate(gfxQuartzFontCache::HashEnumFuncForFamilies, &data);
aListOfFonts.Sort();
aListOfFonts.Compact();
}
struct FontFamilyListData {
FontFamilyListData(nsTArray<nsRefPtr<MacOSFamilyEntry> >& aFamilyArray)
: mFamilyArray(aFamilyArray)
{}
static PLDHashOperator PR_CALLBACK AppendFamily(nsStringHashKey::KeyType aKey,
nsRefPtr<MacOSFamilyEntry>& aFamilyEntry,
void *aUserArg)
{
FontFamilyListData *data = (FontFamilyListData*)aUserArg;
data->mFamilyArray.AppendElement(aFamilyEntry);
return PL_DHASH_NEXT;
}
nsTArray<nsRefPtr<MacOSFamilyEntry> >& mFamilyArray;
};
void
gfxQuartzFontCache::GetFontFamilyList(nsTArray<nsRefPtr<MacOSFamilyEntry> >& aFamilyArray)
{
FontFamilyListData data(aFamilyArray);
mFontFamilies.Enumerate(FontFamilyListData::AppendFamily, &data);
}
MacOSFontEntry*
gfxQuartzFontCache::FindFontForChar(const PRUint32 aCh, gfxFont *aPrevFont)
{
// is codepoint with no matching font? return null immediately
if (mCodepointsWithNoFonts.test(aCh)) {
return nsnull;
}
// short-circuit system font fallback for U+FFFD, used to represent encoding errors
// just use Lucida Grande (system font, guaranteed to be around)
// this helps speed up pages with lots of encoding errors, binary-as-text, etc.
if (aCh == 0xFFFD) {
MacOSFontEntry* fontEntry;
PRBool needsBold; // ignored in the system fallback case
if (aPrevFont) {
fontEntry = FindFontForFamily(NS_LITERAL_STRING("Lucida Grande"), aPrevFont->GetStyle(), needsBold);
} else {
gfxFontStyle normalStyle;
fontEntry = FindFontForFamily(NS_LITERAL_STRING("Lucida Grande"), &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(gfxQuartzFontCache::FindFontForCharProc, &data);
// no match? add to set of non-matching codepoints
if (!data.bestMatch) {
mCodepointsWithNoFonts.set(aCh);
}
return data.bestMatch;
}
PLDHashOperator PR_CALLBACK
gfxQuartzFontCache::FindFontForCharProc(nsStringHashKey::KeyType aKey, nsRefPtr<MacOSFamilyEntry>& aFamilyEntry,
void *userArg)
{
FontSearch *data = (FontSearch*)userArg;
// evaluate all fonts in this family for a match
aFamilyEntry->FindFontForChar(data);
return PL_DHASH_NEXT;
}
MacOSFamilyEntry*
gfxQuartzFontCache::FindFamily(const nsAString& aFamily)
{
nsAutoString key;
MacOSFamilyEntry *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;
}
MacOSFontEntry*
gfxQuartzFontCache::FindFontForFamily(const nsAString& aFamily, const gfxFontStyle* aStyle, PRBool& aNeedsBold)
{
MacOSFamilyEntry *familyEntry = FindFamily(aFamily);
aNeedsBold = PR_FALSE;
if (familyEntry)
return familyEntry->FindFont(aStyle, aNeedsBold);
return nsnull;
}
PRInt32
gfxQuartzFontCache::AppleWeightToCSSWeight(PRInt32 aAppleWeight)
{
if (aAppleWeight < 1)
aAppleWeight = 1;
else if (aAppleWeight > kAppleMaxWeight)
aAppleWeight = kAppleMaxWeight;
return gAppleWeightToCSSWeight[aAppleWeight];
}
PRBool
gfxQuartzFontCache::GetPrefFontFamilyEntries(eFontPrefLang aLangGroup, nsTArray<nsRefPtr<MacOSFamilyEntry> > *array)
{
return mPrefFonts.Get(PRUint32(aLangGroup), array);
}
void
gfxQuartzFontCache::SetPrefFontFamilyEntries(eFontPrefLang aLangGroup, nsTArray<nsRefPtr<MacOSFamilyEntry> >& array)
{
mPrefFonts.Put(PRUint32(aLangGroup), array);
}
void
gfxQuartzFontCache::AddOtherFamilyName(MacOSFamilyEntry *aFamilyEntry, nsAString& aOtherFamilyName)
{
nsAutoString key;
PRBool found;
GenerateFontListKey(aOtherFamilyName, key);
if (!mOtherFamilyNames.GetWeak(key, &found)) {
mOtherFamilyNames.Put(key, aFamilyEntry);
PR_LOG(gFontInfoLog, PR_LOG_DEBUG, ("(fontinit-otherfamily) canonical family: %s, other family: %s\n",
NS_ConvertUTF16toUTF8(aFamilyEntry->Name()).get(),
NS_ConvertUTF16toUTF8(aOtherFamilyName).get()));
}
}
void
gfxQuartzFontCache::InitLoader()
{
GetFontFamilyList(mFontFamiliesToLoad);
mStartIndex = 0;
mNumFamilies = mFontFamiliesToLoad.Length();
}
PRBool
gfxQuartzFontCache::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++) {
AddOtherFamilyNameFunctor addOtherNames(this);
// load the cmap
mFontFamiliesToLoad[i]->ReadCMAP();
// read in other family names
mFontFamiliesToLoad[i]->ReadOtherFamilyNames(addOtherNames);
}
mStartIndex += mIncrement;
if (mStartIndex < mNumFamilies)
return PR_FALSE;
return PR_TRUE;
}
void
gfxQuartzFontCache::FinishLoader()
{
mFontFamiliesToLoad.Clear();
mNumFamilies = 0;
}