Bug 385417 - Rework textrun glyph representation so we can handle clusters containing glyphs in different fonts [p=roc r=stuart r=smontagu a=blocking1.9+]
authorreed@reedloden.com
Thu, 08 Nov 2007 22:27:23 -0800
changeset 7715 ce3cf2f9cdc29e9d75db8989a97f5dad37749cde
parent 7714 d84738a8088661675cd6a6acd1bf47d343094ddd
child 7716 fc349b44fceb4573f72b8182d44d6c08fb0f20d4
push idunknown
push userunknown
push dateunknown
reviewersstuart, smontagu, blocking1
bugs385417
milestone1.9b2pre
Bug 385417 - Rework textrun glyph representation so we can handle clusters containing glyphs in different fonts [p=roc r=stuart r=smontagu a=blocking1.9+]
gfx/src/thebes/nsThebesFontMetrics.cpp
gfx/src/thebes/nsThebesFontMetrics.h
gfx/thebes/public/gfxFont.h
gfx/thebes/src/gfxAtsuiFonts.cpp
gfx/thebes/src/gfxFont.cpp
gfx/thebes/src/gfxOS2Fonts.cpp
gfx/thebes/src/gfxPangoFonts.cpp
gfx/thebes/src/gfxWindowsFonts.cpp
layout/generic/nsTextFrameThebes.cpp
layout/generic/nsTextRunTransformations.cpp
--- a/gfx/src/thebes/nsThebesFontMetrics.cpp
+++ b/gfx/src/thebes/nsThebesFontMetrics.cpp
@@ -266,63 +266,45 @@ nsThebesFontMetrics::GetMaxStringLength(
     const gfxFont::Metrics& m = GetMetrics();
     const double x = 32767.0 / m.maxAdvance;
     PRInt32 len = (PRInt32)floor(x);
     return PR_MAX(1, len);
 }
 
 class StubPropertyProvider : public gfxTextRun::PropertyProvider {
 public:
-    StubPropertyProvider(const nscoord* aSpacing = nsnull)
-      : mSpacing(aSpacing) {}
-
     virtual void GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength,
                                       PRPackedBool* aBreakBefore) {
         NS_ERROR("This shouldn't be called because we never call BreakAndMeasureText");
     }
     virtual gfxFloat GetHyphenWidth() {
-        NS_ERROR("This shouldn't be called because we never specify hyphen breaks");
+        NS_ERROR("This shouldn't be called because we never enable hyphens");
         return 0;
     }
     virtual void GetSpacing(PRUint32 aStart, PRUint32 aLength,
-                            Spacing* aSpacing);
-
-private:
-    const nscoord* mSpacing;
+                            Spacing* aSpacing) {
+        NS_ERROR("This shouldn't be called because we never enable spacing");
+    }
 };
 
-void
-StubPropertyProvider::GetSpacing(PRUint32 aStart, PRUint32 aLength,
-                                 Spacing* aSpacing)
-{
-    PRUint32 i;
-    for (i = 0; i < aLength; ++i) {
-        aSpacing[i].mBefore = 0;
-        // mSpacing is absolute (already includes the character width). This is OK
-        // because gfxTextRunCache sets TEXT_ABSOLUTE_SPACING when it creates
-        // the textrun.
-        aSpacing[i].mAfter = mSpacing ? mSpacing[aStart + i] : 0;
-    }
-}
-
 nsresult 
 nsThebesFontMetrics::GetWidth(const char* aString, PRUint32 aLength, nscoord& aWidth,
                               nsThebesRenderingContext *aContext)
 {
     if (aLength == 0) {
         aWidth = 0;
         return NS_OK;
     }
 
     // callers that hit this should not be so stupid
     if ((aLength == 1) && (aString[0] == ' '))
         return GetSpaceWidth(aWidth);
 
     StubPropertyProvider provider;
-    AutoTextRun textRun(this, aContext, aString, aLength, PR_FALSE);
+    AutoTextRun textRun(this, aContext, aString, aLength);
     if (!textRun.get())
         return NS_ERROR_FAILURE;
 
     aWidth = NSToCoordRound(textRun->GetAdvanceWidth(0, aLength, &provider));
 
     return NS_OK;
 }
 
@@ -336,17 +318,17 @@ nsThebesFontMetrics::GetWidth(const PRUn
         return NS_OK;
     }
 
     // callers that hit this should not be so stupid
     if ((aLength == 1) && (aString[0] == ' '))
         return GetSpaceWidth(aWidth);
 
     StubPropertyProvider provider;
-    AutoTextRun textRun(this, aContext, aString, aLength, PR_FALSE);
+    AutoTextRun textRun(this, aContext, aString, aLength);
     if (!textRun.get())
         return NS_ERROR_FAILURE;
 
     aWidth = NSToCoordRound(textRun->GetAdvanceWidth(0, aLength, &provider));
 
     return NS_OK;
 }
 
