b=364786, improve Mac font selection, r=masayuki,sr=roc
authorvladimir@pobox.com
Wed, 18 Jul 2007 07:22:06 -0700
changeset 3609 b93288890e5d4bfb9d12fad843f7affb622e90cb
parent 3608 6bb8b4f48ee51b555b8044e190163bcdcfb77c4e
child 3610 4107eb58583e3bfc72755b230146e7ade09dbd54
push idunknown
push userunknown
push dateunknown
reviewersmasayuki, roc
bugs364786
milestone1.9a7pre
b=364786, improve Mac font selection, r=masayuki,sr=roc
gfx/thebes/public/gfxFont.h
gfx/thebes/public/gfxPlatform.h
gfx/thebes/src/Makefile.in
gfx/thebes/src/gfxAtsuiFonts.cpp
gfx/thebes/src/gfxFont.cpp
gfx/thebes/src/gfxPlatform.cpp
--- a/gfx/thebes/public/gfxFont.h
+++ b/gfx/thebes/public/gfxFont.h
@@ -993,34 +993,58 @@ public:
         GlyphRun   *mGlyphRun;
         PRUint32    mStringStart;
         PRUint32    mStringEnd;
         PRUint32    mNextIndex;
         PRUint32    mStartOffset;
         PRUint32    mEndOffset;
     };
 
+    class GlyphRunOffsetComparator {
+    public:
+        PRBool Equals(const GlyphRun& a,
+                      const GlyphRun& b) const
+        {
+            return a.mCharacterOffset == b.mCharacterOffset;
+        }
+
+        PRBool LessThan(const GlyphRun& a,
+                        const GlyphRun& b) const
+        {
+            return a.mCharacterOffset < b.mCharacterOffset;
+        }
+    };
+
     friend class GlyphRunIterator;
     friend class FontSelector;
 
     // API for setting up the textrun glyphs. Should only be called by
     // things that construct textruns.
     /**
      * Record every character that is the second half of a surrogate pair.
      * This should be called after creating a Unicode textrun.
      */
     void RecordSurrogates(const PRUnichar *aString);
     /**
      * We've found a run of text that should use a particular font. Call this
      * only during initialization when font substitution has been computed.
      * Call it before setting up the glyphs for the characters in this run;
      * SetMissingGlyph requires that the correct glyphrun be installed.
+     *
+     * If aForceNewRun, a new glyph run will be added, even if the
+     * previously added run uses the same font.  If glyph runs are
+     * added out of strictly increasing aStartCharIndex order (via
+     * force), then SortGlyphRuns must be called after all glyph runs
+     * are added before any further operations are performed with this
+     * TextRun.
      */
-    nsresult AddGlyphRun(gfxFont *aFont, PRUint32 aStartCharIndex);
+    nsresult AddGlyphRun(gfxFont *aFont, PRUint32 aStartCharIndex, PRBool aForceNewRun = PR_FALSE);
     void ResetGlyphRuns() { mGlyphRuns.Clear(); }
+    void SortGlyphRuns();
+
     // Call the following glyph-setters during initialization or during reshaping
     // only. It is OK to overwrite existing data for a character.
     /**
      * Set the glyph for a character. Also allows you to set low surrogates,
      * cluster and ligature continuations.
      */
     void SetCharacterGlyph(PRUint32 aCharIndex, CompressedGlyph aGlyph) {
         NS_ASSERTION(aCharIndex > 0 ||
--- a/gfx/thebes/public/gfxPlatform.h
+++ b/gfx/thebes/public/gfxPlatform.h
@@ -128,17 +128,17 @@ public:
                                           const gfxFontStyle *aStyle) = 0;
 
     /* Returns PR_TRUE if the given block of ARGB32 data really has alpha, otherwise PR_FALSE */
     static PRBool DoesARGBImageDataHaveAlpha(PRUint8* data,
                                              PRUint32 width,
                                              PRUint32 height,
                                              PRUint32 stride);
 
-    void GetPrefFonts(const char *aLangGroup, nsString& array);
+    void GetPrefFonts(const char *aLangGroup, nsString& array, PRBool aAppendUnicode = PR_TRUE);
 
 protected:
     gfxPlatform() { }
     virtual ~gfxPlatform();
 
 };
 
 #endif /* GFX_PLATFORM_H */
--- a/gfx/thebes/src/Makefile.in
+++ b/gfx/thebes/src/Makefile.in
@@ -102,16 +102,17 @@ CPPSRCS +=	gfxFontconfigUtils.cpp
 CPPSRCS +=	nsUnicodeRange.cpp
 EXTRA_DSO_LDOPTS += $(MOZ_PANGO_LIBS) $(CAIRO_FT_LIBS) -lfontconfig -lpangocairo-1.0
 endif
 
 
 ifneq (,$(filter $(MOZ_WIDGET_TOOLKIT),mac cocoa))
 CPPSRCS	+= 	gfxQuartzSurface.cpp gfxQuartzPDFSurface.cpp gfxPlatformMac.cpp gfxAtsuiFonts.cpp
 #CPPSRCS +=	gfxPDFSurface.cpp
+CPPSRCS +=      nsUnicodeRange.cpp
 
 CMMSRCS = gfxQuartzFontCache.mm
 
 # Always link with OpenGL/AGL
 EXTRA_DSO_LDOPTS += -framework OpenGL -framework AGL -framework Cocoa -framework QuickTime
 endif
 
 ifdef MOZ_ENABLE_GLITZ