@@ -392,18 +374,19 @@ nsresult
 nsThebesFontMetrics::DrawString(const char *aString, PRUint32 aLength,
                                 nscoord aX, nscoord aY,
                                 const nscoord* aSpacing,
                                 nsThebesRenderingContext *aContext)
 {
     if (aLength == 0)
         return NS_OK;
 
-    StubPropertyProvider provider(aSpacing);
-    AutoTextRun textRun(this, aContext, aString, aLength, aSpacing != nsnull);
+    NS_ASSERTION(!aSpacing, "Spacing not supported here");
+    StubPropertyProvider provider;
+    AutoTextRun textRun(this, aContext, aString, aLength);
     if (!textRun.get())
         return NS_ERROR_FAILURE;
     gfxPoint pt(aX, aY);
     if (mTextRunRTL) {
         pt.x += textRun->GetAdvanceWidth(0, aLength, &provider);
     }
     textRun->Draw(aContext->Thebes(), pt, 0, aLength,
                   nsnull, &provider, nsnull);
@@ -416,18 +399,19 @@ nsThebesFontMetrics::DrawString(const PR
                                 nscoord aX, nscoord aY,
                                 PRInt32 aFontID,
                                 const nscoord* aSpacing,
                                 nsThebesRenderingContext *aContext)
 {
     if (aLength == 0)
         return NS_OK;
 
-    StubPropertyProvider provider(aSpacing);
-    AutoTextRun textRun(this, aContext, aString, aLength, aSpacing != nsnull);
+    NS_ASSERTION(!aSpacing, "Spacing not supported here");
+    StubPropertyProvider provider;
+    AutoTextRun textRun(this, aContext, aString, aLength);
     if (!textRun.get())
         return NS_ERROR_FAILURE;
     gfxPoint pt(aX, aY);
     if (mTextRunRTL) {
         pt.x += textRun->GetAdvanceWidth(0, aLength, &provider);
     }
     textRun->Draw(aContext->Thebes(), pt, 0, aLength,
                   nsnull, &provider, nsnull);
--- a/gfx/src/thebes/nsThebesFontMetrics.h
+++ b/gfx/src/thebes/nsThebesFontMetrics.h
@@ -153,49 +153,43 @@ public:
 
 protected:
 
     const gfxFont::Metrics& GetMetrics() const;
 
     class AutoTextRun {
     public:
         AutoTextRun(nsThebesFontMetrics* aMetrics, nsIRenderingContext* aRC,
-                    const char* aString, PRInt32 aLength, PRBool aEnableSpacing) {
+                    const char* aString, PRInt32 aLength) {
             mTextRun = gfxTextRunCache::MakeTextRun(
                 reinterpret_cast<const PRUint8*>(aString), aLength,
                 aMetrics->mFontGroup,
                 static_cast<gfxContext*>(aRC->GetNativeGraphicData(nsIRenderingContext::NATIVE_THEBES_CONTEXT)),
                 aMetrics->mP2A,
-                ComputeFlags(aMetrics, aEnableSpacing));
+                ComputeFlags(aMetrics));
         }
         AutoTextRun(nsThebesFontMetrics* aMetrics, nsIRenderingContext* aRC,
-                    const PRUnichar* aString, PRInt32 aLength, PRBool aEnableSpacing) {
+                    const PRUnichar* aString, PRInt32 aLength) {
             mTextRun = gfxTextRunCache::MakeTextRun(
                 aString, aLength, aMetrics->mFontGroup,
                 static_cast<gfxContext*>(aRC->GetNativeGraphicData(nsIRenderingContext::NATIVE_THEBES_CONTEXT)),
                 aMetrics->mP2A,
-                ComputeFlags(aMetrics, aEnableSpacing));
+                ComputeFlags(aMetrics));
         }
         gfxTextRun* operator->() { return mTextRun.get(); }
         gfxTextRun* get() { return mTextRun.get(); }
 
     private:
         gfxTextRunCache::AutoTextRun mTextRun;
         
-        static PRUint32 ComputeFlags(nsThebesFontMetrics* aMetrics,
-                                     PRBool aEnableSpacing) {
+        static PRUint32 ComputeFlags(nsThebesFontMetrics* aMetrics) {
             PRUint32 flags = 0;
             if (aMetrics->GetRightToLeftTextRunMode()) {
                 flags |= gfxTextRunFactory::TEXT_IS_RTL;
             }
-            if (aEnableSpacing) {
-                flags |= gfxTextRunFactory::TEXT_ENABLE_SPACING |
-                         gfxTextRunFactory::TEXT_ABSOLUTE_SPACING |
-                         gfxTextRunFactory::TEXT_ENABLE_NEGATIVE_SPACING;
-            }
             return flags;
         }
     };
     friend class AutoTextRun;
 
     nsRefPtr<gfxFontGroup> mFontGroup;
     gfxFontStyle *mFontStyle;
 
--- a/gfx/thebes/public/gfxFont.h
+++ b/gfx/thebes/public/gfxFont.h
@@ -569,21 +569,16 @@ public:
          * on the spacing provider.
          */
         TEXT_ENABLE_SPACING          = 0x0008,
         /**
          * When set, GetSpacing can return negative spacing.
          */
         TEXT_ENABLE_NEGATIVE_SPACING = 0x0010,
         /**
-         * When set, mAfter spacing for a character already includes the character
-         * width. Otherwise, it does not include the character width.
-         */
-        TEXT_ABSOLUTE_SPACING        = 0x0020,
-        /**
          * When set, GetHyphenationBreaks may return true for some character
          * positions, otherwise it will always return false for all characters.
          */
         TEXT_ENABLE_HYPHEN_BREAKS    = 0x0040,
         /**
          * When set, the text has no characters above 255 and it is stored
          * in the textrun in 8-bit format.
          */
@@ -680,19 +675,19 @@ public:
     typedef gfxFont::RunMetrics Metrics;
 
     // Public textrun API for general use
 
     PRBool IsClusterStart(PRUint32 aPos) {
         NS_ASSERTION(0 <= aPos && aPos < mCharacterCount, "aPos out of range");
         return mCharacterGlyphs[aPos].IsClusterStart();
     }
-    PRBool IsLigatureContinuation(PRUint32 aPos) {
+    PRBool IsLigatureGroupStart(PRUint32 aPos) {
         NS_ASSERTION(0 <= aPos && aPos < mCharacterCount, "aPos out of range");
-        return mCharacterGlyphs[aPos].IsLigatureContinuation();
+        return mCharacterGlyphs[aPos].IsLigatureGroupStart();
     }
     PRBool CanBreakLineBefore(PRUint32 aPos) {
         NS_ASSERTION(0 <= aPos && aPos < mCharacterCount, "aPos out of range");
         return mCharacterGlyphs[aPos].CanBreakBefore();
     }    
 
     PRUint32 GetLength() { return mCharacterCount; }
 
@@ -967,65 +962,63 @@ public:
     // textrun will copy it.
     virtual gfxTextRun *Clone(const gfxTextRunFactory::Parameters *aParams, const void *aText,
                               PRUint32 aLength, gfxFontGroup *aFontGroup, PRUint32 aFlags);
 
     /**
      * This class records the information associated with a character in the
      * input string. It's optimized for the case where there is one glyph
      * representing that character alone.
+     * 
+     * A character can have zero or more associated glyphs. Each glyph
+     * has an advance width and an x and y offset.
+     * A character may be the start of a cluster.
+     * A character may be the start of a ligature group.
+     * A character can be "missing", indicating that the system is unable
+     * to render the character.
+     * 
+     * All characters in a ligature group conceptually share all the glyphs
+     * associated with the characters in a group.
      */
     class CompressedGlyph {
     public:
         CompressedGlyph() { mValue = 0; }
 
         enum {
-            // Indicates that a cluster starts at this character and can be
-            // rendered using a single glyph with a reasonable advance offset
-            // and no special glyph offset. A "reasonable" advance offset is
-            // one that is a) a multiple of a pixel and b) fits in the available
-            // bits (currently 14). We should revisit this, especially a),
-            // if we want to support subpixel-aligned text.
+            // Indicates that a cluster and ligature group starts at this
+            // character; this character has a single glyph with a reasonable
+            // advance and zero offsets. A "reasonable" advance
+            // is one that fits in the available bits (currently 14) (specified
+            // in appunits).
             FLAG_IS_SIMPLE_GLYPH  = 0x80000000U,
             // Indicates that a linebreak is allowed before this character
             FLAG_CAN_BREAK_BEFORE = 0x40000000U,
 
             // The advance is stored in appunits
             ADVANCE_MASK  = 0x3FFF0000U,
             ADVANCE_SHIFT = 16,
 
             GLYPH_MASK = 0x0000FFFFU,
 
-            // Non-simple glyphs have the following tags
+            // Non-simple glyphs may or may not have glyph data in the
+            // corresponding mDetailedGlyphs entry. They have the following
+            // flag bits:
+
+            // When NOT set, indicates that this character corresponds to a
+            // missing glyph and should be skipped (or possibly, render the character
+            // Unicode value in some special way). If there are glyphs,
+            // the mGlyphID is actually the UTF16 character code. The bit is
+            // inverted so we can memset the array to zero to indicate all missing.
+            FLAG_NOT_MISSING              = 0x01,
+            FLAG_NOT_CLUSTER_START        = 0x02,
+            FLAG_NOT_LIGATURE_GROUP_START = 0x04,
+            FLAG_LOW_SURROGATE            = 0x08,
             
-            TAG_MASK                  = 0x000000FFU,
-            // Indicates that this character corresponds to a missing glyph
-            // and should be skipped (or possibly, render the character
-            // Unicode value in some special way)
-            TAG_MISSING               = 0x00U,
-            // Indicates that a cluster starts at this character and is rendered
-            // using one or more glyphs which cannot be represented here.
-            // Look up the DetailedGlyph table instead.
-            TAG_COMPLEX_CLUSTER       = 0x01U,
-            // Indicates that a cluster starts at this character but is rendered
-            // as part of a ligature starting in a previous cluster.
-            // NOTE: we divide up the ligature's width by the number of clusters
-            // to get the width assigned to each cluster.
-            TAG_LIGATURE_CONTINUATION = 0x21U,
-            
-            // Values where the upper 28 bits equal 0x80 are reserved for
-            // non-cluster-start characters (see IsClusterStart below)
-            
-            // Indicates that a cluster does not start at this character, this is
-            // a low UTF16 surrogate
-            TAG_LOW_SURROGATE         = 0x80U,
-            // Indicates that a cluster does not start at this character, this is
-            // part of a cluster starting with an earlier character (but not
-            // a low surrogate).
-            TAG_CLUSTER_CONTINUATION  = 0x81U
+            GLYPH_COUNT_MASK = 0x00FFFF00U,
+            GLYPH_COUNT_SHIFT = 8
         };
 
         // "Simple glyphs" have a simple glyph ID, simple advance and their
         // x and y offsets are zero. Also the glyph extents do not overflow
         // the font-box defined by the font ascent, descent and glyph advance width.
         // These case is optimized to avoid storing DetailedGlyphs.
 
         // Returns true if the glyph ID aGlyph fits into the compressed representation
@@ -1034,31 +1027,29 @@ public:
         }
         // Returns true if the advance aAdvance fits into the compressed representation.
         // aAdvance is in appunits.
         static PRBool IsSimpleAdvance(PRUint32 aAdvance) {
             return (aAdvance & (ADVANCE_MASK >> ADVANCE_SHIFT)) == aAdvance;
         }
 
         PRBool IsSimpleGlyph() const { return (mValue & FLAG_IS_SIMPLE_GLYPH) != 0; }
-        PRBool IsComplex(PRUint32 aTag) const { return (mValue & (FLAG_IS_SIMPLE_GLYPH|TAG_MASK))  == aTag; }
-        PRBool IsMissing() const { return IsComplex(TAG_MISSING); }
-        PRBool IsComplexCluster() const { return IsComplex(TAG_COMPLEX_CLUSTER); }
-        PRBool IsComplexOrMissing() const {
-            return IsComplex(TAG_COMPLEX_CLUSTER) || IsComplex(TAG_MISSING);
-        }
-        PRBool IsLigatureContinuation() const { return IsComplex(TAG_LIGATURE_CONTINUATION); }
-        PRBool IsClusterContinuation() const { return IsComplex(TAG_CLUSTER_CONTINUATION); }
-        PRBool IsLowSurrogate() const { return IsComplex(TAG_LOW_SURROGATE); }
-        PRBool IsClusterStart() const { return (mValue & (FLAG_IS_SIMPLE_GLYPH|0x80U)) != 0x80U; }
-
         PRUint32 GetSimpleAdvance() const { return (mValue & ADVANCE_MASK) >> ADVANCE_SHIFT; }
         PRUint32 GetSimpleGlyph() const { return mValue & GLYPH_MASK; }
 
-        PRUint32 GetComplexTag() const { return mValue & TAG_MASK; }
+        PRBool IsMissing() const { return (mValue & (FLAG_NOT_MISSING|FLAG_IS_SIMPLE_GLYPH)) == 0; }
+        PRBool IsLowSurrogate() const {
+            return (mValue & (FLAG_LOW_SURROGATE|FLAG_IS_SIMPLE_GLYPH)) == FLAG_LOW_SURROGATE;
+        }
+        PRBool IsClusterStart() const {
+            return (mValue & FLAG_IS_SIMPLE_GLYPH) || !(mValue & FLAG_NOT_CLUSTER_START);
+        }
+        PRBool IsLigatureGroupStart() const {
+            return (mValue & FLAG_IS_SIMPLE_GLYPH) || !(mValue & FLAG_NOT_LIGATURE_GROUP_START);
+        }
 
         PRBool CanBreakBefore() const { return (mValue & FLAG_CAN_BREAK_BEFORE) != 0; }
         // Returns FLAG_CAN_BREAK_BEFORE if the setting changed, 0 otherwise
         PRUint32 SetCanBreakBefore(PRBool aCanBreakBefore) {
             NS_ASSERTION(aCanBreakBefore == PR_FALSE || aCanBreakBefore == PR_TRUE,
                          "Bogus break-before value!");
             PRUint32 breakMask = aCanBreakBefore*FLAG_CAN_BREAK_BEFORE;
             PRUint32 toggle = breakMask ^ (mValue & FLAG_CAN_BREAK_BEFORE);
@@ -1068,55 +1059,73 @@ public:
 
         CompressedGlyph& SetSimpleGlyph(PRUint32 aAdvanceAppUnits, PRUint32 aGlyph) {
             NS_ASSERTION(IsSimpleAdvance(aAdvanceAppUnits), "Advance overflow");
             NS_ASSERTION(IsSimpleGlyphID(aGlyph), "Glyph overflow");
             mValue = (mValue & FLAG_CAN_BREAK_BEFORE) | FLAG_IS_SIMPLE_GLYPH |
                 (aAdvanceAppUnits << ADVANCE_SHIFT) | aGlyph;
             return *this;
         }
-        CompressedGlyph& SetComplex(PRUint32 aTag) {
-            mValue = (mValue & FLAG_CAN_BREAK_BEFORE) | aTag;
+        CompressedGlyph& SetComplex(PRBool aClusterStart, PRBool aLigatureStart,
+                PRUint32 aGlyphCount) {
+            mValue = (mValue & FLAG_CAN_BREAK_BEFORE) | FLAG_NOT_MISSING |
+                (aClusterStart ? 0 : FLAG_NOT_CLUSTER_START) |
+                (aLigatureStart ? 0 : FLAG_NOT_LIGATURE_GROUP_START) |
+                (aGlyphCount << GLYPH_COUNT_SHIFT);
             return *this;
         }
-        CompressedGlyph& SetMissing() { return SetComplex(TAG_MISSING); }
-        CompressedGlyph& SetComplexCluster() { return SetComplex(TAG_COMPLEX_CLUSTER); }
-        CompressedGlyph& SetLowSurrogate() { return SetComplex(TAG_LOW_SURROGATE); }
-        CompressedGlyph& SetLigatureContinuation() { return SetComplex(TAG_LIGATURE_CONTINUATION); }
-        CompressedGlyph& SetClusterContinuation() { return SetComplex(TAG_CLUSTER_CONTINUATION); }
+        /**
+         * Missing glyphs are treated as cluster and ligature group starts.
+         */
+        CompressedGlyph& SetMissing(PRUint32 aGlyphCount) {
+            mValue = (mValue & FLAG_CAN_BREAK_BEFORE) |
+                (aGlyphCount << GLYPH_COUNT_SHIFT);
+            return *this;
+        }
+        /**
+         * Low surrogates don't have any glyphs and are not the start of
+         * a cluster or ligature group.
+         */
+        CompressedGlyph& SetLowSurrogate() {
+            mValue = (mValue & FLAG_CAN_BREAK_BEFORE) | FLAG_NOT_MISSING |
+                FLAG_LOW_SURROGATE;
+            return *this;
+        }
+        PRUint32 GetGlyphCount() const {
+            NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph");
+            return (mValue & GLYPH_COUNT_MASK) >> GLYPH_COUNT_SHIFT;
+        }
+
     private:
         PRUint32 mValue;
     };
 
     /**
      * When the glyphs for a character don't fit into a CompressedGlyph record
      * in SimpleGlyph format, we use an array of DetailedGlyphs instead.
      */
     struct DetailedGlyph {
-        /** This is true for the last DetailedGlyph in the array. This lets
-         * us track the length of the array. */
-        PRUint32 mIsLastGlyph:1;
-        /** The glyphID if this is a ComplexCluster, or the Unicode character
-         * if this is a Missing glyph */
-        PRUint32 mGlyphID:31;
+        /** The glyphID, or the Unicode character
+         * if this is a missing glyph */
+        PRUint32 mGlyphID;
         /** The advance, x-offset and y-offset of the glyph, in appunits
          *  mAdvance is in the text direction (RTL or LTR)
          *  mXOffset is always from left to right
          *  mYOffset is always from bottom to top */   
         PRInt32  mAdvance;
         float    mXOffset, mYOffset;
     };
 
     // The text is divided into GlyphRuns as necessary
     struct GlyphRun {
         nsRefPtr<gfxFont> mFont;   // never null
         PRUint32          mCharacterOffset; // into original UTF16 string
     };
 
-    class GlyphRunIterator {
+    class THEBES_API GlyphRunIterator {
     public:
         GlyphRunIterator(gfxTextRun *aTextRun, PRUint32 aStart, PRUint32 aLength)
           : mTextRun(aTextRun), mStartOffset(aStart), mEndOffset(aStart + aLength) {
             mNextIndex = mTextRun->FindFirstGlyphRunContaining(aStart);
         }
         PRBool NextRun();
         GlyphRun *GetGlyphRun() { return mGlyphRun; }
         PRUint32 GetStringStart() { return mStringStart; }
@@ -1171,47 +1180,40 @@ public:
      */
     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.
+     * Set the glyph data for a character. aGlyphs may be null if aGlyph is a
+     * simple glyph or has no associated glyphs. If non-null the data is copied,
+     * the caller retains ownership.
      */
-    void SetCharacterGlyph(PRUint32 aCharIndex, CompressedGlyph aGlyph) {
-        NS_ASSERTION(aCharIndex > 0 ||
-                     (aGlyph.IsClusterStart() && !aGlyph.IsLigatureContinuation()),
-                     "First character must be the start of a cluster and can't be a ligature continuation!");
+    void SetSimpleGlyph(PRUint32 aCharIndex, CompressedGlyph aGlyph) {
+        NS_ASSERTION(aGlyph.IsSimpleGlyph(), "Should be a simple glyph here");
         if (mCharacterGlyphs) {
             mCharacterGlyphs[aCharIndex] = aGlyph;
         }
         if (mDetailedGlyphs) {
             mDetailedGlyphs[aCharIndex] = nsnull;
         }
     }
-    /**
-     * Set some detailed glyphs for a character. The data is copied from aGlyphs,
-     * the caller retains ownership.
-     */
-    void SetDetailedGlyphs(PRUint32 aCharIndex, const DetailedGlyph *aGlyphs,
-                           PRUint32 aNumGlyphs);
-    void SetMissingGlyph(PRUint32 aCharIndex, PRUint32 aChar);
+    void SetGlyphs(PRUint32 aCharIndex, CompressedGlyph aGlyph,
+                   const DetailedGlyph *aGlyphs);
+    void SetMissingGlyph(PRUint32 aCharIndex, PRUint32 aUnicodeChar);
     void SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext, PRUint32 aCharIndex);
     
     void FetchGlyphExtents(gfxContext *aRefContext);
 
     // API for access to the raw glyph data, needed by gfxFont::Draw
     // and gfxFont::GetBoundingBox
     const CompressedGlyph *GetCharacterGlyphs() { return mCharacterGlyphs; }
     const DetailedGlyph *GetDetailedGlyphs(PRUint32 aCharIndex) {
-        // Although mDetailedGlyphs should be non-NULL when ComplexCluster,
-        // Missing glyphs need not have details.
         return mDetailedGlyphs ? mDetailedGlyphs[aCharIndex].get() : nsnull;
     }
     PRBool HasDetailedGlyphs() { return mDetailedGlyphs.get() != nsnull; }
     PRUint32 CountMissingGlyphs();
     const GlyphRun *GetGlyphRuns(PRUint32 *aNumGlyphRuns) {
         *aNumGlyphRuns = mGlyphRuns.Length();
         return mGlyphRuns.Elements();
     }
@@ -1249,22 +1251,17 @@ public:
     PRUint32 mCachedWords;
 #endif
 
 private:
     // **** general helpers **** 
 
     // Allocate aCount DetailedGlyphs for the given index
     DetailedGlyph *AllocateDetailedGlyphs(PRUint32 aCharIndex, PRUint32 aCount);
-    // Computes the x-advance for a given cluster starting at aClusterOffset. Does
-    // not include any spacing. Result is in appunits.
-    PRInt32 ComputeClusterAdvance(PRUint32 aClusterOffset);
 
-    void GetAdjustedSpacing(PRUint32 aStart, PRUint32 aEnd,
-                            PropertyProvider *aProvider, PropertyProvider::Spacing *aSpacing);
     // Spacing for characters outside the range aSpacingStart/aSpacingEnd
     // is assumed to be zero; such characters are not passed to aProvider.
     // This is useful to protect aProvider from being passed character indices
     // it is not currently able to handle.
     PRBool GetAdjustedSpacingArray(PRUint32 aStart, PRUint32 aEnd,
                                    PropertyProvider *aProvider,
                                    PRUint32 aSpacingStart, PRUint32 aSpacingEnd,
                                    nsTArray<PropertyProvider::Spacing> *aSpacing);
--- a/gfx/thebes/src/gfxAtsuiFonts.cpp
+++ b/gfx/thebes/src/gfxAtsuiFonts.cpp
@@ -478,17 +478,20 @@ SetupClusterBoundaries(gfxTextRun *aText
         status = UCFindTextBreak(locator, kUCTextBreakClusterMask, options,
                                  aString, length, breakOffset, &next);
         if (status != noErr)
             break;
         options |= kUCTextBreakIterateMask;
         PRUint32 i;
         for (i = breakOffset + 1; i < next; ++i) {
             gfxTextRun::CompressedGlyph g;
-            aTextRun->SetCharacterGlyph(i, g.SetClusterContinuation());
+            // Remember that this character is not the start of a cluster by
+            // setting its glyph data to "not a cluster start", "is a
+            // ligature start", with no glyphs.
+            aTextRun->SetGlyphs(i, g.SetComplex(PR_FALSE, PR_TRUE, 0), nsnull);
         }
         breakOffset = next;
     }
     UCDisposeTextBreakLocator(&locator);
 }
 
 #define UNICODE_LRO 0x202d
 #define UNICODE_RLO 0x202e
@@ -731,17 +734,17 @@ GetAdvanceAppUnits(ATSLayoutRecord *aGly
     Fixed fixedAdvance = aGlyphs[aGlyphCount].realPos - aGlyphs->realPos;
     return PRInt32((PRInt64(fixedAdvance)*aAppUnitsPerDevUnit + (1 << 15)) >> 16);
 }
 
 /**
  * Given a run of ATSUI glyphs that should be treated as a single cluster/ligature,
  * store them in the textrun at the appropriate character and set the
  * other characters involved to be ligature/cluster continuations as appropriate.
- */ 
+ */
 static void
 SetGlyphsForCharacterGroup(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount,
                            Fixed *aBaselineDeltas, PRUint32 aAppUnitsPerDevUnit,
                            gfxTextRun *aRun, PRUint32 aSegmentStart,
                            const PRPackedBool *aUnmatched,
                            const PRUnichar *aString,
                            const PRUint32 aLength)
 {
@@ -787,75 +790,101 @@ SetGlyphsForCharacterGroup(ATSLayoutReco
                 aRun->SetMissingGlyph(aSegmentStart + index, aString[index]);
             }
         }
         return;
     }
 
     gfxTextRun::CompressedGlyph g;
     PRUint32 offset;
+    // Make all but the first character in the group NOT be a ligature boundary,
+    // i.e. fuse the group into a ligature.
+    // Also make them not be cluster boundaries, i.e., fuse them into a cluster,
+    // if the glyphs are out of character order.
     for (offset = firstOffset + 2; offset <= lastOffset; offset += 2) {
-        PRUint32 index = offset/2;
-        if (!inOrder) {
-            // Because the characters in this group were not in the textrun's
-            // required order, we must make the entire group an indivisible cluster
-            aRun->SetCharacterGlyph(aSegmentStart + index, g.SetClusterContinuation());
-        } else if (!aRun->GetCharacterGlyphs()[index].IsClusterContinuation()) {
-            aRun->SetCharacterGlyph(aSegmentStart + index, g.SetLigatureContinuation());
-        }
+        PRUint32 index = offset/2;        
+        PRBool makeClusterStart = inOrder && aRun->IsClusterStart(index);
+        g.SetComplex(makeClusterStart, PR_FALSE, 0);
+        aRun->SetGlyphs(aSegmentStart + index, g, nsnull);
     }
 
     // Grab total advance for all glyphs
     PRInt32 advance = GetAdvanceAppUnits(aGlyphs, aGlyphCount, aAppUnitsPerDevUnit);
-    PRUint32 index = firstOffset/2;
+    PRUint32 charIndex = aSegmentStart + firstOffset/2;
     if (regularGlyphCount == 1) {
         if (advance >= 0 &&
             (!aBaselineDeltas || aBaselineDeltas[displayGlyph - aGlyphs] == 0) &&
             gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
             gfxTextRun::CompressedGlyph::IsSimpleGlyphID(displayGlyph->glyphID)) {
-            aRun->SetCharacterGlyph(aSegmentStart + index, g.SetSimpleGlyph(advance, displayGlyph->glyphID));
+            aRun->SetSimpleGlyph(charIndex, g.SetSimpleGlyph(advance, displayGlyph->glyphID));
             return;
         }
     }
 
     nsAutoTArray<gfxTextRun::DetailedGlyph,10> detailedGlyphs;
     ATSLayoutRecord *advanceStart = aGlyphs;
     for (i = 0; i < aGlyphCount; ++i) {
         ATSLayoutRecord *glyph = &aGlyphs[i];
         if (glyph->glyphID != ATSUI_SPECIAL_GLYPH_ID) {
+            if (glyph->originalOffset > firstOffset) {
+                PRUint32 glyphCharIndex = aSegmentStart + glyph->originalOffset/2;
+                PRUint32 glyphRunIndex = aRun->FindFirstGlyphRunContaining(glyphCharIndex);
+                PRUint32 numGlyphRuns;
+                const gfxTextRun::GlyphRun *glyphRun = aRun->GetGlyphRuns(&numGlyphRuns) + glyphRunIndex;
+
+                if (glyphRun->mCharacterOffset > charIndex) {
+                    // The font has changed inside the character group. This might
+                    // happen in some weird situations, e.g. if
+                    // ATSUI decides in LTR text to put the glyph for character
+                    // 1 before the glyph for character 0, AND decides to
+                    // give character 1's glyph a different font from character
+                    // 0. This sucks because we can't then safely move this
+                    // glyph to be associated with our first character.
+                    // To handle this we'd have to do some funky hacking with
+                    // glyph advances and offsets so that the glyphs stay
+                    // associated with the right characters but they are
+                    // displayed out of order. Let's not do this for now,
+                    // in the hope that it doesn't come up. If it does come up,
+                    // at least we can fix it right here without changing
+                    // any other code.
+                    NS_ERROR("Font change inside character group!");
+                    // Be safe, just throw out this glyph
+                    continue;
+                }
+            }
+
             gfxTextRun::DetailedGlyph *details = detailedGlyphs.AppendElement();
             if (!details)
                 return;
             details->mAdvance = 0;
-            details->mIsLastGlyph = PR_FALSE;
             details->mGlyphID = glyph->glyphID;
             details->mXOffset = 0;
             if (detailedGlyphs.Length() > 1) {
                 details->mXOffset +=
                     GetAdvanceAppUnits(advanceStart, glyph - advanceStart,
                                        aAppUnitsPerDevUnit);
             }
             details->mYOffset = !aBaselineDeltas ? 0.0f
                 : FixedToFloat(aBaselineDeltas[i])*aAppUnitsPerDevUnit;
         }
     }
     if (detailedGlyphs.Length() == 0) {
         NS_WARNING("No glyphs visible at all!");
-        aRun->SetCharacterGlyph(aSegmentStart + index, g.SetMissing());
+        aRun->SetGlyphs(aSegmentStart + charIndex, g.SetMissing(0), nsnull);
         return;
     }
 
     // The advance width for the whole cluster
     PRInt32 clusterAdvance = GetAdvanceAppUnits(aGlyphs, aGlyphCount, aAppUnitsPerDevUnit);
-    detailedGlyphs[detailedGlyphs.Length() - 1].mIsLastGlyph = PR_TRUE;
     if (aRun->IsRightToLeft())
         detailedGlyphs[0].mAdvance = clusterAdvance;
     else
         detailedGlyphs[detailedGlyphs.Length() - 1].mAdvance = clusterAdvance;
-    aRun->SetDetailedGlyphs(aSegmentStart + index, detailedGlyphs.Elements(), detailedGlyphs.Length());    
+    g.SetComplex(aRun->IsClusterStart(charIndex), PR_TRUE, detailedGlyphs.Length());
+    aRun->SetGlyphs(charIndex, g, detailedGlyphs.Elements());
 }
 
 /**
  * Returns true if there are overrunning glyphs
  */
 static PRBool
 PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun,
                    const PRUnichar *aString, PRBool aWrapped,
@@ -905,36 +934,31 @@ PostLayoutCallback(ATSULineRef aLine, gf
     PRUint32 allFlags = 0;
     // Now process the glyphs, which should basically be in
     // the textrun's desired order, so process them in textrun order
     PRInt32 direction = PRInt32(aRun->GetDirection());
     while (numGlyphs > 0) {
         PRUint32 glyphIndex = isRTL ? numGlyphs - 1 : 0;
         PRUint32 lastOffset = glyphRecords[glyphIndex].originalOffset;
         PRUint32 glyphCount = 1;
-        // Determine the glyphs for this group
+        // Determine the glyphs for this ligature group
         while (glyphCount < numGlyphs) {
             ATSLayoutRecord *glyph = &glyphRecords[glyphIndex + direction*glyphCount];
             PRUint32 glyphOffset = glyph->originalOffset;
             allFlags |= glyph->flags;
-            // Always add the current glyph to the group if it's for the same
-            // character as a character whose glyph is already in the group,
-            // or an earlier character. The latter can happen because ATSUI
-            // sometimes visually reorders glyphs; e.g. DEVANAGARI VOWEL I
-            // can have its glyph displayed before the glyph for the consonant that's
-            // it's logically after (even though this is all left-to-right text).
-            // In this case we need to make sure the glyph for the consonant
-            // is added to the group containing the vowel.
-            if (lastOffset < glyphOffset) {
-                if (!aRun->IsClusterStart(aSegmentStart + glyphOffset/2)) {
-                    // next character is a cluster continuation,
-                    // add it to the current group
-                    lastOffset = glyphOffset;
-                    continue;
-                }
+            if (glyphOffset <= lastOffset) {
+                // Always add the current glyph to the ligature group if it's for the same
+                // character as a character whose glyph is already in the group,
+                // or an earlier character. The latter can happen because ATSUI
+                // sometimes visually reorders glyphs; e.g. DEVANAGARI VOWEL I
+                // can have its glyph displayed before the glyph for the consonant that's
+                // it's logically after (even though this is all left-to-right text).
+                // In this case we need to make sure the glyph for the consonant
+                // is added to the group containing the vowel.
+            } else {
                 // We could be at the end of a character group
                 if (glyph->glyphID != ATSUI_SPECIAL_GLYPH_ID) {
                     // Next character is a normal character, stop the group here
                     break;
                 }
                 if (aUnmatched && aUnmatched[glyphOffset/2]) {
                     // Next character is ummatched, so definitely stop the group here
                     break;
@@ -1243,32 +1267,16 @@ AppendCJKPrefFonts(nsTArray<nsRefPtr<gfx
     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)
-{
-    //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());
-    }
-}
-
-static void
 DisableOptionalLigaturesInStyle(ATSUStyle aStyle)
 {
     static ATSUFontFeatureType selectors[] = {
         kCommonLigaturesOffSelector,
         kRareLigaturesOffSelector,
         kLogosOffSelector,
         kRebusPicturesOffSelector,
         kDiphthongLigaturesOffSelector,
@@ -1521,17 +1529,17 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRu
                 // glyphs exist for all characters in the [runStart, runStart + foundCharacters) substring
 
                 // in the RTL case, handle fallback mirroring
                 if (aRun->IsRightToLeft() && !firstFont->HasMirroringInfo()) {
                     MirrorSubstring(layout, mirroredStr, aString, aLength, runStart, runLength);
                 }
             
                 // add a glyph run for the entire substring
-                AddGlyphRun(aRun, firstFont, aSegmentStart + runStart - headerChars);
+                aRun->AddGlyphRun(firstFont, aSegmentStart + runStart - headerChars, PR_TRUE);
 
                 // do we have any more work to do?
                 if (status == noErr)
                     break;
             }
 
             if (firstTime && !HasFont(substituteFontID)) {
                 // XXX We are using kATSUSequentialFallbacksExclusive at first time.
@@ -1567,17 +1575,17 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRu
                     // 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);
+                    aRun->AddGlyphRun(font, aSegmentStart + changedOffset - headerChars, PR_TRUE);
                 } 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;
@@ -1592,17 +1600,17 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRu
                 // 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);
+                    aRun->AddGlyphRun(firstFont, aSegmentStart + changedOffset - headerChars, PR_TRUE);
 
                     if (!closure.mUnmatchedChars) {
                         closure.mUnmatchedChars = new PRPackedBool[aLength];
                         if (closure.mUnmatchedChars) {
                             memset(closure.mUnmatchedChars.get(), PR_FALSE, aLength);
                         }
                     }
 
--- a/gfx/thebes/src/gfxFont.cpp
+++ b/gfx/thebes/src/gfxFont.cpp
@@ -268,54 +268,48 @@ gfxFont::Draw(gfxTextRun *aTextRun, PRUi
             glyph->y = ToDeviceUnits(y, devUnitsPerAppUnit);
             if (isRTL) {
                 glyph->x -= ToDeviceUnits(advance, devUnitsPerAppUnit);
                 x -= advance;
             } else {
                 x += advance;
             }
             glyphs.Flush(cr, aDrawToPath);
-        } else if (glyphData->IsComplexCluster()) {
+        } else {
+            PRUint32 j;
+            PRUint32 glyphCount = glyphData->GetGlyphCount();
             const gfxTextRun::DetailedGlyph *details = aTextRun->GetDetailedGlyphs(i);
-            for (;;) {
-                glyph = glyphs.AppendGlyph();
-                glyph->index = details->mGlyphID;
-                glyph->x = ToDeviceUnits(x + details->mXOffset, devUnitsPerAppUnit);
-                glyph->y = ToDeviceUnits(y + details->mYOffset, devUnitsPerAppUnit);
+            for (j = 0; j < glyphCount; ++j, ++details) {
                 double advance = details->mAdvance;
-                if (isRTL) {
-                    glyph->x -= ToDeviceUnits(advance, devUnitsPerAppUnit);
-                }
-                x += direction*advance;
-
-                glyphs.Flush(cr, aDrawToPath);
-
-                if (details->mIsLastGlyph)
-                    break;
-                ++details;
-            }
-        } else if (glyphData->IsMissing()) {
-            const gfxTextRun::DetailedGlyph *details = aTextRun->GetDetailedGlyphs(i);
-            if (details) {
-                double advance = details->mAdvance;
-                if (!aDrawToPath) {
-                    gfxPoint pt(ToDeviceUnits(x, devUnitsPerAppUnit),
-                                ToDeviceUnits(y, devUnitsPerAppUnit));
-                    gfxFloat advanceDevUnits = ToDeviceUnits(advance, devUnitsPerAppUnit);
+                if (glyphData->IsMissing()) {
+                    if (!aDrawToPath) {
+                        gfxPoint pt(ToDeviceUnits(x, devUnitsPerAppUnit),
+                                    ToDeviceUnits(y, devUnitsPerAppUnit));
+                        gfxFloat advanceDevUnits = ToDeviceUnits(advance, devUnitsPerAppUnit);
+                        if (isRTL) {
+                            pt.x -= advanceDevUnits;
+                        }
+                        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;
+                    glyph->x = ToDeviceUnits(x + details->mXOffset, devUnitsPerAppUnit);
+                    glyph->y = ToDeviceUnits(y + details->mYOffset, devUnitsPerAppUnit);
                     if (isRTL) {
-                        pt.x -= advanceDevUnits;
+                        glyph->x -= ToDeviceUnits(advance, devUnitsPerAppUnit);
                     }
-                    gfxFloat height = GetMetrics().maxAscent;
-                    gfxRect glyphRect(pt.x, pt.y - height, advanceDevUnits, height);
-                    gfxFontMissingGlyphs::DrawMissingGlyph(aContext, glyphRect, details->mGlyphID);
+                    glyphs.Flush(cr, aDrawToPath);
                 }
                 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;
         }
     }
@@ -330,16 +324,37 @@ gfxFont::Draw(gfxTextRun *aTextRun, PRUi
     }
 
     // draw any remaining glyphs
     glyphs.Flush(cr, aDrawToPath, PR_TRUE);
 
     *aPt = gfxPoint(x, y);
 }
 
+static PRInt32
+GetAdvanceForGlyphs(gfxTextRun *aTextRun, PRUint32 aStart, PRUint32 aEnd)
+{
+    const gfxTextRun::CompressedGlyph *glyphData = aTextRun->GetCharacterGlyphs() + aStart;
+    PRInt32 advance = 0;
+    PRUint32 i;
+    for (i = aStart; i < aEnd; ++i, ++glyphData) {
+        if (glyphData->IsSimpleGlyph()) {
+            advance += glyphData->GetSimpleAdvance();   
+        } else {
+            PRUint32 glyphCount = glyphData->GetGlyphCount();
+            const gfxTextRun::DetailedGlyph *details = aTextRun->GetDetailedGlyphs(i);
+            PRUint32 j;
+            for (j = 0; j < glyphCount; ++j, ++details) {
+                advance += details->mAdvance;
+            }
+        }
+    }
+    return advance;
+}
+
 static void
 UnionWithXPoint(gfxRect *aRect, double aX)
 {
     if (aX < aRect->pos.x) {
         aRect->size.width += aRect->pos.x - aX;
         aRect->pos.x = aX;
     } else if (aX > aRect->XMost()) {
         aRect->size.width = aX - aRect->pos.x;
@@ -400,44 +415,32 @@ gfxFont::Measure(gfxTextRun *aTextRun,
                     if (isRTL) {
                         glyphRect.pos.x -= advance;
                     }
                     glyphRect.pos.x += x;
                     metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect);
                 }
             }
             x += direction*advance;
-        } else if (glyphData->IsComplexCluster()) {
+        } else {
+            PRUint32 glyphCount = glyphData->GetGlyphCount();
             const gfxTextRun::DetailedGlyph *details = aTextRun->GetDetailedGlyphs(i);
-            for (;;) {
+            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 =
                     extents->GetTightGlyphExtentsAppUnits(this, aRefContext, glyphIndex);
                 if (isRTL) {
                     glyphRect.pos.x -= advance;
                 }
                 glyphRect.pos.x += x;
                 metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect);
                 x += direction*advance;
-                if (details->mIsLastGlyph)
-                    break;
-                ++details;
-            }
-        } else if (glyphData->IsMissing()) {
-            const gfxTextRun::DetailedGlyph *details = aTextRun->GetDetailedGlyphs(i);
-            if (details) {
-                double advance = details->mAdvance;
-                gfxRect glyphRect(x, -metrics.mAscent, advance, metrics.mAscent);
-                if (isRTL) {
-                    glyphRect.pos.x -= advance;
-                }
-                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;
             }
@@ -1035,62 +1038,37 @@ gfxTextRun::SetPotentialLineBreaks(PRUin
             NS_WARNING("Break suggested inside cluster!");
             canBreak = PR_FALSE;
         }
         changed |= mCharacterGlyphs[aStart + i].SetCanBreakBefore(canBreak);
     }
     return changed != 0;
 }
 
-PRInt32
-gfxTextRun::ComputeClusterAdvance(PRUint32 aClusterOffset)
-{
-    CompressedGlyph *glyphData = &mCharacterGlyphs[aClusterOffset];
-    if (glyphData->IsSimpleGlyph())
-        return glyphData->GetSimpleAdvance();
-
-    const DetailedGlyph *details = GetDetailedGlyphs(aClusterOffset);
-    if (!details)
-        return 0;
-
-    PRInt32 advance = 0;
-    while (1) {
-        advance += details->mAdvance;
-        if (details->mIsLastGlyph)
-            return advance;
-        ++details;
-    }
-}
-
-static PRBool
-IsLigatureStart(gfxTextRun::CompressedGlyph aGlyph)
-{
-    return aGlyph.IsClusterStart() && !aGlyph.IsLigatureContinuation();
-}
-
 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; !IsLigatureStart(charGlyphs[i]); --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 && !IsLigatureStart(charGlyphs[i]); ++i) {
+    for (i = aPartStart + 1; i < mCharacterCount && !charGlyphs[i].IsLigatureGroupStart(); ++i) {
     }
     result.mLigatureEnd = i;
 
-    PRInt32 ligatureWidth = ComputeClusterAdvance(result.mLigatureStart);
+    PRInt32 ligatureWidth =
+        GetAdvanceForGlyphs(this, 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) {
         if (charGlyphs[i].IsClusterStart()) {
             ++totalClusterCount;
             if (i < aPartStart) {
@@ -1125,68 +1103,34 @@ gfxTextRun::ComputePartialLigatureWidth(
                                         PropertyProvider *aProvider)
 {
     if (aPartStart >= aPartEnd)
         return 0;
     LigatureData data = ComputeLigatureData(aPartStart, aPartEnd, aProvider);
     return data.mPartWidth;
 }
 
-void
-gfxTextRun::GetAdjustedSpacing(PRUint32 aStart, PRUint32 aEnd,
-                               PropertyProvider *aProvider,
-                               PropertyProvider::Spacing *aSpacing)
+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);
 
-    CompressedGlyph *charGlyphs = mCharacterGlyphs;
+#ifdef DEBUG
+    // Check to see if we have spacing inside ligatures
+
+    const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs();
     PRUint32 i;
 
-    if (mFlags & gfxTextRunFactory::TEXT_ABSOLUTE_SPACING) {
-        // Subtract character widths from mAfter at the end of clusters/ligatures to
-        // relativize spacing. This is a bit sad since we're going to add
-        // them in again below when we actually use the spacing, but this
-        // produces simpler code and absolute spacing is rarely required.
-        
-        // The width of the last nonligature cluster, in appunits
-        PRInt32 clusterWidth = 0;
-        for (i = aStart; i < aEnd; ++i) {
-            CompressedGlyph *glyphData = &charGlyphs[i];
-            
-            if (glyphData->IsSimpleGlyph()) {
-                if (i > aStart) {
-                    aSpacing[i - 1 - aStart].mAfter -= clusterWidth;
-                }
-                clusterWidth = glyphData->GetSimpleAdvance();
-            } else if (glyphData->IsComplexOrMissing()) {
-                if (i > aStart) {
-                    aSpacing[i - 1 - aStart].mAfter -= clusterWidth;
-                }
-                clusterWidth = 0;
-                const DetailedGlyph *details = GetDetailedGlyphs(i);
-                if (details) {
-                    while (1) {
-                        clusterWidth += details->mAdvance;
-                        if (details->mIsLastGlyph)
-                            break;
-                        ++details;
-                    }
-                }
-            }
-        }
-        aSpacing[aEnd - 1 - aStart].mAfter -= clusterWidth;
-    }
-
-#ifdef DEBUG
-    // Check to see if we have spacing inside ligatures
     for (i = aStart; i < aEnd; ++i) {
-        if (charGlyphs[i].IsLigatureContinuation()) {
+        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
 }
@@ -1197,35 +1141,35 @@ gfxTextRun::GetAdjustedSpacingArray(PRUi
                                     PRUint32 aSpacingStart, PRUint32 aSpacingEnd,
                                     nsTArray<PropertyProvider::Spacing> *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(aSpacingStart, aSpacingEnd, aProvider,
+    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 && !IsLigatureStart(charGlyphs[*aStart])) {
+    while (*aStart < *aEnd && !charGlyphs[*aStart].IsLigatureGroupStart()) {
         ++(*aStart);
     }
     if (*aEnd < mCharacterCount) {
-        while (*aEnd > *aStart && !IsLigatureStart(charGlyphs[*aEnd])) {
+        while (*aEnd > *aStart && !charGlyphs[*aEnd].IsLigatureGroupStart()) {
             --(*aEnd);
         }
     }
 }
 
 void
 gfxTextRun::DrawGlyphs(gfxFont *aFont, gfxContext *aContext,
                        PRBool aDrawToPath, gfxPoint *aPt,
@@ -1472,28 +1416,26 @@ gfxTextRun::BreakAndMeasureText(PRUint32
                                 PropertyProvider *aProvider,
                                 PRBool aSuppressInitialBreak,
                                 gfxFloat *aTrimWhitespace,
                                 Metrics *aMetrics, PRBool aTightBoundingBox,
                                 gfxContext *aRefContext,
                                 PRBool *aUsedHyphenation,
                                 PRUint32 *aLastBreak)
 {
-    CompressedGlyph *charGlyphs = mCharacterGlyphs;
-
     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(bufferStart, bufferStart + bufferLength, aProvider,
+        GetAdjustedSpacing(this, bufferStart, bufferStart + bufferLength, aProvider,
                            spacingBuffer);
     }
     PRPackedBool hyphenBuffer[MEASUREMENT_BUFFER_SIZE];
     PRBool haveHyphenation = (mFlags & gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS) != 0;
     if (haveHyphenation) {
         aProvider->GetHyphenationBreaks(bufferStart, bufferLength,
                                         hyphenBuffer);
     }
@@ -1517,17 +1459,17 @@ gfxTextRun::BreakAndMeasureText(PRUint32
 
     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(bufferStart, bufferStart + bufferLength, aProvider,
+                GetAdjustedSpacing(this, bufferStart, bufferStart + bufferLength, aProvider,
                                    spacingBuffer);
             }
             if (haveHyphenation) {
                 aProvider->GetHyphenationBreaks(bufferStart, bufferLength,
                                                 hyphenBuffer);
             }
         }
 
@@ -1552,38 +1494,25 @@ gfxTextRun::BreakAndMeasureText(PRUint32
             advance = 0;
             if (width - trimmableAdvance > aWidth) {
                 // No more text fits. Abort
                 aborted = PR_TRUE;
                 break;
             }
         }
         
-        gfxFloat charAdvance = 0;
+        gfxFloat charAdvance;
         if (i >= ligatureRunStart && i < ligatureRunEnd) {
-            CompressedGlyph *glyphData = &charGlyphs[i];
-            if (glyphData->IsSimpleGlyph()) {
-                charAdvance = glyphData->GetSimpleAdvance();
-            } else if (glyphData->IsComplexOrMissing()) {
-                const DetailedGlyph *details = GetDetailedGlyphs(i);
-                if (details) {
-                    while (1) {
-                        charAdvance += details->mAdvance;
-                        if (details->mIsLastGlyph)
-                            break;
-                        ++details;
-                    }
-                }
-            }
+            charAdvance = GetAdvanceForGlyphs(this, i, i + 1);
             if (haveSpacing) {
                 PropertyProvider::Spacing *space = &spacingBuffer[i - bufferStart];
                 charAdvance += space->mBefore + space->mAfter;
             }
         } else {
-            charAdvance += ComputePartialLigatureWidth(i, i + 1, aProvider);
+            charAdvance = ComputePartialLigatureWidth(i, i + 1, aProvider);
         }
         
         advance += charAdvance;
         if (aTrimWhitespace) {
             if (GetChar(i) == ' ') {
                 ++trimmableChars;
                 trimmableAdvance += charAdvance;
             } else {
@@ -1634,61 +1563,41 @@ gfxTextRun::BreakAndMeasureText(PRUint32
 
     return charsFit;
 }
 
 gfxFloat
 gfxTextRun::GetAdvanceWidth(PRUint32 aStart, PRUint32 aLength,
                             PropertyProvider *aProvider)
 {
-    CompressedGlyph *charGlyphs = mCharacterGlyphs;
-
     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<PropertyProvider::Spacing,200> spacingBuffer;
         if (spacingBuffer.AppendElements(aLength)) {
-            GetAdjustedSpacing(ligatureRunStart, ligatureRunEnd, aProvider,
+            GetAdjustedSpacing(this, ligatureRunStart, ligatureRunEnd, aProvider,
                                spacingBuffer.Elements());
             for (i = 0; i < ligatureRunEnd - ligatureRunStart; ++i) {
                 PropertyProvider::Spacing *space = &spacingBuffer[i];
                 result += space->mBefore + space->mAfter;
             }
         }
     }
 
-    PRUint32 i;
-    for (i = ligatureRunStart; i < ligatureRunEnd; ++i) {
-        CompressedGlyph *glyphData = &charGlyphs[i];
-        if (glyphData->IsSimpleGlyph()) {
-            result += glyphData->GetSimpleAdvance();
-        } else if (glyphData->IsComplexOrMissing()) {
-            const DetailedGlyph *details = GetDetailedGlyphs(i);
-            if (details) {
-                while (1) {
-                    result += details->mAdvance;
-                    if (details->mIsLastGlyph)
-                        break;
-                    ++details;
-                }
-            }
-        }
-    }
-
-    return result;
+    return result + GetAdvanceForGlyphs(this, ligatureRunStart, ligatureRunEnd);
 }
 
 PRBool
 gfxTextRun::SetLineBreaks(PRUint32 aStart, PRUint32 aLength,
                           PRBool aLineBreakBefore, PRBool aLineBreakAfter,
                           gfxFloat *aAdvanceWidthDelta,
                           gfxContext *aRefContext)
 {
@@ -1792,136 +1701,126 @@ gfxTextRun::DetailedGlyph *
 gfxTextRun::AllocateDetailedGlyphs(PRUint32 aIndex, PRUint32 aCount)
 {
     if (!mCharacterGlyphs)
         return nsnull;
 
     if (!mDetailedGlyphs) {
         mDetailedGlyphs = new nsAutoArrayPtr<DetailedGlyph>[mCharacterCount];
         if (!mDetailedGlyphs) {
-            mCharacterGlyphs[aIndex].SetMissing();
+            mCharacterGlyphs[aIndex].SetMissing(0);
             return nsnull;
         }
     }
     DetailedGlyph *details = new DetailedGlyph[aCount];
     if (!details) {
-        mCharacterGlyphs[aIndex].SetMissing();
+        mCharacterGlyphs[aIndex].SetMissing(0);
         return nsnull;
     }
     mDetailedGlyphs[aIndex] = details;
     return details;
 }
 
 void
-gfxTextRun::SetDetailedGlyphs(PRUint32 aIndex, const DetailedGlyph *aGlyphs,
-                              PRUint32 aCount)
+gfxTextRun::SetGlyphs(PRUint32 aIndex, CompressedGlyph aGlyph,
+                      const DetailedGlyph *aGlyphs)
 {
-    NS_ASSERTION(aCount > 0, "Can't set zero detailed glyphs");
-    NS_ASSERTION(aGlyphs[aCount - 1].mIsLastGlyph, "Failed to set last glyph flag");
+    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!");
 
-    DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, aCount);
-    if (!details)
-        return;
+    PRUint32 glyphCount = aGlyph.GetGlyphCount();
+    if (glyphCount > 0) {
+        DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, glyphCount);
+        if (!details)
+            return;
+        memcpy(details, aGlyphs, sizeof(DetailedGlyph)*glyphCount);
+    }
+    mCharacterGlyphs[aIndex] = aGlyph;
+}
 
-    memcpy(details, aGlyphs, sizeof(DetailedGlyph)*aCount);
-    mCharacterGlyphs[aIndex].SetComplexCluster();
-}
-  
 void
 gfxTextRun::SetMissingGlyph(PRUint32 aIndex, PRUint32 aChar)
 {
     DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
     if (!details)
         return;
 
-    details->mIsLastGlyph = PR_TRUE;
     details->mGlyphID = aChar;
     GlyphRun *glyphRun = &mGlyphRuns[FindFirstGlyphRunContaining(aIndex)];
     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();
+    mCharacterGlyphs[aIndex].SetMissing(1);
 }
 
 void
 gfxTextRun::RecordSurrogates(const PRUnichar *aString)
 {
     if (!(mFlags & gfxTextRunFactory::TEXT_HAS_SURROGATES))
         return;
 
     // Remember which characters are low surrogates (the second half of
     // a surrogate pair).
     PRUint32 i;
     gfxTextRun::CompressedGlyph g;
     for (i = 0; i < mCharacterCount; ++i) {
         if (NS_IS_LOW_SURROGATE(aString[i])) {
-            SetCharacterGlyph(i, g.SetLowSurrogate());
+            SetGlyphs(i, g.SetLowSurrogate(), nsnull);
         }
     }
 }
 
-static PRUint32
-CountDetailedGlyphs(gfxTextRun::DetailedGlyph *aGlyphs)
-{
-    PRUint32 i = 0;
-    while (!aGlyphs[i].mIsLastGlyph) {
-        ++i;
-    }
-    return i + 1;
-}
-
 static void
 ClearCharacters(gfxTextRun::CompressedGlyph *aGlyphs, PRUint32 aLength)
 {
-    gfxTextRun::CompressedGlyph g;
-    g.SetMissing();
-    PRUint32 i;
-    for (i = 0; i < aLength; ++i) {
-        aGlyphs[i] = g;
-    }
+    memset(aGlyphs, 0, sizeof(gfxTextRun::CompressedGlyph)*aLength);
 }
 
 void
 gfxTextRun::CopyGlyphDataFrom(gfxTextRun *aSource, PRUint32 aStart,
                               PRUint32 aLength, PRUint32 aDest,
                               PRBool aStealData)
 {
     NS_ASSERTION(aStart + aLength <= aSource->GetLength(),
                  "Source substring out of range");
     NS_ASSERTION(aDest + aLength <= GetLength(),
                  "Destination substring out of range");
 
     PRUint32 i;
+    // Copy base character data
     for (i = 0; i < aLength; ++i) {
         CompressedGlyph g = aSource->mCharacterGlyphs[i + aStart];
         g.SetCanBreakBefore(mCharacterGlyphs[i + aDest].CanBreakBefore());
         mCharacterGlyphs[i + aDest] = g;
         if (aStealData) {
-            aSource->mCharacterGlyphs[i + aStart].SetMissing();
+            aSource->mCharacterGlyphs[i + aStart].SetMissing(0);
         }
     }
 
+    // Copy detailed glyphs
     if (aSource->mDetailedGlyphs) {
         for (i = 0; i < aLength; ++i) {
             DetailedGlyph *details = aSource->mDetailedGlyphs[i + aStart];
             if (details) {
                 if (aStealData) {
                     if (!mDetailedGlyphs) {
                         mDetailedGlyphs = new nsAutoArrayPtr<DetailedGlyph>[mCharacterCount];
                         if (!mDetailedGlyphs) {
                             ClearCharacters(&mCharacterGlyphs[aDest], aLength);
                             return;
                         }
                     }        
                     mDetailedGlyphs[i + aDest] = details;
                     aSource->mDetailedGlyphs[i + aStart].forget();
                 } else {
-                    PRUint32 glyphCount = CountDetailedGlyphs(details);
+                    PRUint32 glyphCount = mCharacterGlyphs[i + aDest].GetGlyphCount();
                     DetailedGlyph *dest = AllocateDetailedGlyphs(i + aDest, glyphCount);
                     if (!dest) {
                         ClearCharacters(&mCharacterGlyphs[aDest], aLength);
                         return;
                     }
                     memcpy(dest, details, sizeof(DetailedGlyph)*glyphCount);
                 }
             } else if (mDetailedGlyphs) {
@@ -1929,16 +1828,17 @@ gfxTextRun::CopyGlyphDataFrom(gfxTextRun
             }
         }
     } else if (mDetailedGlyphs) {
         for (i = 0; i < aLength; ++i) {
             mDetailedGlyphs[i + aDest] = nsnull;
         }
     }
 
+    // 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
@@ -1978,17 +1878,17 @@ gfxTextRun::SetSpaceGlyph(gfxFont *aFont
             return;
         CopyGlyphDataFrom(textRun, 0, 1, aCharIndex, PR_TRUE);
         return;
     }
 
     AddGlyphRun(aFont, aCharIndex);
     CompressedGlyph g;
     g.SetSimpleGlyph(spaceWidthAppUnits, spaceGlyph);
-    SetCharacterGlyph(aCharIndex, g);
+    SetSimpleGlyph(aCharIndex, g);
 }
 
 void
 gfxTextRun::FetchGlyphExtents(gfxContext *aRefContext)
 {
     if (!NeedsGlyphExtents(this) && !mDetailedGlyphs)
         return;
 
@@ -2008,38 +1908,37 @@ gfxTextRun::FetchGlyphExtents(gfxContext
             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(this)) {
                     PRUint32 glyphIndex = glyphData->GetSimpleGlyph();
                     if (!extents->IsGlyphKnown(glyphIndex)) {
                         if (!fontIsSetup) {
                             font->SetupCairoFont(aRefContext);
-                            fontIsSetup = PR_TRUE;
+                             fontIsSetup = PR_TRUE;
                         }
 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
                         ++gGlyphExtentsSetupEagerSimple;
 #endif
                         font->SetupGlyphExtents(aRefContext, glyphIndex, PR_FALSE, extents);
                     }
                 }
-            } else if (glyphData->IsComplexCluster()) {
+            } else {
+                PRUint32 k;
+                PRUint32 glyphCount = glyphData->GetGlyphCount();
                 const gfxTextRun::DetailedGlyph *details = GetDetailedGlyphs(j);
-                for (;;) {
+                for (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);
                     }
-                    if (details->mIsLastGlyph)
-                        break;
-                    ++details;
                 }
             }
         }
     }
 }
--- a/gfx/thebes/src/gfxOS2Fonts.cpp
+++ b/gfx/thebes/src/gfxOS2Fonts.cpp
@@ -573,30 +573,30 @@ void gfxOS2FontGroup::CreateGlyphRunsFT(
             printf(" gid=%d, advance=%d (%s)\n", gid, advance,
                    NS_LossyConvertUTF16toASCII(font->GetName()).get());
 #endif
 
             if (advance >= 0 &&
                 gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
                 gfxTextRun::CompressedGlyph::IsSimpleGlyphID(gid))
             {
-                aTextRun->SetCharacterGlyph(utf16Offset,
-                                            g.SetSimpleGlyph(advance, gid));
+                aTextRun->SetSimpleGlyph(utf16Offset,
+                                         g.SetSimpleGlyph(advance, gid));
             } else if (gid == 0) {
                 // gid = 0 only happens when the glyph is missing from the font
                 aTextRun->SetMissingGlyph(utf16Offset, ch);
             } else {
                 gfxTextRun::DetailedGlyph details;
-                details.mIsLastGlyph = PR_TRUE;
                 details.mGlyphID = gid;
                 NS_ASSERTION(details.mGlyphID == gid, "Seriously weird glyph ID detected!");
                 details.mAdvance = advance;
                 details.mXOffset = 0;
                 details.mYOffset = 0;
-                aTextRun->SetDetailedGlyphs(utf16Offset, &details, 1);
+                g.SetComplex(aTextRun->IsClusterStart(utf16Offset), PR_TRUE, 1);
+                aTextRun->SetGlyphs(utf16Offset, g, &details);
             }
 
             NS_ASSERTION(!IS_SURROGATE(ch), "Surrogates shouldn't appear in UTF8");
             if (ch >= 0x10000) {
                 // This character is a surrogate pair in UTF16
                 ++utf16Offset;
             }
         }
--- a/gfx/thebes/src/gfxPangoFonts.cpp
+++ b/gfx/thebes/src/gfxPangoFonts.cpp
@@ -735,17 +735,17 @@ SetupClusterBoundaries(gfxTextRun* aText
     gfxTextRun::CompressedGlyph g;
 
     while (p < end) {
         PangoLogAttr *attr = buffer.Elements();
         pango_break(p, end - p, aAnalysis, attr, buffer.Length());
 
         while (p < end) {
             if (!attr->is_cursor_position) {
-                aTextRun->SetCharacterGlyph(aUTF16Offset, g.SetClusterContinuation());
+                aTextRun->SetGlyphs(aUTF16Offset, g.SetComplex(PR_FALSE, PR_TRUE, 0), nsnull);
             }
             ++aUTF16Offset;
         
             gunichar ch = g_utf8_get_char(p);
             NS_ASSERTION(!IS_SURROGATE(ch), "Shouldn't have surrogates in UTF8");
             if (ch >= 0x10000) {
                 ++aUTF16Offset;
             }
@@ -794,47 +794,48 @@ SetGlyphsForCharacterGroup(const PangoGl
     if (aOverrideSpaceWidth && aUTF8[0] == ' ' &&
         (utf16Offset + 1 == textRunLength ||
          charGlyphs[utf16Offset].IsClusterStart())) {
         width = aOverrideSpaceWidth;
     }
     PRInt32 advance = ConvertPangoToAppUnits(width, appUnitsPerDevUnit);
 
     gfxTextRun::CompressedGlyph g;
+    PRBool atClusterStart = aTextRun->IsClusterStart(utf16Offset);
     // See if we fit in the compressed area.
-    if (aGlyphCount == 1 && advance >= 0 &&
+    if (aGlyphCount == 1 && advance >= 0 && atClusterStart &&
         aGlyphs[0].geometry.x_offset == 0 &&
         aGlyphs[0].geometry.y_offset == 0 &&
         gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
         gfxTextRun::CompressedGlyph::IsSimpleGlyphID(aGlyphs[0].glyph)) {
-        aTextRun->SetCharacterGlyph(utf16Offset,
-                                    g.SetSimpleGlyph(advance, aGlyphs[0].glyph));
+        aTextRun->SetSimpleGlyph(utf16Offset,
+                                 g.SetSimpleGlyph(advance, aGlyphs[0].glyph));
     } else {
         nsAutoTArray<gfxTextRun::DetailedGlyph,10> detailedGlyphs;
         if (!detailedGlyphs.AppendElements(aGlyphCount))
             return NS_ERROR_OUT_OF_MEMORY;
 
         PRUint32 i;
         for (i = 0; i < aGlyphCount; ++i) {
             gfxTextRun::DetailedGlyph *details = &detailedGlyphs[i];
             PRUint32 j = (aTextRun->IsRightToLeft()) ? aGlyphCount - 1 - i : i; 
             const PangoGlyphInfo &glyph = aGlyphs[j];
-            details->mIsLastGlyph = i == aGlyphCount - 1;
             details->mGlyphID = glyph.glyph;
             NS_ASSERTION(details->mGlyphID == glyph.glyph,
                          "Seriously weird glyph ID detected!");
             details->mAdvance =
                 ConvertPangoToAppUnits(glyph.geometry.width,
                                        appUnitsPerDevUnit);
             details->mXOffset =
                 float(glyph.geometry.x_offset)*appUnitsPerDevUnit/PANGO_SCALE;
             details->mYOffset =
                 float(glyph.geometry.y_offset)*appUnitsPerDevUnit/PANGO_SCALE;
         }
-        aTextRun->SetDetailedGlyphs(utf16Offset, detailedGlyphs.Elements(), aGlyphCount);
+        g.SetComplex(atClusterStart, PR_TRUE, aGlyphCount);
+        aTextRun->SetGlyphs(utf16Offset, g, detailedGlyphs.Elements());
     }
 
     // Check for ligatures and set *aUTF16Offset.
     const gchar *p = aUTF8;
     const gchar *end = aUTF8 + aUTF8Length;
     while (1) {
         // Skip the CompressedGlyph that we have added, but check if the
         // character was supposed to be ignored. If it's supposed to be ignored,
@@ -854,22 +855,18 @@ SetGlyphsForCharacterGroup(const PangoGl
         if (p >= end)
             break;
 
         if (utf16Offset >= textRunLength) {
             NS_ERROR("Someone has added too many glyphs!");
             return NS_ERROR_FAILURE;
         }
 
-        if (! charGlyphs[utf16Offset].IsClusterContinuation()) {
-            // This is a separate grapheme cluster but it has no glyphs.
-            // It must be represented by a ligature with the previous
-            // grapheme cluster.
-            aTextRun->SetCharacterGlyph(utf16Offset, g.SetLigatureContinuation());
-        }
+        g.SetComplex(aTextRun->IsClusterStart(utf16Offset), PR_FALSE, 0);
+        aTextRun->SetGlyphs(utf16Offset, g, nsnull);
     }
     *aUTF16Offset = utf16Offset;
     return NS_OK;
 }
 
 nsresult
 gfxPangoFontGroup::SetGlyphs(gfxTextRun *aTextRun, gfxPangoFont *aFont,
                              const gchar *aUTF8, PRUint32 aUTF8Length,
@@ -1045,28 +1042,28 @@ gfxPangoFontGroup::CreateGlyphRunsFast(g
 
             PangoRectangle rect;
             pango_font_get_glyph_extents (pangofont, glyph, NULL, &rect);
 
             PRInt32 advance = PANGO_PIXELS (rect.width * appUnitsPerDevUnit);
             if (advance >= 0 &&
                 gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
                 gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyph)) {
-                aTextRun->SetCharacterGlyph(utf16Offset,
-                                            g.SetSimpleGlyph(advance, glyph));
+                aTextRun->SetSimpleGlyph(utf16Offset,
+                                         g.SetSimpleGlyph(advance, glyph));
             } else {
                 gfxTextRun::DetailedGlyph details;
-                details.mIsLastGlyph = PR_TRUE;
                 details.mGlyphID = glyph;
                 NS_ASSERTION(details.mGlyphID == glyph,
                              "Seriously weird glyph ID detected!");
                 details.mAdvance = advance;
                 details.mXOffset = 0;
                 details.mYOffset = 0;
-                aTextRun->SetDetailedGlyphs(utf16Offset, &details, 1);
+                g.SetComplex(aTextRun->IsClusterStart(utf16Offset), PR_TRUE, 1);
+                aTextRun->SetGlyphs(utf16Offset, g, &details);
             }
 
             NS_ASSERTION(!IS_SURROGATE(ch), "Surrogates shouldn't appear in UTF8");
             if (ch >= 0x10000) {
                 // This character is a surrogate pair in UTF16
                 ++utf16Offset;
             }
         }
--- a/gfx/thebes/src/gfxWindowsFonts.cpp
+++ b/gfx/thebes/src/gfxWindowsFonts.cpp
@@ -682,25 +682,24 @@ SetupTextRunFromGlyphs(gfxTextRun *aRun,
         lastWidth = partialWidthArray[i];
         PRInt32 advanceAppUnits = advancePixels*appUnitsPerDevPixel;
         WCHAR glyph = aGlyphs[i];
         NS_ASSERTION(!gfxFontGroup::IsInvalidChar(aRun->GetChar(i)),
                      "Invalid character detected!");
         if (advanceAppUnits >= 0 &&
             gfxTextRun::CompressedGlyph::IsSimpleAdvance(advanceAppUnits) &&
             gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyph)) {
-            aRun->SetCharacterGlyph(i, g.SetSimpleGlyph(advanceAppUnits, glyph));
+            aRun->SetSimpleGlyph(i, g.SetSimpleGlyph(advanceAppUnits, glyph));
         } else {
             gfxTextRun::DetailedGlyph details;
-            details.mIsLastGlyph = PR_TRUE;
             details.mGlyphID = glyph;
             details.mAdvance = advanceAppUnits;
             details.mXOffset = 0;
             details.mYOffset = 0;
-            aRun->SetDetailedGlyphs(i, &details, 1);
+            aRun->SetGlyphs(i, g.SetComplex(PR_TRUE, PR_TRUE, 1), &details);
         }
     }
     return PR_TRUE;
 }
 
 void
 gfxWindowsFontGroup::InitTextRunGDI(gfxContext *aContext, gfxTextRun *aRun,
                                     const char *aString, PRUint32 aLength)
@@ -1059,17 +1058,17 @@ public:
         if (FAILED(rv))
             return;
         gfxTextRun::CompressedGlyph g;
         // The first character is never inside a cluster. Windows might tell us
         // that it should be, but we have no before-character to cluster
         // it with so we just can't cluster it. So skip it here.
         for (PRUint32 i = 1; i < mRangeLength; ++i) {
             if (!logAttr[i].fCharStop) {
-                aRun->SetCharacterGlyph(i + aOffsetInRun, g.SetClusterContinuation());
+                aRun->SetGlyphs(i + aOffsetInRun, g.SetComplex(PR_FALSE, PR_TRUE, 0), nsnull);
             }
         }
     }
 
     void SaveGlyphs(gfxTextRun *aRun) {
         PRUint32 offsetInRun = mScriptItem->iCharPos + (mRangeString - mItemString);
         SetupClusterBoundaries(aRun, offsetInRun);
 
@@ -1081,20 +1080,18 @@ public:
 
         PRUint32 offset = 0;
         nsAutoTArray<gfxTextRun::DetailedGlyph,1> detailedGlyphs;
         gfxTextRun::CompressedGlyph g;
         const PRUint32 appUnitsPerDevUnit = aRun->GetAppUnitsPerDevUnit();
         while (offset < mRangeLength) {
             PRUint32 runOffset = offsetInRun + offset;
             if (offset > 0 && mClusters[offset] == mClusters[offset - 1]) {
-                if (!aRun->GetCharacterGlyphs()[runOffset].IsClusterContinuation()) {
-                    // No glyphs for character 'index', it must be a ligature continuation
-                    aRun->SetCharacterGlyph(runOffset, g.SetLigatureContinuation());
-                }
+                g.SetComplex(aRun->IsClusterStart(runOffset), PR_FALSE, 0);
+                aRun->SetGlyphs(runOffset, g, nsnull);
             } else {
                 // Count glyphs for this character
                 PRUint32 k = mClusters[offset];
                 PRUint32 glyphCount = mNumGlyphs - k;
                 PRUint32 nextClusterOffset;
                 PRBool missing = IsGlyphMissing(&sfp, k);
                 for (nextClusterOffset = offset + 1; nextClusterOffset < mRangeLength; ++nextClusterOffset) {
                     if (mClusters[nextClusterOffset] > k) {
@@ -1121,32 +1118,32 @@ public:
                                                                 mRangeString[offset + 1]));
                     } else {
                         aRun->SetMissingGlyph(runOffset, mRangeString[offset]);
                     }
                 } else if (glyphCount == 1 && advance >= 0 &&
                     mOffsets[k].dv == 0 && mOffsets[k].du == 0 &&
                     gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
                     gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyph)) {
-                    aRun->SetCharacterGlyph(runOffset, g.SetSimpleGlyph(advance, glyph));
+                    aRun->SetSimpleGlyph(runOffset, g.SetSimpleGlyph(advance, glyph));
                 } else {
                     if (detailedGlyphs.Length() < glyphCount) {
                         if (!detailedGlyphs.AppendElements(glyphCount - detailedGlyphs.Length()))
                             return;
                     }
                     PRUint32 i;
                     for (i = 0; i < glyphCount; ++i) {
                         gfxTextRun::DetailedGlyph *details = &detailedGlyphs[i];
-                        details->mIsLastGlyph = i == glyphCount - 1;
                         details->mGlyphID = mGlyphs[k + i];
                         details->mAdvance = mAdvances[k + i]*appUnitsPerDevUnit;
                         details->mXOffset = float(mOffsets[k + i].du)*appUnitsPerDevUnit*aRun->GetDirection();
                         details->mYOffset = float(mOffsets[k + i].dv)*appUnitsPerDevUnit;
                     }
-                    aRun->SetDetailedGlyphs(runOffset, detailedGlyphs.Elements(), glyphCount);
+                    aRun->SetGlyphs(runOffset,
+                        g.SetComplex(PR_TRUE, PR_TRUE, glyphCount), detailedGlyphs.Elements());
                 }
             }
             ++offset;
         }
     }
 
     void SetCurrentFont(gfxWindowsFont *aFont) {
         if (mCurrentFont != aFont) {
--- a/layout/generic/nsTextFrameThebes.cpp
+++ b/layout/generic/nsTextFrameThebes.cpp
@@ -2077,17 +2077,17 @@ PropertyProvider::GetSpacing(PRUint32 aS
 }
 
 static PRBool
 CanAddSpacingAfter(gfxTextRun* aTextRun, PRUint32 aOffset)
 {
   if (aOffset + 1 >= aTextRun->GetLength())
     return PR_TRUE;
   return aTextRun->IsClusterStart(aOffset + 1) &&
-    !aTextRun->IsLigatureContinuation(aOffset + 1);
+    aTextRun->IsLigatureGroupStart(aOffset + 1);
 }
 
 void
 PropertyProvider::GetSpacingInternal(PRUint32 aStart, PRUint32 aLength,
                                      Spacing* aSpacing, PRBool aIgnoreTabs)
 {
   NS_PRECONDITION(IsInBounds(mStart, mLength, aStart, aLength), "Range out of bounds");
 
--- a/layout/generic/nsTextRunTransformations.cpp
+++ b/layout/generic/nsTextRunTransformations.cpp
@@ -175,108 +175,101 @@ nsTransformingTextRunFactory::MakeTextRu
   // We'll only have a Unicode code path to minimize the amount of code needed
   // for these rarely used features
   NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aString), aLength);
   return MakeTextRun(unicodeString.get(), aLength, aParams, aFontGroup,
                      aFlags & ~(gfxFontGroup::TEXT_IS_PERSISTENT | gfxFontGroup::TEXT_IS_8BIT),
                      aStyles, aOwnsFactory);
 }
 
-static PRUint32
-CountGlyphs(const gfxTextRun::DetailedGlyph* aDetails) {
-  PRUint32 glyphCount;
-  for (glyphCount = 0; !aDetails[glyphCount].mIsLastGlyph; ++glyphCount) {
-  }
-  return glyphCount + 1;
-}
-
 /**
  * Copy a given textrun, but merge certain characters into a single logical
  * character. Glyphs for a character are added to the glyph list for the previous
  * character and then the merged character is eliminated. Visually the results
  * are identical.
  * 
  * This is used for text-transform:uppercase when we encounter a SZLIG,
  * whose uppercase form is "SS".
  * 
  * This function is unable to merge characters when they occur in different
  * glyph runs. It's hard to see how this could happen, but if it does, we just
  * discard the characters-to-merge.
  * 
+ * For simplicity, this produces a textrun containing all DetailedGlyphs,
+ * no simple glyphs. So don't call it unless you really have merging to do.
+ * 
  * @param aCharsToMerge when aCharsToMerge[i] is true, this character is
  * merged into the previous character
  */
 static void
 MergeCharactersInTextRun(gfxTextRun* aDest, gfxTextRun* aSrc,
                          PRPackedBool* aCharsToMerge)
 {
   aDest->ResetGlyphRuns();
 
-  PRUint32 numGlyphRuns;
-  const gfxTextRun::GlyphRun* glyphRuns = aSrc->GetGlyphRuns(&numGlyphRuns);
+  gfxTextRun::GlyphRunIterator iter(aSrc, 0, aSrc->GetLength());
   PRUint32 offset = 0;
-  PRUint32 j;
-  for (j = 0; j < numGlyphRuns; ++j) {
-    PRUint32 runOffset = glyphRuns[j].mCharacterOffset;
-    PRUint32 len =
-      (j + 1 < numGlyphRuns ? glyphRuns[j + 1].mCharacterOffset : aSrc->GetLength()) -
-      runOffset;
-    nsresult rv = aDest->AddGlyphRun(glyphRuns[j].mFont, offset);
+  nsAutoTArray<gfxTextRun::DetailedGlyph,2> glyphs;
+  while (iter.NextRun()) {
+    gfxTextRun::GlyphRun* run = iter.GetGlyphRun();
+    nsresult rv = aDest->AddGlyphRun(run->mFont, offset);
     if (NS_FAILED(rv))
       return;
 
+    PRBool anyMissing = PR_FALSE;
+    PRUint32 mergeRunStart = iter.GetStringStart();
     PRUint32 k;
-    for (k = 0; k < len; ++k) {
-      if (aCharsToMerge[runOffset + k])
-        continue;
-
-      gfxTextRun::CompressedGlyph g = aSrc->GetCharacterGlyphs()[runOffset + k];
-      if (g.IsSimpleGlyph() || g.IsComplexCluster()) {
-        PRUint32 mergedCount = 1;
-        PRBool multipleGlyphs = PR_FALSE;
-        while (k + mergedCount < len) {
-          gfxTextRun::CompressedGlyph h = aSrc->GetCharacterGlyphs()[runOffset + k + mergedCount];
-          if (!aCharsToMerge[runOffset + k + mergedCount] &&
-              !h.IsClusterContinuation() && !h.IsLigatureContinuation())
-            break;
-          if (h.IsComplexCluster() || h.IsSimpleGlyph()) {
-            multipleGlyphs = PR_TRUE;
-          }
-          ++mergedCount;
-        }
-        if (g.IsSimpleGlyph() && !multipleGlyphs) {
-          aDest->SetCharacterGlyph(offset, g);
-        } else {
-          // We have something complex to do.
-          nsAutoTArray<gfxTextRun::DetailedGlyph,2> detailedGlyphs;
-          PRUint32 m;
-          for (m = 0; m < mergedCount; ++m) {
-            gfxTextRun::CompressedGlyph h = aSrc->GetCharacterGlyphs()[runOffset + k + m];
-            if (h.IsSimpleGlyph()) {
-              gfxTextRun::DetailedGlyph* details = detailedGlyphs.AppendElement();
-              if (!details)
-                return;
-              details->mGlyphID = h.GetSimpleGlyph();
-              details->mAdvance = h.GetSimpleAdvance();
-              details->mXOffset = 0;
-              details->mYOffset = 0;
-            } else if (h.IsComplexCluster()) {
-              const gfxTextRun::DetailedGlyph* srcDetails = aSrc->GetDetailedGlyphs(runOffset + k + m);
-              detailedGlyphs.AppendElements(srcDetails, CountGlyphs(srcDetails));
-            }
-            detailedGlyphs[detailedGlyphs.Length() - 1].mIsLastGlyph = PR_FALSE;
-          }
-          detailedGlyphs[detailedGlyphs.Length() - 1].mIsLastGlyph = PR_TRUE;
-          aDest->SetDetailedGlyphs(offset, detailedGlyphs.Elements(), detailedGlyphs.Length());
+    for (k = iter.GetStringStart(); k < iter.GetStringEnd(); ++k) {
+      gfxTextRun::CompressedGlyph g = aSrc->GetCharacterGlyphs()[k];
+      if (g.IsSimpleGlyph()) {
+        if (!anyMissing) {
+          gfxTextRun::DetailedGlyph details;
+          details.mGlyphID = g.GetSimpleGlyph();
+          details.mAdvance = g.GetSimpleAdvance();
+          details.mXOffset = 0;
+          details.mYOffset = 0;
+          glyphs.AppendElement(details);
         }
       } else {
-        aDest->SetCharacterGlyph(offset, g);
+        if (g.IsMissing()) {
+          anyMissing = PR_TRUE;
+          glyphs.Clear();
+        }
+        glyphs.AppendElements(aSrc->GetDetailedGlyphs(k), g.GetGlyphCount());
+      }
+
+      // We could teach this method to handle merging of characters that aren't
+      // cluster starts or ligature group starts, but this is really only used
+      // to merge S's (uppercase &szlig;), so it's not worth it.
+
+      if (k + 1 < iter.GetStringEnd() && aCharsToMerge[k + 1]) {
+        NS_ASSERTION(g.IsClusterStart() && g.IsLigatureGroupStart() &&
+                     !g.IsLowSurrogate(),
+                     "Don't know how to merge this stuff");
+        continue;
       }
+
+      NS_ASSERTION(mergeRunStart == k ||
+                   (g.IsClusterStart() && g.IsLigatureGroupStart() &&
+                    !g.IsLowSurrogate()),
+                   "Don't know how to merge this stuff");
+
+      if (anyMissing) {
+        g.SetMissing(glyphs.Length());
+      } else {
+        g.SetComplex(PR_TRUE, PR_TRUE, glyphs.Length());
+      }
+      aDest->SetGlyphs(offset, g, glyphs.Elements());
       ++offset;
+      glyphs.Clear();
+      anyMissing = PR_FALSE;
+      mergeRunStart = k + 1;
     }
+    NS_ASSERTION(glyphs.Length() == 0,
+                 "Leftover glyphs, don't request merging of the last character with its next!");  
   }
   NS_ASSERTION(offset == aDest->GetLength(), "Bad offset calculations");
 }
 
 static gfxTextRunFactory::Parameters
 GetParametersForInner(nsTransformedTextRun* aTextRun, PRUint32* aFlags,
     gfxContext* aRefContext)
 {
@@ -505,11 +498,20 @@ nsCaseTransformTextRunFactory::RebuildTe
   if (!child)
     return;
   // Copy potential linebreaks into child so they're preserved
   // (and also child will be shaped appropriately)
   NS_ASSERTION(convertedString.Length() == canBreakBeforeArray.Length(),
                "Dropped characters or break-before values somewhere!");
   child->SetPotentialLineBreaks(0, canBreakBeforeArray.Length(),
       canBreakBeforeArray.Elements(), aRefContext);
-  // Now merge multiple characters into one multi-glyph character as required
-  MergeCharactersInTextRun(aTextRun, child, charsToMergeArray.Elements());
+
+  if (extraCharsCount > 0) {
+    // Now merge multiple characters into one multi-glyph character as required
+    MergeCharactersInTextRun(aTextRun, child, charsToMergeArray.Elements());
+  } else {
+    // No merging to do, so just copy; this produces a more optimized textrun.
+    // We can't steal the data because the child may be cached and stealing
+    // the data would break the cache.
+    aTextRun->ResetGlyphRuns();
+    aTextRun->CopyGlyphDataFrom(child, 0, child->GetLength(), 0, PR_FALSE);
+  }
 }