--- a/gfx/thebes/src/gfxAtsuiFonts.cpp
+++ b/gfx/thebes/src/gfxAtsuiFonts.cpp
@@ -41,25 +41,32 @@
 #include "nsString.h"
 #include "nsBidiUtils.h"
 
 #include "gfxTypes.h"
 
 #include "nsPromiseFlatString.h"
 
 #include "gfxContext.h"
+#include "gfxPlatform.h"
 #include "gfxAtsuiFonts.h"
 
 #include "gfxFontTest.h"
 
 #include "cairo-atsui.h"
 
 #include "gfxQuartzSurface.h"
 #include "gfxQuartzFontCache.h"
 
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsUnicodeRange.h"
+#include "nsCRT.h"
+
 // Uncomment this to dump all text runs created to stdout
 // #define DUMP_TEXT_RUNS
 
 #define ROUND(x) (floor((x) + 0.5))
 
 /* We might still need this for fast-pathing, but we'll see */
 #if 0
 OSStatus ATSUGetStyleGroup(ATSUStyle style, void **styleGroup);
@@ -275,16 +282,36 @@ gfxAtsuiFont::~gfxAtsuiFont()
 }
 
 const gfxFont::Metrics&
 gfxAtsuiFont::GetMetrics()
 {
     return mMetrics;
 }
 
+static nsresult
+CreateFontFallbacksFromFontList(nsTArray< nsRefPtr<gfxFont> > *aFonts,
+                                ATSUFontFallbacks *aFallbacks,
+                                ATSUFontFallbackMethod aMethod)
+{
+    // Create the fallback structure
+    OSStatus status = ::ATSUCreateFontFallbacks(aFallbacks);
+    NS_ENSURE_TRUE(status == noErr, NS_ERROR_FAILURE);
+
+    nsAutoTArray<ATSUFontID,16> fids;
+
+    for (unsigned int i = 0; i < aFonts->Length(); i++) {
+        gfxAtsuiFont* atsuiFont = NS_STATIC_CAST(gfxAtsuiFont*, aFonts->ElementAt(i).get());
+        fids.AppendElement(atsuiFont->GetATSUFontID());
+    }
+    status = ::ATSUSetObjFontFallbacks(*aFallbacks, fids.Length(), fids.Elements(), aMethod);
+
+    return status == noErr ? NS_OK : NS_ERROR_FAILURE;
+}
+
 PRBool 
 gfxAtsuiFont::HasMirroringInfo()
 {
     if (!mHasMirroringLookedUp) {
         OSStatus status;
         ByteCount size;
         
         // 361695 - if the font has a 'prop' table, assume that ATSUI will handle glyph mirroring
@@ -340,53 +367,32 @@ gfxAtsuiFontGroup::gfxAtsuiFontGroup(con
 
         // If we get here, we most likely didn't have a default font for
         // a specific langGroup.  Let's just pick the default OSX
         // user font.
         ATSUFontID fontID = gfxQuartzFontCache::SharedFontCache()->GetDefaultATSUFontID (aStyle);
         GetOrMakeFont(fontID, aStyle, &mFonts);
     }
 
-    // Create the fallback structure
-    ATSUCreateFontFallbacks(&mFallbacks);
-
-#define NUM_STATIC_FIDS 16
-    ATSUFontID static_fids[NUM_STATIC_FIDS];
-    ATSUFontID *fids;
-    if (mFonts.Length() > NUM_STATIC_FIDS)
-        fids = (ATSUFontID *) PR_Malloc(sizeof(ATSUFontID) * mFonts.Length());
-    else
-        fids = static_fids;
-
-    for (unsigned int i = 0; i < mFonts.Length(); i++) {
-        gfxAtsuiFont* atsuiFont = static_cast<gfxAtsuiFont*>(static_cast<gfxFont*>(mFonts[i]));
-        fids[i] = atsuiFont->GetATSUFontID();
-    }
-    ATSUSetObjFontFallbacks(mFallbacks,
-                            mFonts.Length(),
-                            fids,
-                            kATSUSequentialFallbacksPreferred /* kATSUSequentialFallbacksExclusive? */);
-
-    if (fids != static_fids)
-        PR_Free(fids);
+    CreateFontFallbacksFromFontList(&mFonts, &mFallbacks, kATSULastResortOnlyFallback);
 }
 
 PRBool
 gfxAtsuiFontGroup::FindATSUFont(const nsAString& aName,
                                 const nsACString& aGenericName,
                                 void *closure)
 {
     gfxAtsuiFontGroup *fontGroup = (gfxAtsuiFontGroup*) closure;
     const gfxFontStyle *fontStyle = fontGroup->GetStyle();
 
     gfxQuartzFontCache *fc = gfxQuartzFontCache::SharedFontCache();
     ATSUFontID fontID = fc->FindATSUFontIDForFamilyAndStyle (aName, fontStyle);
 
     if (fontID != kATSUInvalidFontID) {
-        //printf ("FindATSUFont! %s %d -> %d\n", NS_ConvertUTF16toUTF8(aName).get(), fontStyle->weight, (int)fontID);
+        //fprintf (stderr, "..FindATSUFont: %s\n", NS_ConvertUTF16toUTF8(aName).get());
         GetOrMakeFont(fontID, fontStyle, &fontGroup->mFonts);
     }
 
     return PR_TRUE;
 }
 
 gfxAtsuiFontGroup::~gfxAtsuiFontGroup()
 {
@@ -915,20 +921,266 @@ PostLayoutOperationCallback(ATSULayoutOp
                            gCallbackClosure->mString, gCallbackClosure->mWrapped,
                            gCallbackClosure->mUnmatchedChars,
                            gCallbackClosure->mSegmentStart,
                            gCallbackClosure->mSegmentLength);
     *oCallbackStatus = kATSULayoutOperationCallbackStatusContinue;
     return noErr;
 }
 
+// The lang IDs for font prefs
+enum eFontPrefLang {
+    eFontPrefLang_Western     =  0,
+    eFontPrefLang_CentEuro    =  1,
+    eFontPrefLang_Japanese    =  2,
+    eFontPrefLang_ChineseTW   =  3,
+    eFontPrefLang_ChineseCN   =  4,
+    eFontPrefLang_ChineseHK   =  5,
+    eFontPrefLang_Korean      =  6,
+    eFontPrefLang_Cyrillic    =  7,
+    eFontPrefLang_Baltic      =  8,
+    eFontPrefLang_Greek       =  9,
+    eFontPrefLang_Turkish     = 10,
+    eFontPrefLang_Thai        = 11,
+    eFontPrefLang_Hebrew      = 12,
+    eFontPrefLang_Arabic      = 13,
+    eFontPrefLang_Devanagari  = 14,
+    eFontPrefLang_Tamil       = 15,
+    eFontPrefLang_Armenian    = 16,
+    eFontPrefLang_Bengali     = 17,
+    eFontPrefLang_Canadian    = 18,
+    eFontPrefLang_Ethiopic    = 19,
+    eFontPrefLang_Georgian    = 20,
+    eFontPrefLang_Gujarati    = 21,
+    eFontPrefLang_Gurmukhi    = 22,
+    eFontPrefLang_Khmer       = 23,
+    eFontPrefLang_Malayalam   = 24,
+
+    eFontPrefLang_LangCount   = 25, // except Others and UserDefined.
+
+    eFontPrefLang_Others      = 25, // x-unicode
+    eFontPrefLang_UserDefined = 26,
+
+    eFontPrefLang_CJKSet      = 27, // special code for CJK set
+    eFontPrefLang_AllCount    = 28
+};
+
+// The lang names for eFontPrefLang
+static const char *gPrefLangNames[] = {
+    "x-western",
+    "x-central-euro",
+    "ja",
+    "zh-TW",
+    "zh-CN",
+    "zh-HK",
+    "ko",
+    "x-cyrillic",
+    "x-baltic",
+    "el",
+    "tr",
+    "th",
+    "he",
+    "ar",
+    "x-devanagari",
+    "x-tamil",
+    "x-armn",
+    "x-beng",
+    "x-cans",
+    "x-ethi",
+    "x-geor",
+    "x-gujr",
+    "x-guru",
+    "x-khmr",
+    "x-mlym",
+    "x-unicode",
+    "x-user-def"
+};
+
+static eFontPrefLang
+GetFontPrefLangFor(const char* aLang)
+{
+    if (!aLang || aLang[0])
+        return eFontPrefLang_Others;
+    for (PRUint32 i = 0; i < PRUint32(eFontPrefLang_LangCount); ++i) {
+        if (!PL_strcasecmp(gPrefLangNames[i], aLang))
+            return eFontPrefLang(i);
+    }
+    return eFontPrefLang_Others;
+}
+
+eFontPrefLang
+GetFontPrefLangFor(PRUint8 aUnicodeRange)
+{
+    switch (aUnicodeRange) {
+        case kRangeCyrillic:   return eFontPrefLang_Cyrillic;
+        case kRangeGreek:      return eFontPrefLang_Greek;
+        case kRangeTurkish:    return eFontPrefLang_Turkish;
+        case kRangeHebrew:     return eFontPrefLang_Hebrew;
+        case kRangeArabic:     return eFontPrefLang_Arabic;
+        case kRangeBaltic:     return eFontPrefLang_Baltic;
+        case kRangeThai:       return eFontPrefLang_Thai;
+        case kRangeKorean:     return eFontPrefLang_Korean;
+        case kRangeJapanese:   return eFontPrefLang_Japanese;
+        case kRangeSChinese:   return eFontPrefLang_ChineseCN;
+        case kRangeTChinese:   return eFontPrefLang_ChineseTW;
+        case kRangeDevanagari: return eFontPrefLang_Devanagari;
+        case kRangeTamil:      return eFontPrefLang_Tamil;
+        case kRangeArmenian:   return eFontPrefLang_Armenian;
+        case kRangeBengali:    return eFontPrefLang_Bengali;
+        case kRangeCanadian:   return eFontPrefLang_Canadian;
+        case kRangeEthiopic:   return eFontPrefLang_Ethiopic;
+        case kRangeGeorgian:   return eFontPrefLang_Georgian;
+        case kRangeGujarati:   return eFontPrefLang_Gujarati;
+        case kRangeGurmukhi:   return eFontPrefLang_Gurmukhi;
+        case kRangeKhmer:      return eFontPrefLang_Khmer;
+        case kRangeMalayalam:  return eFontPrefLang_Malayalam;
+        case kRangeSetCJK:     return eFontPrefLang_CJKSet;
+        default:               return eFontPrefLang_Others;
+    }
+}
+
+static const char*
+GetPrefLangName(eFontPrefLang aLang)
+{
+    if (PRUint32(aLang) < PRUint32(eFontPrefLang_AllCount))
+        return gPrefLangNames[PRUint32(aLang)];
+    return nsnull;
+}
+
+struct AFLClosure {
+    const gfxFontStyle *style;
+    nsTArray<nsRefPtr<gfxFont> > *fontArray;
+};
+
+PRBool
+AppendFontToList(const nsAString& aName,
+                 const nsACString& aGenericName,
+                 void *closure)
+{
+    struct AFLClosure *afl = (struct AFLClosure *) closure;
+
+    gfxQuartzFontCache *fc = gfxQuartzFontCache::SharedFontCache();
+    ATSUFontID fontID = fc->FindATSUFontIDForFamilyAndStyle (aName, afl->style);
+
+    if (fontID != kATSUInvalidFontID) {
+        //fprintf (stderr, "..AppendFontToList: %s\n", NS_ConvertUTF16toUTF8(aName).get());
+        GetOrMakeFont(fontID, afl->style, afl->fontArray);
+    }
+
+    return PR_TRUE;
+}
+
+static nsresult
+AppendPrefFonts(nsTArray<nsRefPtr<gfxFont> > *aFonts,
+                eFontPrefLang aLang,
+                PRUint32& didAppendBits,
+                const gfxFontStyle *aStyle)
+{
+    if (didAppendBits & (1 << aLang))
+        return NS_OK;
+
+    didAppendBits |= (1 << aLang);
+
+    const char* langGroup = GetPrefLangName(aLang);
+    if (!langGroup || !langGroup[0]) {
+        NS_ERROR("The langGroup is null");
+        return NS_ERROR_FAILURE;
+    }
+    gfxPlatform *platform = gfxPlatform::GetPlatform();
+    NS_ENSURE_TRUE(platform, NS_ERROR_OUT_OF_MEMORY);
+    nsString fonts;
+    platform->GetPrefFonts(langGroup, fonts, PR_FALSE);
+    if (fonts.IsEmpty())
+        return NS_OK;
+
+    struct AFLClosure afl = { aStyle, aFonts };
+    gfxFontGroup::ForEachFont(fonts, nsDependentCString(langGroup),
+                              AppendFontToList, &afl);
+    return NS_OK;
+}
+
+static nsresult
+AppendCJKPrefFonts(nsTArray<nsRefPtr<gfxFont> > *aFonts,
+                   PRUint32& didAppendBits,
+                   const gfxFontStyle *aStyle)
+{
+    nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+    NS_ENSURE_TRUE(prefs, NS_ERROR_OUT_OF_MEMORY);
+
+    nsCOMPtr<nsIPrefBranch> prefBranch;
+    prefs->GetBranch(nsnull, getter_AddRefs(prefBranch));
+    NS_ENSURE_TRUE(prefBranch, NS_ERROR_OUT_OF_MEMORY);
+
+    // Add the CJK pref fonts from accept languages, the order should be same order
+    nsXPIDLCString list;
+    nsresult rv = prefBranch->GetCharPref("intl.accept_languages", getter_Copies(list));
+    if (NS_SUCCEEDED(rv) && !list.IsEmpty()) {
+        const char kComma = ',';
+        const char *p, *p_end;
+        list.BeginReading(p);
+        list.EndReading(p_end);
+        while (p < p_end) {
+            while (nsCRT::IsAsciiSpace(*p)) {
+                if (++p == p_end)
+                    break;
+            }
+            if (p == p_end)
+                break;
+            const char *start = p;
+            while (++p != p_end && *p != kComma)
+                /* nothing */ ;
+            nsCAutoString lang(Substring(start, p));
+            lang.CompressWhitespace(PR_FALSE, PR_TRUE);
+            eFontPrefLang fpl = GetFontPrefLangFor(lang.get());
+            switch (fpl) {
+                case eFontPrefLang_Japanese:
+                case eFontPrefLang_Korean:
+                case eFontPrefLang_ChineseCN:
+                case eFontPrefLang_ChineseHK:
+                case eFontPrefLang_ChineseTW:
+                    rv = AppendPrefFonts(aFonts, fpl, didAppendBits, aStyle);
+                    NS_ENSURE_SUCCESS(rv, rv);
+                    break;
+                default:
+                    break;
+            }
+            p++;
+        }
+    }
+
+    // Prefer the system locale if it is CJK.
+    ScriptCode sysScript = ::GetScriptManagerVariable(smSysScript);
+    // XXX Is not there the HK locale?
+    switch (sysScript) {
+        case smJapanese:    rv = AppendPrefFonts(aFonts, eFontPrefLang_Japanese, didAppendBits, aStyle);  break;
+        case smTradChinese: rv = AppendPrefFonts(aFonts, eFontPrefLang_ChineseTW, didAppendBits, aStyle); break;
+        case smKorean:      rv = AppendPrefFonts(aFonts, eFontPrefLang_Korean, didAppendBits, aStyle);    break;
+        case smSimpChinese: rv = AppendPrefFonts(aFonts, eFontPrefLang_ChineseCN, didAppendBits, aStyle); break;
+        default:            rv = NS_OK;
+    }
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // last resort... (the order is same as old gfx.)
+    rv = AppendPrefFonts(aFonts, eFontPrefLang_Japanese, didAppendBits, aStyle);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = AppendPrefFonts(aFonts, eFontPrefLang_Korean, didAppendBits, aStyle);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = AppendPrefFonts(aFonts, eFontPrefLang_ChineseCN, didAppendBits, aStyle);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = AppendPrefFonts(aFonts, eFontPrefLang_ChineseHK, didAppendBits, aStyle);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = AppendPrefFonts(aFonts, eFontPrefLang_ChineseTW, didAppendBits, aStyle);
+    return rv;
+}
+
 static void
 AddGlyphRun(gfxTextRun *aRun, gfxAtsuiFont *aFont, PRUint32 aOffset)
 {
-    aRun->AddGlyphRun(aFont, aOffset);
+    //fprintf (stderr, "+ AddGlyphRun: %d %s\n", aOffset, NS_ConvertUTF16toUTF8(aFont->GetUniqueName()).get());
+    aRun->AddGlyphRun(aFont, aOffset, PR_TRUE);
     if (!aRun->IsClusterStart(aOffset)) {
         // Glyph runs must start at cluster boundaries. However, sometimes
         // ATSUI matches different fonts for characters in the same cluster.
         // If this happens, break up the cluster. It's not clear what else
         // we can do.
         NS_WARNING("Font mismatch inside cluster");
         gfxTextRun::CompressedGlyph g;
         aRun->SetCharacterGlyph(aOffset, g.SetMissing());
@@ -972,18 +1224,18 @@ DisableOptionalLigaturesInStyle(ATSUStyl
 //   @param layout      ATSUI layout for the entire text run
 //   @param mirrorStr   container used for mirror string, null until a mirrored character is found
 //   @param aString     original string
 //   @param aLength     length of the original string
 //   @param runStart    start offset of substring to be mirrored
 //   @param runLength   length of substring to be mirrored
 
 static void MirrorSubstring(ATSUTextLayout layout, nsAutoArrayPtr<PRUnichar>& mirroredStr,
-                                    const PRUnichar *aString, PRUint32 aLength,
-                                    UniCharArrayOffset runStart, UniCharCount runLength)
+                            const PRUnichar *aString, PRUint32 aLength,
+                            UniCharArrayOffset runStart, UniCharCount runLength)
 {
     UniCharArrayOffset  off;
     
     // do the mirroring manually!!
     for (off = runStart; off < runStart + runLength; off++) {
         PRUnichar  mirroredChar;
         
         mirroredChar = (PRUnichar) SymmSwap(aString[off]);
@@ -1006,18 +1258,18 @@ static void MirrorSubstring(ATSUTextLayo
 
 PRBool
 gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
                                const PRUnichar *aString, PRUint32 aLength,
                                PRBool aWrapped, PRUint32 aSegmentStart,
                                PRUint32 aSegmentLength)
 {
     OSStatus status;
-    gfxAtsuiFont *atsuiFont = GetFontAt(0);
-    ATSUStyle mainStyle = atsuiFont->GetATSUStyle();
+    gfxAtsuiFont *firstFont = GetFontAt(0);
+    ATSUStyle mainStyle = firstFont->GetATSUStyle();
     nsTArray<ATSUStyle> stylesToDispose;
     PRUint32 headerChars = aWrapped ? 1 : 0;
     const PRUnichar *realString = aString + headerChars;
     NS_ASSERTION(aSegmentLength == aLength - (aWrapped ? 3 : 0),
                  "Length mismatch");
 
 #ifdef DUMP_TEXT_RUNS
     NS_ConvertUTF16toUTF8 str(realString, aSegmentLength);
@@ -1085,132 +1337,243 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRu
     ATSUSetLayoutControls(layout,
                           NS_ARRAY_LENGTH(layoutTags),
                           layoutTags,
                           layoutArgSizes,
                           layoutArgs);
 
     /* Now go through and update the styles for the text, based on font matching. */
 
+    nsAutoArrayPtr<PRUnichar> mirroredStr;
+    nsTArray<PRUint32> missingOffsetsAndLengths;
+    nsTArray<ATSUFontFallbacks> fallbacksToDispose;
+
+    PRBool firstTime = PR_TRUE;
+
     UniCharArrayOffset runStart = headerChars;
-    UniCharCount totalLength = runStart + aSegmentLength;
     UniCharCount runLength = aSegmentLength;
-    nsAutoArrayPtr<PRUnichar>  mirroredStr;
+    UniCharCount totalLength = headerChars + aSegmentLength;
+    
+    nsresult rv;
 
-    //fprintf (stderr, "==== Starting font maching [string length: %d]\n", totalLength);
-    while (runStart < totalLength) {
-        ATSUFontID substituteFontID;
-        UniCharArrayOffset changedOffset;
-        UniCharCount changedLength;
+    //fprintf (stderr, "==== Starting font maching [string length: %d (%s)]\n", totalLength, NS_ConvertUTF16toUTF8(aString, aLength).get());
+    do {
+        PRUint32 missingRanges = missingOffsetsAndLengths.Length() / 2;
+        UniCharArrayOffset missingStart = 0;
+        UniCharCount missingLength = 0;
+
+        //fprintf (stderr, "(loop start, missing ranges: %d)\n", missingRanges);
+
+        if (missingRanges > 0) {
+            // we had some misses.  Let's figure out what languages we need
+            // and pull in the fallback fonts from prefs.
 
-        OSStatus status = ATSUMatchFontsToText(layout, runStart, runLength,
-                                                &substituteFontID, &changedOffset, &changedLength);
-        if (status == noErr) {
-        
-            // glyphs exist for all characters in the [runStart, runStart + runLength) substring
-            
-            // in the RTL case, handle fallback mirroring
-            if (aRun->IsRightToLeft() && !atsuiFont->HasMirroringInfo()) {
-                MirrorSubstring(layout, mirroredStr, aString, aLength, runStart, runLength);
-            }
-            
-            // add a glyph run for the entire substring
-            AddGlyphRun(aRun, atsuiFont, aSegmentStart + runStart - headerChars);
-            
-            break;
+            missingStart = missingOffsetsAndLengths[(missingRanges-1) * 2];
+            missingLength = missingOffsetsAndLengths[(missingRanges-1) * 2 + 1];
+            missingOffsetsAndLengths.RemoveElementsAt((missingRanges-1) * 2, 2);
+
+            runStart = missingStart;
+            runLength = missingLength;
+
+            //fprintf (stderr, ".. range: %d %d\n", runStart, runLength);
+
+            totalLength = runStart + runLength;
+
+            PRUint32 didAppendFonts = 0;
+            PRUint32 lastRange = kRangeTableBase;
+
+            nsAutoTArray<nsRefPtr<gfxFont>,3> mLangFonts;
+
+            for (PRUint32 j = 0; j < runLength; j++) {
+                PRUint32 unicodeRange = FindCharUnicodeRange(aString[j+runStart]);
+                if (unicodeRange == lastRange)
+                    continue;
+
+                lastRange = unicodeRange;
+                eFontPrefLang prefLang = GetFontPrefLangFor(unicodeRange);
 
-        } else if (status == kATSUFontsMatched) {
-        
-            // substitute font will be used in [changedOffset, changedOffset + changedLength)
- 
-            // create a new style for the substitute font
-            ATSUStyle subStyle;
-            ATSUCreateStyle (&subStyle);
-            ATSUCopyAttributes (mainStyle, subStyle);
+                if (prefLang == eFontPrefLang_CJKSet)
+                    rv = AppendCJKPrefFonts(&mLangFonts, didAppendFonts, GetStyle());
+                else
+                    rv = AppendPrefFonts(&mLangFonts, prefLang, didAppendFonts, GetStyle());
+                if (NS_FAILED(rv))
+                    break;
+            }
+
+            if (NS_FAILED(rv))
+                break;
 
-            ATSUAttributeTag fontTags[] = { kATSUFontTag };
-            ByteCount fontArgSizes[] = { sizeof(ATSUFontID) };
-            ATSUAttributeValuePtr fontArgs[] = { &substituteFontID };
+            // always append Unicode
+            rv = AppendPrefFonts(&mLangFonts, eFontPrefLang_Others, didAppendFonts, GetStyle());
+            if (NS_FAILED(rv))
+                break;
+
+            // now we have a list of fonts in mLangFonts (potentially -- 0-length is ok)
+            ATSUFontFallbacks langFallbacks;
+            rv = CreateFontFallbacksFromFontList(&mLangFonts, &langFallbacks, kATSUSequentialFallbacksPreferred);
+            if (NS_FAILED(rv))
+                break;
+
+            fallbacksToDispose.AppendElement(langFallbacks);
 
-            ATSUSetAttributes (subStyle, 1, fontTags, fontArgSizes, fontArgs);
+            static ATSUAttributeTag fallbackTags[] = { kATSULineFontFallbacksTag };
+            static ByteCount fallbackArgSizes[] = { sizeof(ATSUFontFallbacks) };
+            ATSUAttributeValuePtr fallbackArgs[] = { &langFallbacks };
 
-            // apply the new style to the layout for the changed substring
-            ATSUSetRunStyle (layout, subStyle, changedOffset, changedLength);
+            ATSUSetLayoutControls(layout,
+                                  NS_ARRAY_LENGTH(fallbackTags),
+                                  fallbackTags, fallbackArgSizes, fallbackArgs);
+        }
 
-            // if needed, add a glyph run for [runStart, changedOffset) with the original font
-            if (changedOffset > runStart) {
-            
-                // in the RTL case, handle fallback mirroring
-                if (aRun->IsRightToLeft() && !atsuiFont->HasMirroringInfo()) {
-                    MirrorSubstring(layout, mirroredStr, aString, aLength, runStart, 
-                                        changedOffset - runStart);
-                }
-                
-                AddGlyphRun(aRun, atsuiFont, aSegmentStart + runStart - headerChars);
+        while (runStart < totalLength) {
+            ATSUFontID substituteFontID;
+            UniCharArrayOffset changedOffset;
+            UniCharCount changedLength;
+            UniCharCount foundCharacters;
+
+            OSStatus status = ATSUMatchFontsToText(layout, runStart, runLength,
+                                                   &substituteFontID, &changedOffset, &changedLength);
+
+            if (status == noErr) {
+                foundCharacters = runLength;
+                changedLength = 0;
+            } else {
+                foundCharacters = changedOffset - runStart;
             }
 
-            // add a glyph run for [changedOffset, changedOffset + changedLength) with the
-            // substituted font
-            gfxAtsuiFont *font = FindFontFor(substituteFontID);
-            if (font) {
+            // first, handle any characters that were matched with firstFont
+            if (foundCharacters) {
+                //fprintf (stderr, "... glyphs found in first: %d %d\n", runStart, foundCharacters);
+                // glyphs exist for all characters in the [runStart, runStart + foundCharacters) substring
 
                 // in the RTL case, handle fallback mirroring
-                if (aRun->IsRightToLeft() && !font->HasMirroringInfo()) {
-                    MirrorSubstring(layout, mirroredStr, aString, aLength, changedOffset, 
-                                        changedLength);
+                if (aRun->IsRightToLeft() && !firstFont->HasMirroringInfo()) {
+                    MirrorSubstring(layout, mirroredStr, aString, aLength, runStart, runLength);
                 }
-                
-                AddGlyphRun(aRun, font, aSegmentStart + changedOffset - headerChars);
-            }
-            
-            stylesToDispose.AppendElement(subStyle);
             
-        } else if (status == kATSUFontsNotMatched) {
-        
-            //fprintf (stderr, "ATSUMatchFontsToText returned kATSUFontsNotMatched\n");
-            /* I need to select the last resort font; how the heck do I do that? */
-            // Record which font is associated with these glyphs, anyway
+                // add a glyph run for the entire substring
+                AddGlyphRun(aRun, firstFont, aSegmentStart + runStart - headerChars);
+
+                // do we have any more work to do?
+                if (status == noErr)
+                    break;
+            }
+
+            // then, handle any chars that were found in the fallback list
+            if (status == kATSUFontsMatched) {
+                // substitute font will be used in [changedOffset, changedOffset + changedLength)
+                gfxAtsuiFont *font = FindFontFor(substituteFontID);
+
+                //fprintf (stderr, "... glyphs matched in fallback: %d %d (%s)\n", changedOffset, changedLength, NS_ConvertUTF16toUTF8(font->GetUniqueName()).get());
+
+                if (font) {
+                    // create a new style for the substitute font
+                    ATSUStyle subStyle;
+                    ATSUCreateStyle (&subStyle);
+                    ATSUCopyAttributes (mainStyle, subStyle);
+
+                    ATSUAttributeTag fontTags[] = { kATSUFontTag };
+                    ByteCount fontArgSizes[] = { sizeof(ATSUFontID) };
+                    ATSUAttributeValuePtr fontArgs[] = { &substituteFontID };
 
-            // hmmm, so was changedOffset set?  we appear to ignore it...
-            
-            // in the RTL case, handle fallback mirroring
-            if (aRun->IsRightToLeft() && !atsuiFont->HasMirroringInfo()) {
-                MirrorSubstring(layout, mirroredStr, aString, aLength, runStart, runLength);
-            }
-            
-            AddGlyphRun(aRun, atsuiFont, aSegmentStart + runStart - headerChars);
-            
-            if (!closure.mUnmatchedChars) {
-                closure.mUnmatchedChars = new PRPackedBool[aLength];
-                if (closure.mUnmatchedChars) {
-                    memset(closure.mUnmatchedChars.get(), PR_FALSE, aLength);
+                    ATSUSetAttributes (subStyle, 1, fontTags, fontArgSizes, fontArgs);
+
+                    // apply the new style to the layout for the changed substring
+                    ATSUSetRunStyle (layout, subStyle, changedOffset, changedLength);
+
+                    stylesToDispose.AppendElement(subStyle);
+
+                    // add a glyph run for [changedOffset, changedOffset + changedLength) with the
+                    // substituted font
+
+                    // in the RTL case, handle fallback mirroring
+                    if (aRun->IsRightToLeft() && !font->HasMirroringInfo()) {
+                        MirrorSubstring(layout, mirroredStr, aString, aLength, changedOffset, 
+                                        changedLength);
+                    }
+                
+                    AddGlyphRun(aRun, font, aSegmentStart + changedOffset - headerChars);
+                } else {
+                    // We could hit this case if we decided to ignore the
+                    // font when enumerating at startup; pretend that these are
+                    // missing glyphs.
+                    // XXX - wait, why did it even end up in the fallback list,
+                    // then?
+
+                    status = kATSUFontsNotMatched;
                 }
             }
-            if (closure.mUnmatchedChars) {
-                memset(closure.mUnmatchedChars.get() + changedOffset - headerChars,
-                       PR_TRUE, changedLength);
+
+            if (status == kATSUFontsNotMatched) {
+                //fprintf (stderr, "... glyphs not found: %d %d\n", changedOffset, changedLength);
+
+                // If this is our first time through (no fallback),
+                // or if we're missing a different range than before,
+                // add it to the list of ranges to examine
+                if (firstTime ||
+                    (changedOffset != missingStart && changedLength != missingLength))
+                {
+                    //fprintf (stderr, " (will look again at %d %d)\n", changedOffset, changedLength);
+                    missingOffsetsAndLengths.AppendElement(changedOffset);
+                    missingOffsetsAndLengths.AppendElement(changedLength);
+                } else {
+                    AddGlyphRun(aRun, firstFont, aSegmentStart + changedOffset - headerChars);
+
+                    if (!closure.mUnmatchedChars) {
+                        closure.mUnmatchedChars = new PRPackedBool[aLength];
+                        if (closure.mUnmatchedChars) {
+                            memset(closure.mUnmatchedChars.get(), PR_FALSE, aLength);
+                        }
+                    }
+
+                    if (closure.mUnmatchedChars) {
+                        memset(closure.mUnmatchedChars.get() + changedOffset - headerChars,
+                               PR_TRUE, changedLength);
+                    }
+                }
             }
-            
+
+            //fprintf (stderr, "total length: %d changedOffset: %d changedLength: %d\n",  runLength, changedOffset, changedLength);
+
+            runStart = changedOffset + changedLength;
+            runLength = totalLength - runStart;
         }
 
-        //fprintf (stderr, "total length: %d changedOffset: %d changedLength: %d\p=n",  runLength, changedOffset, changedLength);
+        firstTime = PR_FALSE;
+    } while (missingOffsetsAndLengths.Length() > 0);
+
+    // put back the fallback object, because we'll be disposing
+    // of any of the ones we created while doing fallback
+    if (fallbacksToDispose.Length() > 0) {
+        static ATSUAttributeTag fallbackTags[] = { kATSULineFontFallbacksTag };
+        static ByteCount fallbackArgSizes[] = { sizeof(ATSUFontFallbacks) };
+        ATSUAttributeValuePtr fallbackArgs[] = { GetATSUFontFallbacksPtr() };
 
-        runStart = changedOffset + changedLength;
-        runLength = totalLength - runStart;
+        ATSUSetLayoutControls(layout,
+                              NS_ARRAY_LENGTH(fallbackTags),
+                              fallbackTags, fallbackArgSizes, fallbackArgs);
     }
 
+    for (PRUint32 i = 0; i < fallbacksToDispose.Length(); i++)
+        ATSUDisposeFontFallbacks(fallbacksToDispose[i]);
+
+    // sort our glyph runs; we may have added
+    // some out of order while doing fallback font matching
+    aRun->SortGlyphRuns();
+
+    //fprintf (stderr, "==== End font matching\n");
+
     // Trigger layout so that our callback fires. We don't actually care about
     // the result of this call.
     ATSTrapezoid trap;
     ItemCount trapCount;
     ATSUGetGlyphBounds(layout, 0, 0, headerChars, aSegmentLength,
                        kATSUseFractionalOrigins, 1, &trap, &trapCount); 
 
     ATSUDisposeTextLayout(layout);
 
-    //fprintf (stderr, "==== End font matching\n");
     PRUint32 i;
     for (i = 0; i < stylesToDispose.Length(); ++i) {
         ATSUDisposeStyle(stylesToDispose[i]);
     }
     gCallbackClosure = nsnull;
     return !closure.mOverrunningGlyphs;
 }
--- a/gfx/thebes/src/gfxFont.cpp
+++ b/gfx/thebes/src/gfxFont.cpp
@@ -1516,44 +1516,53 @@ gfxTextRun::FindFirstGlyphRunContaining(
         }
     }
     NS_ASSERTION(mGlyphRuns[start].mCharacterOffset <= aOffset,
                  "Hmm, something went wrong, aOffset should have been found");
     return start;
 }
 
 nsresult
-gfxTextRun::AddGlyphRun(gfxFont *aFont, PRUint32 aUTF16Offset)
+gfxTextRun::AddGlyphRun(gfxFont *aFont, PRUint32 aUTF16Offset, PRBool aForceNewRun)
 {
     PRUint32 numGlyphRuns = mGlyphRuns.Length();
-    if (numGlyphRuns > 0) {
+    if (!aForceNewRun &&
+        numGlyphRuns > 0)
+    {
         GlyphRun *lastGlyphRun = &mGlyphRuns[numGlyphRuns - 1];
 
         NS_ASSERTION(lastGlyphRun->mCharacterOffset <= aUTF16Offset,
-                     "Glyph runs out of order");
+                     "Glyph runs out of order (and run not forced)");
 
         if (lastGlyphRun->mFont == aFont)
             return NS_OK;
         if (lastGlyphRun->mCharacterOffset == aUTF16Offset) {
             lastGlyphRun->mFont = aFont;
             return NS_OK;
         }
     }
 
-    NS_ASSERTION(numGlyphRuns > 0 || aUTF16Offset == 0,
-                 "First run doesn't cover the first character?");
+    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;
     return NS_OK;
 }
 
+void
+gfxTextRun::SortGlyphRuns()
+{
+    GlyphRunOffsetComparator comp;
+    mGlyphRuns.Sort(comp);
+}
+
 PRUint32
 gfxTextRun::CountMissingGlyphs()
 {
     PRUint32 i;
     PRUint32 count = 0;
     for (i = 0; i < mCharacterCount; ++i) {
         if (mCharacterGlyphs[i].IsMissing()) {
             ++count;
--- a/gfx/thebes/src/gfxPlatform.cpp
+++ b/gfx/thebes/src/gfxPlatform.cpp
@@ -282,16 +282,17 @@ AppendGenericFontFromPref(nsString& aFon
     if (NS_SUCCEEDED(rv)) {
         if (!aFonts.IsEmpty())
             aFonts.AppendLiteral(", ");
         aFonts.Append(value);
     }
 }
 
 void
-gfxPlatform::GetPrefFonts(const char *aLangGroup, nsString& aFonts)
+gfxPlatform::GetPrefFonts(const char *aLangGroup, nsString& aFonts, PRBool aAppendUnicode)
 {
     aFonts.Truncate();
 
     AppendGenericFontFromPref(aFonts, aLangGroup, nsnull);
-    AppendGenericFontFromPref(aFonts, "x-unicode", nsnull);
+    if (aAppendUnicode)
+        AppendGenericFontFromPref(aFonts, "x-unicode", nsnull);
 }